• Java虚拟机二三事:虚拟机类加载机制


    概述

    虚拟机把Class文件加载到内存,经过校验、解析和初始化,最终转换成虚拟机可以使用的Java类型,这就是Java的虚拟机类加载机制。
    总体流程如图
    在这里插入图片描述

    一、加载

    触发加载的时机

    如果类没有初始化,那么以下几种方式会触发类的加载

    • 当调用以下字节码指令
      • 使用new初始化对象
      • 访问类的static变量、static方法
    • 反射访问类
    • 触发了子类的初始化,先从父类开始初始化
    • 虚拟机启动时需要执行的主类,也就是包含main函数的类
    • 使用MethodHandler

    这里有两个注意的点

    1. 通过数组定义来使用类,不会触发类的加载
    2. 尽量把固定的int值定位staic final常量,也不会触发类的加载(这是因为在编译阶段,对static final常量进行了优化,都放在常量池中,所以实际上触发的是对常量池的调用)
    SubClass[] classArray = new SubClass[10];
    
    public static final String value = "SubClass value";
    
    • 1
    • 2
    • 3

    还有一点,子接口的初始化不会影响父接口,只有用到父接口,比如父接口中定义的常量,才会触发父接口的初始化

    加载

    通过类的全限定名获取这个类的二进制字节流,在内存中生成代表这个类的class对象

    • 加载阶段可以用系统的类加载器,也可以用自定义的类加载器
    • 数组不通过类加载创建,而是虚拟机直接创建,但是数组的元素类型最终还是要通过类加载器去加载

    二、验证

    验证class字节流是否合法。
    因为class字节流不一定是java源码编译而来,所以虚拟机需要保证class合法的,不会对载入了错误的字节流导致崩溃

    • 文件格式验证:文件头是否合法
    • 元数据验证:是否有父类,是否父类是不允许被继承的
    • 字节码验证:语义合法
    • 符号引用验证:当要把符号引用转换为直接饮用的时候,通过类中的权限定名是否能找到对应的类

    三、准备

    为类变量准备内存。
    设置static变量的初始值,但跟真正的初始化有区别,此处只是赋一个默认值

    public static int value = 123;
    
    • 1

    比如这种,会赋0。但是如果是这种,就会直接赋值

    public static final int value = 123;
    
    • 1

    四、解析

    将符号引用转换为直接引用。
    符号引用:一组用来描述目标的符号,只要使用时能定位到目标即可
    直接引用:直接指向目标的指针或句柄
    解析内容

    • 类或接口的解析
    • 类或接口的字段、方法解析

    五、初始化

    调用cinit方法,进行变量的初始化。
    cinit方法是编译器收集的所有变量赋值操作和static代码块合并而成,顺序是按源文件中的顺序决定的

    • 先执行父类的cinit,然后在执行子类的
    • 这就意味着父类是static代码块优于子类执行

    虚拟机会保证多线程环境下的cinit方法的执行,多个线程访问,只有一个线程可以调用cinit方法,这就是内部类实现单例的原理

    类加载器

    使用与卸载就不多说了,当虚拟机发现没有对该类型的引用的时候,会触发类的卸载
    什么是类加载器?通过一个类的全限定名来获取这个类的二进制流,放到java虚拟机外部去实现,以便让应用程序自己决定如何去加载这个类

    类与类加载器

    比较两个类是否相等,只有判断两个类,只有判断在同一个类加载器下才有意义,即使是同一个class对象,用不同的类加载器加载,那么这个类 必不相等

    双亲委派模型

    在这里插入图片描述
    启动类加载器:JAVA_HOME\lib类库加载到虚拟机内存中
    扩展类加载器:JAVA_HOME\lib\ext 目录中
    应用程序类加载器:系统类加载器,加载用户类路径上所制定的类库。默认类加载器

    1. 如果一个类加载器收到了加载类的请求,都是先交给父类加载,每一层都是如此
    2. 只有父加载器反馈无法加载,子类加载器才会尝试加载
      这样的好处是,有优先级的层次关系,比如object,哪个类加载器都要加载,最终委派给顶层

    破坏双亲委派模型

    重写loadclass方法,特殊的类自己处理,其他的调用super方法

    参考

    《深入理解JAVA虚拟机》

  • 相关阅读:
    python加入环境变量
    leetcode 1342.将数字变成0的操作次数
    javascript中的数组设计方法
    生活中的视音频技术
    【ACWing】2568. 树链剖分
    二十六、【颜色调整】
    Java日期处理
    SQLite 数据库安装及使用(Linux)
    logback 日志,java-jar 启动报错
    10.19作业
  • 原文地址:https://blog.csdn.net/one1go/article/details/125418956