类加载时机
- 类从被加载到虚拟机到卸载出内存为止
- 整个生命周期:加载、验证、准备、解析、初始化、使用、卸载七个阶段,其中验证、准备、解析三个阶段统称为链接
初始化
有且只有五中情况必须对类进行初始化:
- 遇到new、getstatic、putstatic、invokestatic这4条字节码指令时,如果类没有进行初始化需要对其进行初始化
- 使用java.lang.reflect包通过反射对类进行调用时,如果类没有进行初始化需要对其进行初始化
- 初始化一个类时,如果发现其父类还未进行初始化,需要先对其父类进行初始化
- 虚拟机启动时需要指定一个执行的主类(包含main()函数),虚拟机会先初始化这个类
- 当时用JDK动态语言支持时,如果一个java.lang.invoke.MethodHandler实例最后
的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
类的加载过程
加载:
- 通过一个类的全限定名来获取该类的二进制字节流
- 将这个字节流所代表的静态结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象作为方法区这个类的各种数据的访问入口
验证:
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备:
- 给类变量分配内存并初始化0值
解析:
-
虚拟机将常量池内的符号引用替换为直接引用的过程
符号引用:
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要能够无歧义的定位到目标即可。
直接引用:
直接引用是可以直接只想目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用和虚拟机实现的内存布局相关,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用往往是不一样的。如果有了直接引用,那么引用的对象必然已经存在于内存中。
初始化:
- 在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其他资源
类加载器
类加载器的定义:实现“通过一个类的全限定名来获取此类的二进制流”这个动作的外部代码模块
比较两个类是否相等:来源于同一个class文件、被同一个虚拟机加载、加载它们的类加载器相同
双亲委派模型
工作流程:一个类加载器收到了加载类的请求,其本身不会直接去进行加载,而是把这个加载的请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传到顶层的启动类加载其中,只有当父类加载器反馈自己无法完成这个加载请求(搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
好处:java类随着它的类加载器具备了一种带有优先级的层级关系。
例如类java.lang.Object,它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委给处于模型最顶端的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个称为java.lang.Object的类,并放在程序的ClassPath中,那系统中将会出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。