Android Tech And Thoughts.

Java Class Loading

Word count: 1.9kReading time: 6 min
2019/11/29 Share

JVM结构.png

Java代码需要先编译成 class 字节码,然后被虚拟机解释执行

虚拟器类加载机制涉及到两个方面的内容:

1.虚拟机如何加载class文件

2.class文件中的信息进入到虚拟机后会发生什么变化

不同于 C/C++ 这种编译语言生成的可执行文件,Java字节码只有基于 JVM 才能真正执行。

把字节码从class文件(或其它外部来源)加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这一过程就是虚拟机的类加载机制。 –[深入理解Java虚拟机]

这里的类包括 类、接口、数组类(数组类是虚拟机内部产生的),本文主要讨论普通类的加载,无论哪种形式的类,其加载流程都是一样的

与那些需要在编译时进行连接工作的语言不同,在Java语言里面,类型的加载、连接和初始化都是在运行期间完成的。虽然这会让类加载时稍微增加一些性能开销,但是却提供了高度的灵活性。例如:用户可以让一个本地的应用程序在运行时从网络或者其他地方加载一个二进制流作为程序代码的一部分。

类加载的时机

类的生命周期

加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的,而解析阶段则不一定:

它在某些情况下可以在初始化阶段之后再开始,这是为了支持Java的运行时绑定(或者说动态绑定或者晚期绑定)

1.什么时候需要开始类加载的第一阶段:加载?

Java 虚拟机中并没有进行强行约束,这点可以交给虚拟机的具体实现来自由把握

2.但是,对于初始化阶段,则有明确的规定,有且只有以下五种情况,进行类的初始化

  • 遇到 new 、getstatic、putstatic、invokestaticm 这四条字节码指令时,如果类没有进行初始化,则进行初始化
  • 使用 java.lang.reflect 包的方法对类进行反射调用的时候
  • 当初始化一个类时,如果发现其父类还没有进行初始化
  • 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法的那个类),虚拟机会先初始化这个主类
  • 参见P211,暂时不太懂

除此之外,所有引用类的方法都不会触发初始化,成为被动引用。但是注意,不初始化,不代表没有执行前面的几个步骤,这个依据虚拟机的具体实现

类加载的过程

虚拟机类加载的过程可以分为三大步骤

1.Loading : 加载

2.Linking:链接

  1. 验证
  2. 准备
  3. 解析

3.Initializing:初始化

加载

“加载”是”类加载”(Class Loading)过程的一个阶段,要注意区分,也不要把类加载说成加载类

在加载阶段,虚拟机主要完成以下三个事情:

1)通过一个类的全限定名来获取定义此类的二进制字节流

2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

3)在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口

相对于类加载的其它阶段而言,一个非数组类的加载阶段(准确地说,是该阶段中获取二进制字节流的动作)是开发人员可控性最强的。这是因为加载阶段既可以使用系统提供的引导类加载器来完成,也可以由用户自定义的类加载器去完成


加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟所需的格式存储在方法区之中,方法区中的数据存储格式由虚拟机实现自行定义(虚拟机规范未规定此区域的具体数据结构)

T:需要注意的是,加载阶段与连接阶段的部分内容是交叉进行的

验证

验证是连接阶段的第一步,这一阶段的目的是为了确保 Class 文件的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

Java相对于其它语言(如C/C++)是相对安全的吗,使用纯粹的 Java 代码无法做到诸如访问数组边界以外的数据、将一个对象转换为它并未实现的类型、跳转到不存在的代码行之类的事情。但是,Class文件并不一定要求用Java源码编译出来,可以使用人恶化途径产生,甚至包括使用十六进制直接编写出来的Class文件(前面也提到了,Class文件的来源不确定),在字节码层面,上述Java代码无法做到的事情都是可以实现的。虚拟机如果不检查输入的字节流,对其完全信任的话,很可能因为载入了幽海的字节流而导致系统崩溃。

所以,验证对虚拟机是一项极其重要的工作

从整体上看,验证阶段大致会完成下面四个阶段的校验工作:

1.文件格式校验

2.元数据验证

3.字节码验证

4.符号引用验证

T:对于虚拟机的类加载机制来说,验证阶段是一个重要的、但非必要(因为对程序运行期没有影响)的阶段。

如果所运行的全部代码都确保安全,那么在实施阶段就可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间(毕竟这个阶段耗时还是很多的)

准备

准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。

强调一下:

  • 这时候进行内存分配的仅包含类变量(被static修饰的变量),而不包括实例变量

  • 这时候的初始化一般是零值(除非具有 ConstantValue 属性),但是也会有一些特殊情况:如果类字段的字段属性表中存在 ConstantValue 属性,那么再准备阶段 value 就会被初始化为 ConstantValue 属性所指定的值。

    public static final int value = 123;

    编译时 Javac 将会为 value 生成 ConstantValue 属性,在准备阶段虚拟机就会根据 Constant Value 的设置将 value 赋值为 123.

解析

解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程

  • 符号引用(SymbolicReferences):符号引用以一组符号来描述索引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可
  • 直接引用(Direct References):直接引用可以是直接指向目标的指针

总结:

classload.png

Thanks for Image from http://ilacoder.com/

Reference:

01.理解类加载机制

02.jvm类加载机制

03.面试-jvm类加载详解

03.深入理解Java虚拟机

CATALOG
  1. 1. 类加载的时机
  2. 2. 类加载的过程
    1. 2.1. 加载
    2. 2.2. 验证
    3. 2.3. 准备
    4. 2.4. 解析
    5. 2.5. 总结:
      1. 2.5.1. Reference: