(一)生命周期
类从被加载到虚拟机内存中开始,到卸载出内存为止,他的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中验证、准备和解析三个部分统称为连接(Linking)。如下图:
(二)顺序
**
加载、验证、准备、初始化和卸载五个阶段是顺序是确定的。但是解析阶段则不一定:他在某些情况下可以在初始化阶段之后在开始,这是为了支持Java语言的运行时绑定的(动态绑定或晚期绑定)
**
(三)什么时候初始化?
加载阶段什么时候执行交给虚拟机来自由把握,但是对于初始化阶段,虚拟机规范则规定有且只有以下五种情况必须立即对类进行“初始化”(加载、验证和准备肯定需要在此之前)。
- 1)遇到new(new 对象的时候)、getstatic、putstatic(读取或设置一个类的静态字段,final除外,因为final已在编译器把结果放入常量池了)、invokestatic(调用一个类的静态方法的时候)。
- 2)反射的时候,若没初始化,则先初始化。
- 3)当初始化一个类时,若发现其父类还没有进行过初始化,则需要先触发其父类的初始化。
- 4)当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的类),虚拟机会先初始化这个类。
- 5)当用JDK1.7的动态语言支持时,若一个MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
以上五种场景称为对一个类进行主动引用;除此之外,所有引用类的方式都不会触发初始化,称为被动应用。如下三个Dmeo都是被动引用。’
(四)被动引用Demo
Demo1 通过子类引用父类的静态字段,不会导致子类初始化
package com.designmodel.vm.day01;
/**
* 父类
*
* @author [email protected]
* @date 2017年4月27日
*/
public class SuperClass {
static {
System.out.println("SuperClass init。。。。");
}
public static int value =123;
}
package com.designmodel.vm.day01;
/**
* 子类
*
* @author [email protected]
* @date 2017年4月27日
*/
public class SubClass extends SuperClass {
static {
System.out.println("SubClass init。。。");
}
}
package com.designmodel.vm.day01;
/**
* 被动实用类字段演示
*
* @author [email protected]
* @date 2017年4月27日
*/
public class NotInitialzation {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
输出结果
SuperClass init。。。。
123
结果分析
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段只会触发父类的初始化而不会触发子类的初始化。,
Demo2通过数组定义来引用类,不会触发此类的初始化。
PS:引用上面的SuperClass
和SubClass
package com.designmodel.vm.day01;
/**
* 被动实用类字段演示
*
* @author [email protected]
* @date 2017年4月27日
*/
public class NotInitialzation {
public static void main(String[] args) {
SuperClass[] superClass = new SuperClass[100];
}
}
输出结果
空
结果分析
什么都没输出,说明并没有触发类SuperClass
的初始化,创建动作由字节码指令newarray触发。不会触发此类的初始化。
Demo3常量在编译阶段会存入调用类的常量池中,本质上没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
package com.designmodel.vm.day01;
/**
* 常量
*
* @author [email protected]
* @date 2017年4月27日
*/
public class ConstClass {
static {
System.out.println("ConstClass init。。。。");
}
public static final String HELLO = "Hello";
}
package com.designmodel.vm.day01;
/**
* 被动实用类字段演示
*
* @author [email protected]
* @date 2017年4月27日
*/
public class NotInitialzation {
public static void main(String[] args) {
System.out.println(ConstClass.HELLO);
}
}
输出结果
Hello
结果分析
从结果中可以发现,并没有初始化ConstClass
类,这是因为在编译阶段通过常量传播优化,已经将此常量的值Hello
存储到了NotInitialization
类的常量池中,以后NotInitialzation
对常量ConstClass.HELLO
的引用实际都被转化为NotInitialzation
类对自身常量池的引用了。也就是说,实际上NotInitialzation
的Class文件之中并没有ConstClass
类的符号引用入口。这两个类在编译成Class之后就不存在任何联系了。
(五)、接口与类初始化区别
接口初始化过程与类真正的区别在于第三种:
当一个类在初始化时,要求其父类全部都已经初始化过了,但是一个接口再初始化时,并不要求其父接口全部都完成了初始化,只有在真正使用到父接口的时候(如引用接口中定义的常量)才会初始化。
若有兴趣,欢迎来加入群,【Java初学者学习交流群】:458430385,此群有Java开发人员、UI设计人员和前端工程师。有问必答,共同探讨学习,一起进步!
欢迎关注我的微信公众号【Java码农社区】,会定时推送各种干货: