当前位置 博文首页 > 用心编码:深入理解JAVA虚拟机(一、类加载机制)

    用心编码:深入理解JAVA虚拟机(一、类加载机制)

    作者:[db:作者] 时间:2021-09-07 13:25

    问题

    • 虚拟机如何加载 Class 文件或者 字节码二进制流?
    • Class文件中的信息进入虚拟机后会发生什么变化?

    类加载机制

    加载流程

    虚拟机把描述类的数据从Class文件或者字节码二进制流数据加载到JVM内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。

    特点

    • 运行期完成类的加载、连接、初始化工作
    • 运行期动态加载和动态连接

    七阶段

    七大加载过程

    在这里插入图片描述

    Loading

    1.虚拟机通过一个类的全限定名来获取类的二进制字节流,Java虚拟机规范中并未明确指明类的二进制字节流只能来自于Class文件,这就给Java变成带来了很大的可扩展性,常见的获取java字节码二进制字节流的方式有以下几种:

    • 形成 JAR/EAR/WAR 包形式
    • Applet,从网络中获取二进制字节流
    • 动态代理、运行时计算生成, java.lang.reflect.Proxy
    • 其它文件生成,JSP->Class

    2.虚拟机获取到类字节码后,将该字节流锁代表的静态存储结构转化为方法区运行时的数据结构,这里的方法区运行时数据结构是由Java虚拟机实现所决定的,不同的java虚拟机,其方法区数据结构都会不同。

    3.在内存中生成一个代表这个类的 java.lang.Class 的对象,作为方法区这个类的所有数据的访问入口。java.lang.Class 可存储在虚拟机内存模型的堆或者方法区中,这由Java虚拟机实现所决定。

    通过以上三步基本上就完成了 类的加载阶段。

    Verification

    验证阶段是虚拟机的一项重要的工作,保证了java代码在虚拟机中安全、稳步运行,主要是为了减少一些不安全,会损害虚拟机的java代码进入虚拟机,但是通过验证的java代码,也不代表着一定不会损害虚拟机。总之出去安全的考虑,在虚拟机加载后,会验证二进制字节码数据流是否符合虚拟机的要求。主要验证一下4部分内容:

    • 二进制文件格式
      编译阶段已经将java代码编译称为特定格式的class文件,class是二进制数据,主要验证二进制字节流中的 魔数、JDK版本、常量池信息等。只有通过了二进制文件格式验证的字节流数据才会被虚拟机存储于虚拟机的内存模型的方法区中。Class文件格式将在 <<深入理解JAVA虚拟机(二、Class文件结构)>>中详细阐述。

    • 元数据
      元数据验证是对虚拟机中方法区中的数据进行语义分,验证其是否符合java语言规范。主要是对类的定义、属性定义进行校验。

    • 字节码
      字节码验证是对虚拟机中方法区中的数据的类的方法、逻辑处理进行校验,这部分校验工作比较复杂,也是校验过程中比较耗费性能的一个过程,而且校验结果不完全准确,毕竟这一阶段是通过程序去校验程序的逻辑性,可能存在一些case没有考虑周到的情况。

    • 符号引用
      符号引用也就是在类中调用了一些其它类的方法、属性等操作,符号引用是验证直接引用是否可以通过类的全限定名进行引用的过程。在解析阶段会直接进行方法、字段、接口进行直接应用。

    Preparation

    • 正式为类变量(static 修饰的变量称为类变量,普通变量称为对象变量)分配内存和为类变量设置零初始值
      在这里插入图片描述
    • 类变量的内存都将在方法区中进行分配。

    Resolution

    • 解析过程是将常量池中的符号引用替换为直接引用的过程。
    • 主要针对类或接口、字段、类方法、方法类型、方法句柄、调用点限定符进行解析。

    Initialization

    初始化5大场景(类没有初始化则会对类进行初始化)
    注意:初始化指的是:如果该类没有被初始化,那么满足下面的条件后会对类进行初始化操作。

    • new
      在java代码中通过new关键字进行初始化动态,虚拟机会对类进行初始化操作。
      当读取或者设置类的静态字段(final 除外)的时候也会触发类的初始化操作。
      当调用类的静态方法的时候也会触发类的初始化操作。
      字节码指令:new/getstatic/putstatic/invokestatic
    • 反射
      java.lang.reflect 发射调用
    • 先初始化父类
      初始化一个类时,如果其父类未初始化,那么会先初始化其父类。
      接口初始化的时候不要求其父接口也被初始化,接口初始化满足:在具体使用的时候再去初始化其父接口。
    • 执行主类
      虚拟机启动时,需要制定一个执行主类(main方法),执行主类会被初始化。
    • 动态语言(扩展Scala…)
      为了扩展虚拟机,虚拟机在设计的时候已经考虑到,可能将虚拟机应用于不同的平台,不同的开发语言,也就出现了现在的可以运行于虚拟机上的Scala、Groovy语言。

    类加载器

    双亲委派模型(Parents Delegation Model)
    在这里插入图片描述
    未完待续…

    特殊性

    • 数组类
      数组类的加载本身不由类加载器进行加载,而是由Java虚拟机直接创建。
    • 被动引用
      1.子类调用父类静态字段,不会触发此类初始化。
      2.通过数组来定义引用类,不会触发此类初始化。
      3.常量在编译阶段会存入调用类的常量池中,本质上没有直接引用定义常量的类,因此不会触发常量类的初始化。(比如:在编译完后,其实不将常量类对接的字节码文件加入运行环境,代码也是可以运行的。)

    内容总结

    1.类加载机制
    2.类加载过程
    3.类加载器

    cs