如下内容为个人总结,如有错误,欢迎评论区指正;
-
类加载的时机
-
类加载到虚拟机内存开始,到卸载出内存,整个生命周期包括如下七个,其中加载、验证、准备、初始化、卸载这个五个过程的顺序是确定的;
-
加载
-
验证
-
准备
-
解析
-
初始化
-
使用
-
卸载
-
-
并且对于初始化,虚拟机规定了有且只有如下五种情况必须立即对类进行初始化
-
遇到new、getstatic、putstatic、invokestatic这四条字节码时,如果类没有进行初始化,必须先进行初始化;
-
使用java.lang.reflect对类进行反射调用时,如果类没有进行初始化,必须先进行初始化;
-
当初始化一个类时,如果他的父类还没有进行初始化,则需要先触发其父类的初始化;
-
当虚拟机启动时,用户需要指定要执行的主类,虚拟机会先初始化这个主类;
-
当使用动态语言支持,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个句柄对应的类没有进行初始化,则需要先进行初始化;
-
-
以上五类操作称为对一个类的主动引用,除此以外,其他引用类的方式都不会触发其初始化,称为被动引用;
-
被动引用的三个例子
-
对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过子类来引用父类的静态字段不会触发子类的静态字段;
-
通过数组定义引用类,不会触发该类的初始化;
-
SuperClass[] arr = new SuperClass[10];此种方式不会触发SupperClass的初始化;
-
-
常量在编译阶段会存入调用类的常量池,本质并没有直接引用定义常量的类,因此不会触发类的初始化;
-
-
-
-
类加载的过程
-
类加载包括,加载、验证、准备、解析、初始化,接下来看一下这个五个阶段所执行的具体动作;
-
加载
-
加载时“类加载(Class Loading)”的一个阶段,加载阶段虚拟机需要完成三件事情:
-
通过类的全限定名获取类的二进制字节流;
-
将这个字节流代表的静态存储结构转化为方法区的运行时数据结构;
-
在内存中生成代表这个类的java.lang.Class对象,作为访问方法区这个类的各种数据的入口;
-
-
-
验证
-
文件格式的验证
-
元数据的验证
-
这个类是否有父类(除了Object类,其他类都应该有父类);
-
这个类是否继承了不允许被继承的类,例如final修饰的类;
-
如果这个类不是抽象类,是否实现了父类或接口之中要求实现的类;
-
类中的字段、方法是否和父类发生了冲突;
-
-
字节码验证
-
符号引用验证
-
符号引用是否可以通过全限定名获取对应的类;
-
指定类中是否包含描述中的方法和字段;
-
符号引用中类、字段、方法的可见性(public、default、protected、default);
-
-
-
准备
-
准备阶段时正式为类变量分配内存并设置类变量初始值的阶段,这些变量将在方法区中分配,类变量指static修饰的变量,不包括实例变量,实例变量在对象实例化的时候在堆分配;
-
初始值一般为0,具体赋值将在初始化阶段进行;
-
常量直接赋值;
-
-
解析
-
解析阶段是虚拟机将常量池中的符号引用替换为直接引用(指针或句柄)的过程;
-
符号引用包括三类:
-
类和接口的全限定名
-
字段的名称和描述符
-
方法的名称和描述符
-
-
-
-
初始化
-
初始化类变量和其他资源
-
从另一个角度讲,可以理解为执行类构造器<clinit>()方法的执行过程;
-
<clinit>()时所有类变量的赋值动作和静态代码块(static{})中的语句合并产生;
-
-
-
参考《深入理解Java虚拟机》