满嘴都是糖果

类加载的过程

一个类从加载到虚拟机内存到跌在出内存,拥有七个阶段:加载、验证、准备、解析、初始化、使用、卸载。

加载

加载阶段,虚拟机会完成三件事

  1. 通过类的全限定名获取二进制字节流
  2. 将字节流的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的方法入口

验证

包含四部分:文件格式验证、元数据验证、字节码验证和符号引用验证

  1. 文件格式验证:验证字节流是否复合Class文件格式的规范
  2. 元数据验证:对字节码描述的信息进行语义分析
  3. 字节码 验证:最复杂的阶段,对类的方法体进行校验,确定程序的语义是合法的,符合逻辑。
  4. 符号引用验证:对类自身意外的信息进行匹配性的验证。

如果全部代码已经被反复的验证过了,可以通过-Xverify:none关闭大部分的类验证,缩短虚拟机加载时间。

准备

为静态变量分配内存,并设置类变量的初始值,比如int类型初始值是0。

但是被final修饰的静态变量,将会根据该字段表中的ConstantValue赋初始值,public static final int value = 123;中value将会被赋与123而不是0。

解析

解析动作主要针对类、接口、字段、类方法、接口方法、方法类型等。将常量池中的符号引用替换为直接引用。符号引用指通过任何形式的字面量定位到目标;直接引语是指直接指向目标的指针、相对偏移量或者句柄。

初始化

在准备阶段,类变量已经赋予一次初始零值,在初始化阶段,还将执行类构造器<clinit>()<clinit>()将自动收集类中所有类变量的赋值动作和静态语句块中的语句。在子类执行<clinit>()之前,要先执行父类的<clinit>()。在多线程环境下,<clinit>()将会被正确地加锁同步

以下六种情况必须对类进行初始化
  1. 遇到new、 getstatic、 putstatic或invokestatic这四条字节码指令时, 如果类型没有进行过初始化, 则需要先触发其初始化阶段。 能够生成这四条指令的典型Java代码场景有: · 使用new关键字实例化对象的时候。 · 读取或设置一个类型的静态字段(被final修饰、 已在编译期把结果放入常量池的静态字段除外)的时候。 · 调用一个类型的静态方法的时候
  2. 使用java.lang.reflect包的方法对类型进行反射调用的时候, 如果类型没有进行过初始化, 则需要先触发其初始化。
  3. 当初始化类的时候, 如果发现其父类还没有进行过初始化, 则需要先触发其父类的初始化
  4. 当虚拟机启动时, 用户需要指定一个要执行的主类( 包含main()方法的那个类) , 虚拟机会先初始化这个主类。
  5. 当使用JDK 7新加入的动态语言支持时, 如果一个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStaticREF_putStaticREF_invokeStaticREF_newInvokeSpecial四种类型的方法句柄, 并且这个方法句柄对应的类没有进行过初始化, 则需要先触发其初始化。
  6. 当一个接口中定义了JDK 8新加入的默认方法( 被default关键字修饰的接口方法) 时, 如果有这个接口的实现类发生了初始化, 那该接口要在其之前被初始化。

类加载器

对于任意一个类, 都必须由加载它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性, 每一个类加载器, 都拥有一个独立的类名称空间。 因此 即使这两个类来源于同一个Class文件, 被同一个Java虚拟机加载, 只要加载它们的类加载器不同, 那这两个类就必定不相等。

自JDK1.2开始,Java一直保持这个三层类加载器双亲委派的类加载架构。

三层类加载器分为启动器类加载器、扩展类加载器和应用程序类加载器

各种类加载器之间的层次关系被称为类加载器的“双亲委派模型”,这确保了同一个Class文件总是被同一个类加载器所加载,确立其在Java虚拟机中的唯一性

双亲委派模型的工作过程是: 如果一个类加载器收到了类加载的请求, 它首先不会自己去尝试加载这个类, 而是把这个请求委派给父类加载器去完成, 每一个层次的类加载器都是如此, 因此所有的加载请求最终都应该传送到最顶层的启动类加载器中, 只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类) 时, 子加载器才会尝试自己去完成加载 。例如类java.lang.Object, 它存放在rt.jar之中, 无论哪一个类加载器要加载这个类, 最终都是委派给处于模型最顶端的启动类加载器进行加载, 因此Object类在程序的各种类加载器环境中都能够保证是同一个类。

模块化系统

JDK 9的模块不仅仅像之前的JAR包那样只是简单地充当代码的容器, 除了代码外, Java的模块定义还包含以下内容:

与类路径(ClassPath)对应的,JDK9提出了模块路径(ModulePath),一个类是一个模块还是传统的jar包,只依赖于它放在哪种路径上。

JAR文件在类路径的访问规则:类路径下的所有JAR文件都会被打包成一个匿名模块,它没有任何隔离,可以看到和使用类路径上所有的包、JDK系统模块中所有的导出包, 以及模块路径上所有模块中导出的包。

模块在模块路径的访问规则:具名模块只能访问到它依赖定义中列明的模块和包,但是看不见Jar包内容

JAR文件在模块路径的访问规则:它将变成一个自动模块,默认依赖于整个模块路径中的所有模块。

模块化下的类加载器

扩展类加载器被平台类加载器取代。此外,类加载的委派关系也发生了变动。 当平台及应用程序类加载器收到类加载请求, 在委派给父加载器加载前, 要先判断该类是否能够归属到某一个系统模块中, 如果可以找到这样的归属关系, 就要优先委派给负责那个模块的加载器完成加载。