一个类从加载到虚拟机内存到跌在出内存,拥有七个阶段:加载、验证、准备、解析、初始化、使用、卸载。
加载阶段,虚拟机会完成三件事
包含四部分:文件格式验证、元数据验证、字节码验证和符号引用验证
如果全部代码已经被反复的验证过了,可以通过-Xverify:none关闭大部分的类验证,缩短虚拟机加载时间。
为静态变量分配内存,并设置类变量的初始值,比如int类型初始值是0。
但是被final修饰的静态变量,将会根据该字段表中的ConstantValue赋初始值,public static final int value = 123;
中value将会被赋与123而不是0。
解析动作主要针对类、接口、字段、类方法、接口方法、方法类型等。将常量池中的符号引用替换为直接引用。符号引用指通过任何形式的字面量定位到目标;直接引语是指直接指向目标的指针、相对偏移量或者句柄。
在准备阶段,类变量已经赋予一次初始零值,在初始化阶段,还将执行类构造器<clinit>()
,<clinit>()
将自动收集类中所有类变量的赋值动作和静态语句块中的语句。在子类执行<clinit>()
之前,要先执行父类的<clinit>()
。在多线程环境下,<clinit>()
将会被正确地加锁同步。
REF_getStatic
、 REF_putStatic
、 REF_invokeStatic
、 REF_newInvokeSpecial
四种类型的方法句柄, 并且这个方法句柄对应的类没有进行过初始化, 则需要先触发其初始化。对于任意一个类, 都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性, 每一个类加载器, 都拥有一个独立的类名称空间。 因此 即使这两个类来源于同一个Class文件, 被同一个Java虚拟机加载, 只要加载它们的类加载器不同, 那这两个类就必定不相等。
自JDK1.2开始,Java一直保持这个三层类加载器、双亲委派的类加载架构。
三层类加载器分为启动器类加载器、扩展类加载器和应用程序类加载器。
<JAVA_HOME>\lib
目录或者被-Xbootclasspath参数指定的路径下的类库(rt.jar、tools.jar等)加载到虚拟机内存<JAVA_HOME>\lib\ext
目录中活着被java.ext.dirs系统变量指定的路径中所有的类库。在JDK9之后,扩展机制被模块化所取代。各种类加载器之间的层次关系被称为类加载器的“双亲委派模型”,这确保了同一个Class文件总是被同一个类加载器所加载,确立其在Java虚拟机中的唯一性。
双亲委派模型的工作过程是: 如果一个类加载器收到了类加载的请求, 它首先不会自己去尝试加载这个类, 而是把这个请求委派给父类加载器去完成, 每一个层次的类加载器都是如此, 因此所有的加载请求最终都应该传送到最顶层的启动类加载器中, 只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类) 时, 子加载器才会尝试自己去完成加载 。例如类java.lang.Object, 它存放在rt.jar之中, 无论哪一个类加载器要加载这个类, 最终都是委派给处于模型最顶端的启动类加载器进行加载, 因此Object类在程序的各种类加载器环境中都能够保证是同一个类。
JDK 9的模块不仅仅像之前的JAR包那样只是简单地充当代码的容器, 除了代码外, Java的模块定义还包含以下内容:
依赖其他模块的列表。
与类路径(ClassPath)对应的,JDK9提出了模块路径(ModulePath),一个类是一个模块还是传统的jar包,只依赖于它放在哪种路径上。
JAR文件在类路径的访问规则:类路径下的所有JAR文件都会被打包成一个匿名模块,它没有任何隔离,可以看到和使用类路径上所有的包、JDK系统模块中所有的导出包, 以及模块路径上所有模块中导出的包。
模块在模块路径的访问规则:具名模块只能访问到它依赖定义中列明的模块和包,但是看不见Jar包内容
JAR文件在模块路径的访问规则:它将变成一个自动模块,默认依赖于整个模块路径中的所有模块。
扩展类加载器被平台类加载器取代。此外,类加载的委派关系也发生了变动。 当平台及应用程序类加载器收到类加载请求, 在委派给父加载器加载前, 要先判断该类是否能够归属到某一个系统模块中, 如果可以找到这样的归属关系, 就要优先委派给负责那个模块的加载器完成加载。