精华内容
下载资源
问答
  • 我们都知道,在Java语言中,有静态字段和实例字段,但是,它们两者之间的初始化说明还是有区别的!!! 1. 静态字段 static 静态字段 / 静态变量 的初始化过程,由Java虚拟机JVM加载后,自动进行静态字段初始...

    我们都知道,在Java语言中,有静态字段和实例字段,但是,它们两者之间的初始化是有区别的。

    其中,需要说明的一点就是:静态字段由类调用,实例字段由对象调用!!!

    1. 静态字段

    static 静态字段 / 静态变量 的初始化过程,由Java虚拟机JVM加载类后,自动进行静态字段初始化。

    ①静态字段的默认初始化:静态字段设置为其类型的默认值。

    ②静态字段的声明初始化:静态字段设置为声明时的初始化值。

    ③静态字段的静态块初始化:依次调用静态块进行初始化。

    从源程序的角度看,静态字段以上三种初始化的顺序:

    ①首先进行默认初始化。

    ②然后根据声明初始化、静态块初始化这两者在程序中的顺序来依次进行!!!(两者谁先谁后是不一定的)

    2. 实例字段 

    实例字段 / 实例变量 的初始化过程,由new进行实例初始化。

    ①实例字段的默认初始化:实例字段设置为其类型的默认值。

    ②实例字段的声明初始化:实例字段设置为声明时的初始化值。

    ③实例字段的实例构造方法初始化:根据实例构造方法签名,调用实例构造方法进行初始化。

    从源程序的角度看,实例字段以上三种初始化的顺序:

    ①首先必须进行默认初始化。

    ②然后进行声明初始化。

    ③最后进行实例构造方法的初始化。

    因为实例字段相对来说,比较好理解,所以我们在这里重点对静态字段的初始化进行讲解!!!

    我们依次来看下面的五个程序!!!👇👇👇 

    Program Ⅰ: (没有静态块,静态变量的声明初始化的顺序不同)

    class Test1 {
    	public Test1() {
    		System.out.println("Test1实例构造函数之前 a="+Test.a+",b="+Test.b);
    		Test.a++;
    		Test.b++;
    		System.out.println("Test1实例构造函数之后 a="+Test.a+",b="+Test.b);
    	}
    }
    public class Test {
    	public static Test1 t=new Test1();
    	public static int a;
    	public static int b=10;
    	public static void main(String[] args) {
    		System.out.println("最后的结果:a="+Test.a+",b="+Test.b);
    	}
    }

    Program Output Ⅰ: 

    Program Analysis Ⅰ:

    按此顺序初始化静态变量:默认初始化→声明初始化 

    默认初始化:静态变量按照声明的顺序依次设置为该类型的默认值。

    声明初始化:静态变量按声明的顺序依次设置为声明初始化的值,如果没有声明初始化就跳过。 

    静态字段初始化流程如下:

    1. 默认初始化:类被加载后,首先静态字段会进行默认初始化。结果:t=null,a=0,b=0

    2. 声明初始化:①首先进行静态字段t的声明初始化,创建Test1的实例,调用实例构造方法,执行之后的结果:a=1,b=1

                              ②然后进行静态字段a和b的声明初始化,因为静态字段a没有声明初始化,所以就跳过,并执行Test.a++,                     此时a=1;但是静态字段b由声明初始化,结果b=10。(大家对照这个流程,看一下上面的代码和运行结果)

     

    Program Ⅱ:(没有静态块,静态变量的声明初始化的顺序不同)

    class Test1 {
    	public Test1() {
    		System.out.println("Test1实例构造函数之前 a="+Test.a+",b="+Test.b);
    		Test.a++;
    		Test.b++;
    		System.out.println("Test1实例构造函数之后 a="+Test.a+",b="+Test.b);
    	}
    }
    public class Test {
    	public static int a;
    	public static int b=10;
            public static Test1 t=new Test1();
    	public static void main(String[] args) {
    		System.out.println("最后的结果:a="+Test.a+",b="+Test.b);
    	}
    }

    Program Output Ⅱ: 

    Program Analysis Ⅱ:

    按此顺序初始化静态变量:默认初始化→声明初始化 

    默认初始化:静态变量按照声明的顺序依次设置为该类型的默认值。

    声明初始化:静态变量按声明的顺序依次设置为声明初始化的值,如果没有声明初始化就跳过。 

    静态字段初始化流程如下:

    1. 默认初始化:类被加载后,首先静态字段会进行默认初始化。结果:t=null,a=0,b=0。

    2. 声明初始化:①首先进行静态字段a和b的声明初始化,因为静态字段a没有声明初始化,所以就跳过,结果还是a=0;但                     静态字段b有声明初始化,所以结果是b=10。

                             ②然后再进行静态字段t的声明初始化,创建Test1的实例,调用实例构造方法,Test.a++,Test.b++,执行                     之后的结果是a=1,b=11。(大家对照这个流程,看一下上面的代码和运行结果)

     

    Program Ⅲ:(有静态块,静态块定义在声明初始化语句之后)

    class Test1 {
    	public Test1() {
    		System.out.println("Test1实例构造函数之前 a="+Test.a+",b="+Test.b);
    		Test.a++;
    		Test.b++;
    		System.out.println("Test1实例构造函数之后 a="+Test.a+",b="+Test.b);
    	}
    }
    public class Test {
    	public static Test1 t=new Test1();
    	public static int a;
    	public static int b=10;
    	static {
    		a=20;
    		System.out.println("static静态代码块1之后 a="+Test.a+",b="+Test.b);
    	}
    	static {
    		b=30;
    		System.out.println("static静态代码块2之后 a="+Test.a+",b="+Test.b);
    	}
    	public static void main(String[] args) {
    		System.out.println("最后的结果:a="+Test.a+",b="+Test.b);
    	}
    }

    Program Output Ⅲ:

    Program Analysis Ⅲ:

    按此顺序初始化静态变量:默认初始化→声明初始化→静态代码块初始化

    默认初始化:静态变量按照声明的顺序依次设置为该类型的默认值。

    声明初始化:静态变量按声明的顺序依次设置为声明初始化的值,如果没有声明初始化就跳过。 

    静态代码块初始化:按照静态代码块的顺序依次设置静态字段的值。

    静态字段初始化流程如下:

    1. 默认初始化:类被加载后,首先静态字段会进行默认初始化。 结果:t=null,a=0,b=0。

    2. 声明初始化:①首先进行静态字段t的声明初始化,创建Test1的实例,调用实力构造方法,执行之后的结果是a=1,b=1

                              ②进行静态字段a和b的声明初始化,因为静态字段a没有声明初始化,所以就跳过,结果还是a=1;但是静                    态字段b有声明初始化,所以结果是b=10。

    3. 静态代码块初始化:①首先执行静态代码块1,对静态字段a进行静态块初始化,结果是a=20,b=10。

                                         ②然后执行静态代码块2,对静态字段b进行静态块初始化,结果是a=20,b=30。(大家对照这个流程,看一下上面的代码和运行结果)

     

    Program Ⅳ:(有静态块,静态块定义在声明初始化语句之前)

    class Test1 {
    	public Test1() {
    		System.out.println("Test1实例构造函数之前 a="+Test.a+",b="+Test.b);
    		Test.a++;
    		Test.b++;
    		System.out.println("Test1实例构造函数之后 a="+Test.a+",b="+Test.b);
    	}
    }
    public class Test {
    	static {
    		a=20;
    		System.out.println("static静态代码块1之后 a="+Test.a+",b="+Test.b);
    	}
    	static {
    		b=30;
    		System.out.println("static静态代码块2之后 a="+Test.a+",b="+Test.b);
    	}
    	public static Test1 t=new Test1();
    	public static int a;
    	public static int b=10;
    	public static void main(String[] args) {
    		System.out.println("最后的结果:a="+Test.a+",b="+Test.b);
    	}
    }

    Program Output Ⅳ:

    Program Analysis Ⅳ:

    按此顺序初始化静态变量:默认初始化→静态代码块初始化→声明初始化

    默认初始化:静态变量按照声明的顺序依次设置为该类型的默认值。

    静态代码块初始化:按静态块的顺序依次设置静态字段的值。 

    声明初始化:静态变量按声明的顺序依次设置为声明初始化的值,如果没有声明初始化就跳过。

    静态字段初始化流程如下:

    1. 默认初始化:类被加载后,首先静态字段会进行默认初始化。 结果:t=null,a=0,b=0。

    2. 静态代码块初始化:①首先执行静态代码块1,对静态字段a进行静态块初始化,结果是a=20,b=0。

                                         ②然后执行静态代码块2,对静态字段b进行静态块初始化,结果是a=20,b=30。

    3. 声明初始化:①首先进行静态字段t的声明初始化,创建Test1的实例,调用实例构造方法,执行后的结果是a=21,b=31

                              ②然后进行静态字段a和b的声明初始化,因为静态字段a没有声明初始化,所以就跳过,结果还是a=21;                      但是静态字段b有声明初始化,所以结果是b=10。(大家对照这个流程,看一下上面的代码和运行结果)

     

    Program Ⅴ:(有静态块,声明初始化语句在两个静态块定义之间)

    class Test1 {
    	public Test1() {
    		System.out.println("Test1实例构造函数之前 a="+Test.a+",b="+Test.b);
    		Test.a++;
    		Test.b++;
    		System.out.println("Test1实例构造函数之后 a="+Test.a+",b="+Test.b);
    	}
    }
    public class Test {
    	static {
    		a=20;
    		System.out.println("static静态代码块1之后 a="+Test.a+",b="+Test.b);
    	}
    	public static Test1 t=new Test1();
    	public static int a;
    	public static int b=10;
    	static {
    		b=30;
    		System.out.println("static静态代码块2之后 a="+Test.a+",b="+Test.b);
    	}
    	public static void main(String[] args) {
    		System.out.println("最后的结果:a="+Test.a+",b="+Test.b);
    	}
    }

    Program Output Ⅴ:

    Program Analysis Ⅴ:

    按此顺序初始化静态变量:默认初始化→静态代码块1初始化→声明初始化→静态代码块2初始化

    默认初始化:静态变量按照声明的顺序依次设置为该类型的默认值。

    静态代码块初始化:按静态块的顺序依次设置静态字段的值。 

    声明初始化:静态变量按声明的顺序依次设置为声明初始化的值,如果没有声明初始化就跳过。

    静态字段初始化流程如下:

    1. 默认初始化:类被加载后,首先静态字段会进行默认初始化。 结果:t=null,a=0,b=0。

    2. 静态代码块1初始化:对静态字段a进行静态块初始化,结果a=20,b=0。

    3. 声明初始化:①首先进行静态字段t的声明初始化,创建Test1的实例,调用实例构造方法,执行之后结果是a=21,b=1。

                              ②然后进行静态字段a和b的声明初始化,因为静态字段a没有声明初始化,所以就跳过,结果还是a=21;                      但是静态字段b有声明初始化,所以结果是b=10。

    4. 静态代码块2初始化:对静态字段b进行静态块初始化,结果a=21,b=30。(大家对照这个流程,看一下上面的代码和运行结果)


    根据上面这五个Java小程序,大家可能觉得比较多,看起来不舒服,但是静态字段这部分内容还是很重要的!!!

    所以希望大家可以耐心的去理解每一个程序代码,对我们后面的Java知识的学习还是有很大帮助的!!!😊😊😊

    展开全文
  • 文章目录加载机制加载机制分类、加载器、双亲委派机制加载机制分类加载器双亲委派机制加载.class文件的方式生命周期生命周期结束加载过程JVM初始化步骤加载时机类初始化时机初始化类实例化 ...

    平常总是对类的加载,初始化,实例化,类加载机制。。。等等概念容易搞混,这里记录一下!

    类加载机制

    虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型的过程就是虚拟机的 类加载机制

    类加载机制分类、类加载器、双亲委派机制

    类加载机制分类

    • 全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
    • 父类委托(双亲委派),先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类 (这个是JVM采用的!)

    • 缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效

    类加载器

    虚拟机设计团队把类加载中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码块称为类加载器。

    从Java开发人员的角度看,类加载器大致分为如下3种

    启动类加载器(Bootstrap Classloader):负责将存放在<JAVA_HOME>\lib(Javahome即jdk的安装目录)目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库即使放在lib下面也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被Java程序直接使用。

    扩展类加载器(Extension Classloader):该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的系统路径中的所有类库。开发者可以直接使用扩展类加载器。

    应用程序类加载器(Application Classloader):该加载器由sun.misc.Launcher$AppClassLoader实现,它负责加载用户类路径(ClassPath)上所指定的类库。开发者可以直接使用此加载器。如果应用程序中没有自定义的类加载器,那么这个就是程序默认执行的类加载器。(系统加载器)

    我们的应用程序都是由这3种类加载器相互配合进行加载的。如果有必要,还可以加入自定义的类加载器。

    这些类加载器之间的关系如下图:

    img

    双亲委派机制

    双亲委派模型的工作过程是:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求最终都应该到达顶层的启动类加载器。只有当父加载无法完成这个加载请求时,子加载器才会尝试自己去加载。

    1、当ApplicationClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

    2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

    3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

    4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

    ClassLoader源码分析:

    protected Class<?> loadClass(String name, boolean resolve)
            throws ClassNotFoundException
        {
            synchronized (getClassLoadingLock(name)) {
                // 先检查此类是否已被加载
                Class<?> c = findLoadedClass(name);
                if (c == null) {
                    long t0 = System.nanoTime();
                    try {
                        //委派给父类加载器去加载
                        if (parent != null) {
                            c = parent.loadClass(name, false);
                        } else {
                            //如果没有父加载器,则调用启动类加载器
                            c = findBootstrapClassOrNull(name);
                        }
                    } catch (ClassNotFoundException e) {
                        // ClassNotFoundException thrown if class not found
                        // from the non-null parent class loader
                    }
                    //如果父加载器无法加载,则调用本身加载器去加载
                    if (c == null) {
                        // If still not found, then invoke findClass in order
                        // to find the class.
                        long t1 = System.nanoTime();
                        c = findClass(name);
     
                        // this is the defining class loader; record the stats
                        sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                        sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                        sun.misc.PerfCounter.getFindClasses().increment();
                    }
                }
                if (resolve) {
                    resolveClass(c);
                }
                return c;
            }
        }                      
    

    双亲委派模型意义:

    • 系统类防止内存中出现多份同样的字节码
    • 保证Java程序安全稳定运行

    加载.class文件的方式

    • 从本地系统中直接加载
    • 通过网络下载.class文件
    • 从zip,jar等归档文件中加载.class文件
    • 从专有数据库中提取.class文件
    • 将Java源文件动态编译为.class文件

    类生命周期

    img

    类生命周期结束

    在如下几种情况下,Java虚拟机将结束生命周期

    • 执行了System.exit()方法

    • 程序正常执行结束

    • 程序在执行过程中遇到了异常或错误而异常终止

    • 由于操作系统出现错误而导致Java虚拟机进程终止

    类加载过程

    其中类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。 在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。

    • 加载:就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。

    • 连接:包括以下三个步骤

      • 验证:是否有正确的内部结构,并和其他类协调一致
      • 准备:负责为类的静态成员分配内存 (所使用的内存都将在方法区中进行分配),并设置默认初始化值 (注意这里并没有指定具体的值,只是默认值,比如int是0,boolean是false。指定具体的值那是在类的初始化。但是,如果是static final静态常量,则直接指定具体的值!)
      • 解析:将常量池内的符号引用替换为直接引用
    • 初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式:

      ①声明类变量并指定初始值

      ②使用静态代码块为类变量指定初始值

    JVM初始化步骤

    1、假如这个类还没有被加载和连接,则程序先加载并连接该类

    2、假如该类的直接父类还没有被初始化,则先初始化其直接父类

    3、假如类中有初始化语句,则系统依次执行这些初始化语句

    img

    类加载时机

    类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误

    所以什么情况下虚拟机需要开始加载一个类呢?虚拟机规范中并没有对此进行强制约束,这点可以交给虚拟机的具体实现来自由把握。

    类初始化时机

    注意和上面的类加载时机区别,不要搞混

    那么,什么情况下虚拟机需要开始初始化一个类呢?这在虚拟机规范中是有严格规定的,虚拟机规范指明 有且只有 五种情况必须立即对类进行初始化(而这一过程自然发生在加载、验证、准备之后):

    1) 遇到new、getstatic、putstatic或invokestatic这四条字节码指令(注意,newarray指令触发的只是数组类型本身的初始化,而不会导致其相关类型的初始化,比如,new String[]只会直接触发String[]类的初始化,也就是触发对类[Ljava.lang.String的初始化,而直接不会触发String类的初始化)时,如果类没有进行过初始化,则需要先对其进行初始化。生成这四条指令的最常见的Java代码场景是:

    • 使用new关键字实例化对象的时候;

    • 读取或设置一个类的静态字段(被final修饰,已在编译器把结果放入常量池的静态字段除外)的时候;

    • 调用一个类的静态方法的时候。

    2) 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化。

    3) 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

    4) 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类。

    5) 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getstatic,REF_putstatic,REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行初始化,则需要先出触发其初始化。

    注意,对于这五种会触发类进行初始化的场景,虚拟机规范中使用了一个很强烈的限定语:“有且只有”,这五种场景中的行为称为对一个类进行 主动引用。除此之外,所有引用类的方式,都不会触发初始化,称为 被动引用。
      
    被动引用的几种经典场景
    1)、通过子类引用父类的静态字段,不会导致子类初始化

     public class SSClass{
        static{
            System.out.println("SSClass");
        }
    }  
    
    public class SClass extends SSClass{
        static{
            System.out.println("SClass init!");
        }
    
        public static int value = 123;
    
        public SClass(){
            System.out.println("init SClass");
        }
    }
    
    public class SubClass extends SClass{
        static{
            System.out.println("SubClass init");
        }
    
        static int a;
    
        public SubClass(){
            System.out.println("init SubClass");
        }
    }
    
    public class NotInitialization{
        public static void main(String[] args){
            System.out.println(SubClass.value);
        }
    }
    /* Output: 
            SSClass
            SClass init!
            123     
     */
    

    对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。在本例中,由于value字段是在类SClass中定义的,因此该类会被初始化;此外,在初始化类SClass时,虚拟机会发现其父类SSClass还未被初始化,因此虚拟机将先初始化父类SSClass,然后初始化子类SClass,而SubClass始终不会被初始化。
    2)、通过数组定义来引用类,不会触发此类的初始化

    public class NotInitialization{
        public static void main(String[] args){
            SClass[] sca = new SClass[10];
        }
    }
    

    上述案例运行之后并没有任何输出,说明虚拟机并没有初始化类SClass。但是,这段代码触发了另外一个名为[Lcn.edu.tju.rico.SClass的类的初始化。从类名称我们可以看出,这个类代表了元素类型为SClass的一维数组,它是由虚拟机自动生成的,直接继承于Object的子类,创建动作由字节码指令newarray触发。
    3)、常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化

    public class ConstClass{
    
        static{
            System.out.println("ConstClass init!");
        }
    
        public static  final String CONSTANT = "hello world";
    }
    
    public class NotInitialization{
        public static void main(String[] args){
            System.out.println(ConstClass.CONSTANT);
        }
    }/* Output: 
            hello world
     *///:~
    

    上述代码运行之后,只输出 “hello world”,这是因为虽然在Java源码中引用了ConstClass类中的常量CONSTANT,但是编译阶段将此常量的值“hello world”存储到了NotInitialization常量池中,对常量ConstClass.CONSTANT的引用实际都被转化为NotInitialization类对自身常量池的引用了。也就是说,实际上NotInitialization的Class文件之中并没有ConstClass类的符号引用入口,这两个类在编译为Class文件之后就不存在关系了。

    类的初始化

    是完成程序执行前的准备工作。在这个阶段,静态的(变量,代码块)会被执行,而静态方法会在第一次调用才执行。同时在会开辟一块存储空间用来存放静态的数据。初始化只在类加载的时候执行一次。

    这里贴一张JVM的图:运行时区内存分配

    img测试类:

    public class StaticTest {
        public static void main(String[] args) {
            System.out.println("-----执行staticFunction()----");
            staticFunction();
            System.out.println("执行StaticTest st = new StaticTest();");
            StaticTest st = new StaticTest();
        }
    
        int a = 110;    // 实例变量
        static int b = 112;     // 静态变量
        static StaticTest st = new StaticTest();
    
        static {   //静态代码块
            System.out.println("1");
        }
    
        {       // 实例代码块
            System.out.println("2");
        }
    
        public StaticTest() {    // 实例构造器
            System.out.println("3");
            System.out.println("a=" + a + ",b=" + b);
        }
    
        public static void staticFunction() {   // 静态方法
            System.out.println("4");
        }
    
    }
    

    输出结果分析:

    前三行输出:是执行static StaticTest st = new StaticTest();相当于在实例化对象,而后由于执行了main方法,所以static静态块执行了,然后才执行staticFunction()。

    最后三行输出:就是执行StaticTest st = new StaticTest();,再次创建一个对象,注意这时候不会再执行静态代码块!只会首次执行一次!!

    2
    3
    a=110,b=112
    1
    -----执行staticFunction()----
    4
    执行StaticTest st = new StaticTest();
    2
    3
    a=110,b=112
    

    如果把static StaticTest st = new StaticTest();这个去掉,那输出如下:

    这里由于不用实例化对象,这里直接进入main方法,会先加载静态代码块,然后执行方法,最后创建对象!

    1
    -----执行staticFunction()----
    4
    执行StaticTest st = new StaticTest();
    2
    3
    a=110,b=112
    

    注意!!!
    main函数是static的,它执行前一定要先加载本类–也就是要进行类的初始化,所以静态代码块一定优先于main函数执行
    注意类的初始化顺序:变量(先static变量,后全局变量)、然后再static静态代码块!!!

    也就是单纯运行main函数也会输出如下结果:

       public static void main(String[] args) {
          /*  System.out.println("-----执行staticFunction()----");
            staticFunction();
            System.out.println("执行StaticTest st = new StaticTest();");
            StaticTest st = new StaticTest();*/
        }
        
    输出结果:    
    2
    3
    a=110,b=112
    1
    

    注意,对于这五种会触发类进行初始化的场景,虚拟机规范中使用了一个很强烈的限定语:“有且只有”,这五种场景中的行为称为对一个类进行 主动引用。除此之外,所有引用类的方式,都不会触发初始化,称为 被动引用。

    类的实例化

    类的实例化也叫对象初始化,这是一个概念。类是对象的抽象,对象是类的具体实例!

    是指创建一个对象的过程。这个过程中会在堆中开辟内存,将一些非静态的方法,变量存放在里面。在程序执行的过程中,可以创建多个对象,既多次实例化。每次实例化都会开辟一块新的内存。

    可以参考我的另一篇文章进行理解,new 一个对象到底发生了什么?https://blog.csdn.net/belongtocode/article/details/105794529

    public class Student {
        private String name = "林青霞";
        private int    age  = 27;
    
        public Student() {
            name = "刘意";
            age = 30;
        }
    
    }
    
    class StudentDemo {
        public static void main(String[] args) {
            Student s = new Student();
        }
    }
    
    

    上面main方法中运行的程序过程如下:
    (1)用户创建了一个Student对象,运行时JVM首先会去方法区寻找该对象的类型信息,没有则使用类加载器classloader将Student.class字节码文件加载至内存中的方法区,并将Student类的类型信息存放至方法区。
    (2)接着JVM在堆中为新的Student实例分配内存空间,这个实例持有着指向方法区的Student类型信息的引用,引用指的是类型信息在方法区中的内存地址。
    (3)在此运行的JVM进程中,会首先起一个线程跑该用户程序,而创建线程的同时也创建了一个虚拟机栈,虚拟机栈用来跟踪线程运行中的一系列方法调用的过程,每调用一个方法就会创建并往栈中压入一个栈帧,栈帧用来存储方法的参数,局部变量和运算过程的临时数据。上面程序中的stu是对Student的引用,就存放于栈中,并持有指向堆中Student实例的内存地址。
    (4)JVM根据stu引用持有的堆中对象的内存地址,定位到堆中的Student实例,由于堆中实例持有指向方法区的Student类型信息的引用,从而获得add()方法的字节码信息,接着执行add()方法包含的指令。

    image-20200829144652537

    对象的创建过程

    对象的内存布局:
    在 Hotspot 虚拟机中,对象在内存中的布局可以分为 3 块区域:对象头、实例数据和对齐填充。

    Step1:类加载检查

    虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那必须先执行相应的类加载过程。

    Step2:分配内存

    类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式“指针碰撞”“空闲列表” 两种,选择哪种分配方式由 Java 堆是否规整决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定

    Step3:初始化零值

    内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

    Step4:设置对象头

    初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息。 这些信息存放在对象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方式。

    Step5:执行 init 方法

    在上面工作都完成之后,从虚拟机的视角来看,一个新的对象已经产生了,但从 Java 程序的视角来看,对象创建才刚开始,<init> 方法还没有执行,所有的字段都还为零。所以一般来说,执行 new 指令之后会接着执行 <init> 方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完全产生出来。

    参考文章:
    https://www.cnblogs.com/ityouknow/p/5603287.html
    https://www.cnblogs.com/chenpt/p/9777367.html
    https://blog.csdn.net/qq_39429962/article/details/83190112
    https://blog.csdn.net/justloveyou_/article/details/72466105

    展开全文
  • 问题:字段初始值设定项无法引用非静态字段、方法或属性的问题下面代码出错的原因,在中定义的字段为什么不能用? public class Test { public Test() ...}C#规定在内部只能定义属性或者变量,并初始化

    问题:字段初始值设定项无法引用非静态字段、方法或属性的问题

    下面代码出错的原因,在类中定义的字段为什么不能用?
    public class Test
    {
    public Test()
    {
    }
    public int Age=23;
    public int temp = Age;//ERROR 字段初始值设定项无法引用非静态字段、方法或属性
    }

    C#规定在类内部只能定义属性或者变量,并初始化,不能直接变量引用变量。

    在初始化类实例之前就调用了字段

    c# 中字段的初始化先于构造函数。

    string y = “asdf” + x; 实际上是 y = “asdf” + this.x;

    因为这时this还没被初始化,所以编译会报错。

    属性内存和变量内存的分配的时间不一样。
    属性是用的时候分配,变量是在类初始化时候分配。

    展开全文
  • 3. 当创建一个的实例时,初始化实例字段 4. 静态或实例 字段间的初始化顺序与在的定义时,字段所处的行号顺序一致 5. 静态字段初始化过程: 1)载入虚拟机时,分配静态字段所需内存,并清空内存,实际上就是...

    4 . 4  成员初始化;6 . 9 初始化和类装载


    构建器的调用遵照下面的顺序:
    (1) 调用基础类构建器。这个步骤会不断重复下去,首先得到构建的是分级结构的根部,然后是下一个衍生
    类,等等。直到抵达最深一层的衍生类。
    (2) 按声明顺序调用成员初始化模块。
    (3) 调用衍生构建器的主体。


    0. 先初始化父类字段

    1. 先静态字段,再实例字段

    2. 当类载入虚拟机时(第一次调用静态字段,或静态方法,或实例化一个对象),初始化静态字段

    3. 当创建一个类的实例时,初始化实例字段

    4. 静态或实例 字段间的初始化顺序与在类的定义时,字段所处的行号顺序一致

    5. 静态字段初始化过程:

    1)类载入虚拟机时,分配静态字段所需内存,并清空内存,实际上就是置0:简单数据类型均为0值,布尔型为false,引用类型为null

    2)执行字段定义时的赋值操作

    3)执行静态初始化块的赋值操作

    6. 实例字段初始化过程:

    1)新建一个对象时,分配对象所需内存,当然也包括实例字段所需内存,并清空内存,实际上就是置0:简单数据类型均为0值,布尔型为false,引用类型为null

    2)执行字段定义时的赋值操作

    3)执行实例初始化块的赋值操作

    4)执行构造方法里的赋值操作


    7. 载入一个类时,若检测到它有父类,则先载入它的父类,如此递推的找到继承的根类,从根类往下执行载入


    8. 方法内定义的局部变量,与类成员字段不同,方法内的局部变量定义时不会执行清空内存操作,所以必须进行赋值,初始化之后才能使用这些局部变量



    9. 代码示例

    class CA

    {

    int i;

    CA(int i)

    {

    this.i = i;

    }

    }


    class Test

    {

    static int snum1;

    static int snum2 = 2;//定义时的赋值操作

    static CA sca1;

    static CA sca2 = new CA(2);

    static

    {//静态初始化话块里的的赋值操作

    snum1 = 1;

    snum2 = 22;

    sca1 = new CA(1);

    }


    int num3;

    int num4 = 4;//定义时的赋值操作

    CA ca3 = null;

    CA ca4 = new CA(4);

    {//实例初始化话块里的的赋值操作

    num3 = 3;

    num4 = 44;

    ca3 = new CA(3);

    }


    Test()

    {{//构造方法里的的赋值操作

    num4 = 444;

    ca4 = new CA(44);

    }

    }

    snum2 初始化过程中值的变化为:0(清空内存)、2(定义时赋值)、22(静态初始化块内赋值)

    num4 初始化过程中值的变化为:0(清空内存)、4(定义时赋值)、44(实例初始化块内赋值)、444()构造方法内赋值




    展开全文
  • 在一个静态初始化程序求值的时候,或者静态字段初始化的时候发生了异常,就会抛出一个ExceptionInInitializerError异常。 JDK中对NoClassDefFoundError的描述是: JVM(Java虚拟机)或者装载器(ClassLoader)实例.....
  • 本文实例分析了C#中结构(struct)的部分初始化和完全初始化,分享给大家供大家参考。具体分析如下: 假设有这样一个值类型struct,如下所示: public struct Size { public int Length; public int Width; public...
  • 刚学习C#时,在内定义变量时,经常会出现标题内容的问题,即字段初始值无法引用非静态字段、方法或属性。这是为什么呢? 下面我们拿一个小程序来举例子。关于的ArrayList 实例t的3种不同的生成方法。 class ...
  • 下面代码出错的原因,在中定义的字段为什么不能用? (1) public string text = test(); //提示 字段或属性的问题字段初始值设定项无法引用非静态字段、方法 protected void Page_Load(object sender, EventArgs...
  • 以及类实例初始化

    千次阅读 2006-08-26 16:59:00
    java虚拟机会在6种情况下初始化一个对象,这六种情况称为主动使用,分别是:l 创建的新实例l 调用中声明的静态方法l 操作或接口中声明的非常量静态字段l 调用Java API中特定的反射方法l 初始化一个的...
  • 加载时直接初始化静态字段; 加载时调用静态方法初始化静态字段; 实例化对象时,在调用构造函数之前代码块中初始化字段; 实例化对象时,在调用构造函数之时初始化字段;  初始化字段顺序1->2->3->4 ...
  • Java何时初始化final字段

    千次阅读 2017-02-19 01:18:02
    不可变的实例有天然是线程安全的优势,在写不可变的时候经常用到final字段, 比如: private final String name;但是这样写,一般的IDE应该会给出错误提示,说你没有正确的初始化。那么, 为什么final字段需要...
  • 初始化必须发生在装载、连接以后,java虚拟机一般会在每个或接口主动使用时才会对类型进行初始 化。下面六中情形符合主动使用的要求: 1.当创建某个的新实例时。注:包括创建的所有方式,如new、反射、克隆...
  • //如果是说结构体字段有默认的初始值,不能内联初始化,但是为什么这里又可以初始化呢? } public void showInfo() { Console.WriteLine("hahahha!"); } } Information info1 = new ...
  • Java实例变量初始化

    千次阅读 2018-04-11 10:04:08
    一旦一个被加载连接初始化,他就可以随时被使用了,程序可以访问他的静态字段,调用静态方法,或者创建它的实例。在Java程序中可以被明确或者隐含地实例化,有四种途径:明确使用new操作符;调用Class或者...
  • 从 HotSpot 源代码的角度,阐述了虚拟机内部是如何创建,表示一个 Java 的,静态变量存储在哪里,如何初始化,虚拟机如何访问到这些静态变量。并给出实验佐证。
  • 类初始化对象初始化

    千次阅读 2015-08-13 23:26:43
    的生命周期: 加载——> 验证、准备、解析——>初始化——>使用——>...生成这4条指令的最常见的java代码场景是:使用new关键字实例化对象的时候、读取或者设置一个的静态字段(被final修饰的除外),以及调用一
  • 这里直接给出C#成员一般初始化顺序: 子类静态字段 子类静态构造 子类实例字段 父类静态字段 父类静态构造 父类实例字段 父类实例构造 子类实例构造 为什么说是“一般”初始化顺序呢?因为根据结构的...
  •  如果中定义的字段不使用修饰符static,该字段为实例字段,每创建该的一个对象,在对象内创建一个该字段实例,创建它的对象被撤销,该字段对象也被撤销,实例字段采用如下方法引用:实例名.实例字段
  • C# 之 静态字段初始化

    2015-06-05 11:59:00
    当不存在 static 修饰符时,由该声明引入的字段为实例字段(实例变量)。  静态字段不属于某个特定的实例;相反,它只标识了一个存储位置。不管创建了多少个实例,对于关联的应用程序域来说,在任何时候静态字段...
  • C#本质上是一组类型声明(区别于...初始化是指为分配好内存空间的对象赋值的过程。 一些字段的创建也是按照上述的流程。 例如: int a; //int这种类型本来就是封装好的类型,它的声明包含在其他命名空间中,这里是...
  • 达梦数据库实例初始化参数的设置

    千次阅读 2020-02-24 17:44:14
    当达梦数据库安装完毕,开始初始化实例时,我们会遇到这样一个界面: 这么多的参数,许多朋友不知从何入手,在这里,我就按照从上往下的顺序,为大家解释一下。 1、簇大小: 即EXTENT_SIZE,数据文件使用的簇大小,...
  • 什么是实例字段

    千次阅读 2012-07-18 16:14:49
    实例字段定义  在所有的构造函数中被初始化为缺省值(0/false/null)  可以在一构造函数中显式初始化  可以在它们声明时初始化 sealed class Pair { public Pair(int x, int y) { this.x = ...
  • 接口初始化类初始化区别

    千次阅读 2017-11-09 20:59:10
    虚拟机规范严格规定了有且只有四种情况必须立即对进行初始化: 1. 遇到new、getstatic、putstatic、invokestatic这四条字节码指令时, 如果还没有进行过初始化,则需要先触发其初始化。 生成这四条指令最常见...
  • 这里的显式调用构造函数应该是指继承时子的构造函数中调用父类的构造函数吧。对象初始化时,调用父类的构造函数,早于子类成员变量的初始化,所以如2楼所说实例字段还没初始化
  • 然后,从Person派生出一个学生Student,为其扩展出一个学号字段,同时为学生创建构造函数来初始化其三个字段,要求其中从父类中继承的字段由父类的构造函数负责初始化。请利用静态字段的知识在学生中增加...
  • java对象初始化字段的默认值

    千次阅读 2011-02-12 17:43:28
    注意,java中任何对象初始化时,属性字段都有默认值,如一般char、String会被初始化为null(char为空),其他数值型初始化为0,如: Class Begin{ int i;//初始化为0 char c;//初始化为空 float f;//...
  • 关于final修饰的字段初始化问题

    千次阅读 2020-07-04 13:40:28
    事实证明, final修饰的非静态的成员变量是可以通过构造方法进行初始化的, 但是考虑到一个中的构造方法可能有多个因素, 我又加入了一个无参的构造方法 可以发现添加无参构造方法之后, 编译直接报错了, 但是如果在...
  • 类初始化阶段详解

    千次阅读 2018-03-04 20:46:24
    一个Java的完整的生命周期会经历加载、连接、初始化、使用、和卸载五个阶段,当然也有在加载或者连接之后没有被初始化就直接被使用的情况。如图所示: 初始化做了什么 为的静态变量赋予正确的初始值...
  • 类初始化

    千次阅读 2016-10-25 15:29:22
    主动引用类初始化加载过程的最后一个阶段,到初始化阶段,才真正开始执行中的Java程序代码。...生成这四条指令最常见的Java代码场景是:使用new关键字实例化对象时、读取或设置一个的静态字段

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 247,639
精华内容 99,055
关键字:

初始化类的实例字段