六种主动调用:html
1)、建立类的实例(new操做、反射、cloning、反序列化)java
2)、调用类的静态方法c++
3)、使用或对类/接口的static属性赋值(不包括static final的与在编译期肯定的常量表达式(包括常量、字符串常量))数组
4)、调用API中的反射方法,Class.forName()等。数据结构
5)、子类被初始化ide
6)、被设定为JVM启动时的启动类(含main方法的主类)post
其它都为被动引用:被动引用不会触发类的初始化操做(只会加载、连接),如仅申明一个类的引用、经过数组定义引用类等。url
加载、连接(验证、准备、解析)、初始化、使用、卸载spa
1)、加载.net
i)、java编译器加载类的二进制字节流文件(.class文件),若是该类有基类,向上一直加载到根基类(无论基类是否使用都会加载)。
ii)、将二进制字节码加载到内存,解析成方法区对应的数据结构。
iii)、在java逻辑堆中生成该类的java.lang.Class对象,做为方法区中该类的入口。
类加载器:分默认加载器和用户自定义加载器
Bootstrap ClassLoader:顶层加载器,由c++实现。负责JVM启动时加载JDK核心类库以及加载后面两个类加载器。
Extension ClassLoader:继承自ClassLoader的类,负责加载{JAVA_HOME}/jre/lib/ext目录下的全部jar包。
App ClassLoader:上面加载器的子对象,负责加载应用程序CLASSPATH目录下的class文件和jar包。
Customer ClassLoader:用户继承自ClassLoader类的自定义加载器,用来处理特殊加载需求。如Tomcat等都有本身实现的加载器。
类加载器采用双亲委托(自底向上查询)来避免重复加载类,而加载顺序倒是自顶向下的。
2)、连接
i)、验证:字节码完整性、final类与方法、方法签名等的检查验证。
ii)、准备:为静态变量分配存储空间(内存单元全置0,即基本类型为默认值,引用类型为null)。
iii)、解析(这步是可选的):将常量池内的符号引用替换为直接引用。
类的加载和连接只执行一次,故static成员也只加载一次,做为类所拥有、类的全部实例共享。
3)、初始化
包括类的初始化、对象的初始化。
类的初始化:
初始化静态字段(执行定义处的赋值表达式)、执行静态初始化块。
注:有父类则先递归的初始化父类的。
对象的初始化:
若是须要建立对象,则会执行建立对象并初始化:
i)、在堆上为建立的对象分配足够的存储空间,并将存储单元清零,即基本类型为默认值,引用类型为null。
i)、初始化非静态成员变量(即执行变量定义处的赋值表达式)。
ii)、执行构造方法。
注:若是有父类,则先递归的初始化父类成员,最后才是本类。
4)、使用
5)、卸载
对象的引用(栈中)在超出做用域后被系统当即回收;对象自己(堆中)被gc标记为垃圾,在gc下次执行垃圾处理时被回收。
一个类最早初始化static变量和static块;
而后分配该类以及父类的成员变量的内存空间,再赋值初始化,最后调用构造方法;
在父类与子类之间,老是优先建立、初始化父类。
即:(静态变量、静态初始化块)–>(变量、初始化块)–> 构造器,其中基类老是优先于子类的。
详细分析例题:
一、static静态成员初始化细节
1 public class Test8 { 2 3 public static void main(String[] args) { 4 System.out.println(Super.a); 5 System.out.println(Super.b); 6 System.out.println(Super.bb); 7 System.out.println(Super.c); 8 System.out.println(new Super("cc").c); 9 //对于“初始化”的意思,应该包括初始化和执行赋值表达式(若是有的话)。例如static成员的初始化实际上包括两步:准备阶段(JVM连接)中的内存分配(全置0,即基本类型成默认值,引用类型为null)和初始化中的static成员赋初值操做(即若是有的话,执行static字段定义处的赋值表达式) 10 //对于a、b 由于有赋初值的表达式,故会获得自定义的初始值。对于c则采用准备阶段的null。 11 //只有建立对象才会调用构造方法(执行构造方法中的动做)。b的值被从新设置。 12 13 Super sup2 = new Super("ccc"); //至关于每次新建对象都对'实例所共享、类全部的'b从新设值(是从新给b赋值,不是新建)。 14 System.out.print(Super.c); 15 } 16 17 } 18 19 20 class Super{ 21 static String a; 22 static String b = getB(); 23 static String bb = getC(); 24 static String c = "c"; 25 26 Super(String s){ 27 c = s; 28 } 29 30 static String getC(){ 31 return c; 32 } 33 34 static String getB(){ 35 return "b"; 36 } 37 }
二、‘单例模式’中静态成员初始化问题
1 public class Test9 { 2 3 public static void main(String[] args) { 4 System.out.println(Single.b); 5 System.out.println(Single.c); 6 //在Single类加载的连接阶段静态字段都置默认值(基本类型为默认,引用为null),因此sin、b、c首先为null、0、0。 7 //而后按照定义的顺序执行初始化赋值,先执行sin的赋值,由于用new建立对象,因此会执行构造方法,而后b=1,c=1。这时由于static字段只加载一次,因此b、c只是作赋值操做(有赋值表达式的话),因此b没操做,c从新赋值为0。 8 9 10 //那么,若是交换静态字段sin和c的位置,上面输出? 11 } 12 13 } 14 15 16 class Single{ 17 private static Single sin = new Single(); 18 public static int b; 19 public static int c = 0; 20 21 private Single(){ 22 b++; 23 c++; 24 } 25 26 public Single getInstance(){ 27 return sin; 28 } 29 }
三、构造方法内的多态对初始化的影响
1 public class Test10 { 2 3 public static void main(String[] args) { 4 new SubTest(); 5 6 //输出为:SuperTest() before draw() 7 // SubTest() ,i = 0 8 // SuperTest() after draw() 9 // SubTest() ,i = 1 10 11 //分析:由于没有静态成员,因此在用new建立子类对象时,先在堆中为该对象分配足够空间(内存空间全置二进制的0,即基本类型为默认值,引用类型为null), 12 //而后,调用父类构造方法(有实例变量会先初始化实例变量),但draw()调用的是子类的重写方法,那么问题是,这时候子类实例变量i只分配了内存空间(默认值为0), 13 //尚未初始化,因此输出的i为0。直到子类初始化实例变量时,i才被赋值为1,最后执行子类的构造方法,因此输出i为1。 14 } 15 16 } 17 18 19 class SuperTest{ 20 SuperTest(){ 21 System.out.println("SuperTest() before draw()"); 22 draw(); //调用子类的重写方法(多态) 23 System.out.println("SuperTest() after draw()"); 24 } 25 void draw(){ 26 System.out.println("super draw"); 27 } 28 29 } 30 31 class SubTest extends SuperTest{ 32 private int i = 1; 33 SubTest(){ 34 System.out.println("SubTest() ,i = "+i); } 35 @Override 36 void draw(){ 37 System.out.println("SubTest() ,i = "+i); 38 } 39 }
总结:类的加载与初始化顺序上面已经总结了。但实际判断时任然须要谨慎。
i)、对于不少书上说的和你们挂在嘴边的“初始化”一词,如‘初始化‘静态变量、‘初始化’实例变量。这里的初始化个人更细入的理解是,‘初始化’包括“分配内存空间”和“执行赋值表达式”两步。
ii)、“分配内存空间”,即将获取到的内存单元所有置为二进制的0(对于基本类型天然就是默认值,对于引用类型都为null),而这一步是无论变量定义处的赋值表达式的。如int a ; int b =1; 在这一步都是同样置为二进制的0的。
“执行赋值表达式”,便是在变量“分配内存空间”后对变量的赋值操做。如 int a;int b =1; 在这一步a没有赋值操做,b就有赋值操做了,而后a依然仍是分配内存空间后的默认值,而b就从新赋值为1了。
iii)、“初始化”即先分配内存空间,再对变量执行赋值表达式(若是有的话)。这样分前后的意义保证了对变量的赋值前,变量已经获取到了正确的初始内存空间。如static变量的初始化,实际上在’准备阶段‘就分配好内存单元,
在’初始化阶段‘的第一步才执行定义处的赋值表达式。这就是例一中考察的重点,在分配内存空间后与执行定义处的赋值操做后获得的值不同。又如实例变量的初始化,他的所谓“初始化”也是分两个阶段的,不过他的两个
阶段间相隔的操做很少,因此看成一个概念一般不会出问题,但遇到例三的状况就出问题了。参考《Thinking In Java》中的建议就是“尽可能在构造方法中慎用非final或private(隐式为final)方法”
iiii)、对于个人理解把“初始化”细化为“分配内存空间”和“执行赋值表达式”两步,其实也挺纠结的。’分配内存空间’即包括内存空间的初始分配,而后变量也天然获得初始值了(对于基本类型天然就是默认值,对于引用类型都为null),
这不就是“初始化”的意思嘛?而“执行赋值表达式”更像是用户根据本身的程序须要设置自定义的初始值,而不是分配内存空间后的默认值(这应该就是一般意义的“初始化”了吧)。而这个设置自定义初始值的行为,
便可以是在变量的定义处,也能够是在构造方法中,或者在须要时刻的方法调用中(惰性初始化)。而这种设置自定义初始值的行为的正确保证,就是上面总结的“类的加载与初始化顺序”的严格顺序执行。