• JVM系列(一):JVM类加载过程详解


    Java 通过引入字节码和 JVM 机制,提供了强大的跨平台能力,理解 Java 的类加载机制是深入 Java 开发的必要条件。

    一、Java代码执行流程

    Java程序运行时,必须经过编译运行两个步骤。首先将后缀名为.java的源文件进行编译,最终生成后缀名为.class的字节码文件。然后JVM虚拟机启动时,会初始化好类加载器(ClassLoader)。通过ClassLoader,JVM将编译好的字节码文件加载到内存(类加载)。最后由JVM对加载到内存的java类进行解释执行,显示结果。

    以我们常见的Test.java为例,具体流程如下图所示:
    请添加图片描述

    二、类加载过程

    类加载过程主要分为三个步骤:加载链接初始化,而其中链接过程又分为三个步骤:验证准备解析,加上卸载使用两个步骤统称为为类的生命周期

    请添加图片描述

    加载

    简单来说,加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。

    1、字节码来源

    由于没有具体指明需要在哪里获取class文件,导致字节码来源途径非常丰富:

    • 从压缩包中读取,如jar、war

    • 从网络中获取,如Web Applet

    • 动态生成,如动态代理、CGLIB

    • 由其他文件生成,如JSP

    • 从数据库读取

    • 从加密文件中读取

    2、内存储存
    • 静态储存解析成运行时数据,存放在方法区

    • 堆区生成该类的Class对象,作为方法区这个类的各种数据的访问入口

    链接

    验证

    验证阶段主要是为了为了确保Class文件的字节流中包含的信息符合虚拟机要求,并且不会危害虚拟机。

    而验证主要分为以下四类:

    • 文件格式验证

    • 元数据验证

    • 字节码验证

    • 符号引用验证

    准备

    准备阶段会为类的静态变量分配内存、赋初值

    数据类型

    零值

    int

    0

    long

    0L

    short

    (short)0

    char

    ‘’

    byte

    (byte)0

    boolean

    false

    float

    0.0f

    double

    0.0d

    reference

    null

    需要注意的有以下几点:

    • 实例变量是在创建对象的时候完成赋值的,没有赋初值一说
    • final修饰的常量在编译的时候会给属性添加ConstantValue属性,准备阶段直接完成赋值,即没有赋初值这一步

    解析

    解析阶段会将符号引用替换为直接引用,该过程也被称为静态链接

    1、符号引用

    以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可

    2、直接引用

    可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。而直接引用必须引用的目标已经在内存中存在

    3、动态链接

    即符号引用替换为直接引用的阶段,该阶段会把一些静态方法(符号引用,比如main()方法)替换为指向数据所存内存的指针或句柄等(直接引用)。

    初始化

    初始化阶段是执行类构造器 () 方法的过程。这一步主要的目的是:根据程序员程序编码制定的主观计划去初始化类变量和其他资源。

    1、执行类构造器()方法的过程
    • 对类的静态变量初始化为指定的值,执行静态代码块
    • 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
    • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步
    • 当范围一个Java类的静态域时,只有真正声名这个域的类才会被初始化
    2、初始化的时机

    需要在主动引用时,才会执行初始化

    • new、getstatic、putstatic、invokestatic
    • 对内进行反射调用时
    • 初始化一个类的子类会去加载其父类
    • 启动程序所使用的main方法所在类
    • 当使用jdk1.7动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果

    除了主动引用外,还有以下三种情况被称为被动引用,不会触发初始化

    • 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
    • 定义对象数组和集合,不会触发该类的初始化
    • 类A引用类B的static final常量不会导致类B初始化(注意静态常量必须是字面值常量,否则还是会触发B的初始化)

    三、触发类加载过程的时机

    触发类加载过程的时机主要分为隐式加载显示加载两种情况

    隐式加载

    • 创建类对象
    • 使用类的静态域
    • 创建子类对象
    • 使用子类的静态域
    • 在JVM启动时,BootStrapLoader会加载一些JVM自身运行所需的class
    • 在JVM启动时,ExtClassLoader会加载指定目录下一些特殊的class
    • 在JVM启动时,AppClassLoader会加载classpath路径下的class,以及main函数所在的类的class文件

    显示加载

    • ClassLoader.loadClass(className):只加载和连接、不会进行初始化
    • Class.forName(String name, boolean initialize,ClassLoader loader):使用ClassLoader进行加载和连接,根据参数initialize决定是否初始化。

    四、类加载完成后在内存中储存

    类加载完成后主要包括类信息以及类Class对象,其中类信息保存在方法区中,类Class对象保存在堆区

    类信息主要包含运行时常量池、类型信息、字段信息、方法信息、类加载器的引用、对应class实例的引用等信息。

    类加载器的引用:这个类到类加载器实例的引用

    对应class实例的引用:类加载器在加载类信息放到方法区中后,会创建一个对应的Class 类型的对象实例放到堆(Heap)中, 作为开发人员访问方法区中类定义的入口和切入点

  • 相关阅读:
    数字化赋能半导体行业,B2B撮合系统助力企业缩短交易链路,实现高效供需匹配
    中国预制菜行业发展形势与前景规划建议报告2022-2028年版
    快速认识什么是:Docker
    C语言指针操作(二)通过指针引用数组
    Vue3 + 百度地图实现位置选择,获取地址经纬度
    【TS】函数和函数类型
    SpringBoot : ch06 整合 web (一)
    万字长文详解Java线程池面试题
    CSS 通过伪类 nth-child 和 nth-of-type 实现奇偶选择器的区别
    血压心电的测量小工具,轻松了解身体状况,dido Y1S手环上手
  • 原文地址:https://blog.csdn.net/emgexgb_sef/article/details/126595947