• JVM-类加载机制


    名词解释
    *.class文件的结构
    查看指令: javap -verbose hello.class
    包含信息:

    结构信息(版本号,大小信息);

    元数据(类,继承,接口,字段声明,方法声明);

    方法信息(语句,表达式,异常,堆栈大小与类型,调试信息)

    符号引用
    符号引用符号引用就是 class 文件中的     
    1. CONSTANT_Class_info 
    2. CONSTANT_Field_info
    3. CONSTANT_Method_info等类型的常量
    符号引用以 一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义的定位到目标即可,使用符号引用时,被引用的目标不一定已经加载到内存中。
    直接引用
    直接引用可以是指向目标的指针,相对偏移量或是一个能间接 定位到目标的句柄
    如果有 了直接引用,那引用的目标必定已经在内存中存在

    参考:

    个人理解,如果使用符号引用,虚拟机其实也不知道具体引用的类的内存地址,那么也就无法真正的调用到该类,所以要把符号引用转为直接引用,这样就能够真正定位到类在内存中的地址,如果符号引用转直接引用失败,就说明类还没有被加载到内存中,就会报错。

    https://blog.csdn.net/weixin_38106322/article/details/109239971

    类加载机制概述

    1、类加载器把编译好的那些“.class”字节码文件给加载到JVM中,供后续代码运行来使用。
    2、JVM基于自己的 字节码执行引擎,来执行加载到内存里的类。
    3、类加载的时机
       一个类被使用到到时候
    eg:比如你的代码中有一个“main()”方法,那么JVM就会从这个“main()”方法开始执行里面的代码。他需要哪个类的时候,就会使用类加载器来加载对应的类,反正对应的类就在“.class”文件中。
    4、通过Java命令执行代码的大体流程如下:
    java  xxx 指令 在Windows下对应就是一个exe文件。dll 相当于java jar包。
    C++启动程序 -》创建java虚拟机 -》java类加载器 -》加载class文件

    类加载机制详解

    类是如何加载到jvm中
    JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。

    加载过程分析

    1. 加载

    通过类全名来获取定义此类的二进制字节流,
    将字节流所代表的 静态存储结构转换为方法区中运行时数据结构
    在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
    扩展:
    • 静态存储结构:数据存储于class文件的结构。
    • 运行时数据结构:数据存储于JVM的数据结构。
    • 二进制流是从.class文件中获取的。这个class文件并不一定只来源于本地,也可以来源于网络等。

    2. 连接

    将java二进制代码合并到java运行状态中的过程。
    • 验证:校验字节码文件的正确性 目的保证class流的格式正确。
    (简单来说,这一步就是根据Java虚拟机规范,来校验你加载进来的“.class”文件中的内容,是否符合指定的规范。)
    • 准备:Java虚拟机为类变量(static修饰的变量)、加载到内存中的类分配内存空间,设置默认初始值。
      •   示例代码:
    public static int v=1; 在准备阶段中,v会被设置为0   在初始化的中才会被设置为1
    对于static final类型,在准备阶段就会被赋上正确的值: public static final  int v=1;
    • 解析::将符号引用替换为直接引用
      • 该阶段会把一些静态方法(符号引用,比如 main()方法)替换为指向数据所存内存的指针或句柄等(直接引用),这是所谓的静态链接过 程(类加载期间完成),
      • 动态链接:是在程序运行期间完成的将符号引用替换为直接引用
    3  初始化 (核心阶段)
    初始化阶段是类加载最后一个阶段,前面的类加载阶段之后,除了在加载阶段可以自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。如: 在初始化阶段,JVM执行类的初始化语句, 为类的静态变量赋予用户给定的初始值,执行静态代码块 
    什么时候会初始化一个类?
    一般来说有以下一些时机:比如“new ReplicaManager()”来实例化类的对象了,此时就会触发类的加载到初始化的全过程,把这个类准备好,然后再实例化一个对象出来;(这里也说明了为什么 构造方法是在 静态代码块之后执行(先准备代码在实例化 ))
    或者是包含“main()”方法的主类,必须是立马初始化的。
    此外,这里还有一个非常重要的规则,就是如果初始化一个类的时候,发现他的父类还没初始化,那么必须先初始化他的父类

    4 使用

    5 卸载

    加载器的类型

    类加载器类型总体来说可以分为以下几种:启动类加载器、其他类加载器。类的加载过程遵循双亲委派模型。
    1、Bootstrap ClassLoader(启动类加载器)
        JVM启动,启动类加载器 加载 Java安装目录下jre 的 “lib”目录中的核心类库
    /opt/apps/jdk1.8.0_251/jre/lib
    --> rt.jar、charsets.jar等> rt.jar、charsets.jar等
    2、Extension ClassLoader(扩展类加载器)
        JVM启动,通过扩展类加载器加载“lib/ext”目录
    [root@iZ2zei4wx78lgr80odfj6eZ ext]# pwd# pwd
    /opt/apps/jdk1.8.0_251/jre/lib/ext
    3、Application ClassLoader(应用程序类加载器)
        加载ClassPath 环境变量所指定的路径中的类
    4、自定义类加载器    
        根据自己需求进行类加载

    类加载器初始化过程

    参见类运行加载全过程图可知其中会创建JVM启动器实例sun.misc.Launcher。 
    sun.misc.Launcher初始化使用了单例模式设计,保证一个JVM虚拟机内只有一个 sun.misc.Launcher实例。 
    在Launcher构造方法内部,其创建了两个类加载器,分别是 sun.misc.Launcher.ExtClassLoader(扩展类加载器)和sun.misc.Launcher.AppClassLoader(应 用类加载器)。 
    JVM默认使用Launcher的getClassLoader()方法返回的类加载器AppClassLoader的实例加载我们 的应用程序。

    双亲委派模型

    概述(向上寻找、向下委托)

    类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是 把这个请求委派给父类加载器完成。
    每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),
    子加载器才会尝试自己去加载,这个过程为称为双亲委派模型。

    AppClassLoader加载类的双亲委派机制源码

    AppClassLoader 的loadClass方法最终会调用其父类ClassLoader的loadClass方法,
    • 1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接 返回。 
    • 2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加 载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加 载。
    • 3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的 findClass方法来完成类加载。 
    • 对应代码位置://ClassLoader的loadClass方法,里面实现了双亲委派机制

    为什么需要双亲委派模型

    • 保证类加载机制的安全性。
      • 防止病毒代码植入,自己写的java.lang.String.class类不会被加载,这样便可以防止核心 API库被随意篡改
    • 避免类的重复加载:
      • 当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,保证被加载类的唯一性
    • 参考 : https://www.cnblogs.com/wxd0108/p/6681618.html

    全盘负责委托机制 

    • “全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类 所依赖及引用的类也由这个ClassLoder载入。

    如何打破双亲委派模式

    • 覆写loadClass方法

    打破双亲委派模式的案例

    • tomcat类加载器
      • Common类加载器,加载Tomcat和Web应用都复用的类。
      • Catalina类加载器,加载Tomcat专有类(这些类在web应用中不可见)
      • Shared类加载器,加载所有Web应用都复用都类。(这些类tomcat中不可见)
      • WebappClassLoader:webapp私有的类加载器,只对当前webapp可见
    • Jsp类加载器,不同的jsp页面有不同的类加载器,方便实现jsp页面的热插拔。
    • 类加载机制原理
      • a. CommonClassLoader能加载的类都可以被Catalina ClassLoader和SharedClassLoader使用,从而实现了公有类库的共用

        b. CatalinaClassLoader和Shared ClassLoader自己能加载的类则与对方相互隔离

        c. WebAppClassLoader可以使用SharedClassLoader加载到的类,但各个WebAppClassLoader实例之间相互隔离,多个WebAppClassLoader是同级关系

        d. 而JasperLoader的加载范围仅仅是这个JSP文件所编译出来的那一个.Class文件,它出现的目的就是为了被丢弃:当Web容器检测到JSP文件被修改时,会替换掉目前的JasperLoader的实例,并通过再建立一个新的Jsp类加载器来实现JSP文件的HotSwap功能

    • 为什么打破双亲委派模式?
      • 隔离性
        • 一个web应用可以部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本。
          • 因此需要保证每个应用程序的类库是独立的、相互隔离的。
        • web容器也有自己的类库,不能和应用程序的类库混淆,需要相互隔离
      • 共享性
        • 部署在同一个web容器中的相同类库的相同版本可以共享,否则,会有重复的类库被加载进JVM
      • 独立性
        • web容器支持jsp文件修改后不用重启,jsp文件也是要编译成.class文件的,支持HotSwap功能
    • tomcat如何打破双亲委派模式的?
      • 参考tomcat类加载机制 
  • 相关阅读:
    I/O控制方式(程序直接控制方式,中断驱动方式,DMA方式,通道控制方式)
    Jenkins UI 自动化持续化集成测试
    A-Level经济例题解析及练习Computing MPL and VMPL
    Vue3 环境变量
    Java注解@Transa1ctional失效特殊情况
    18-1、k8s 对外服务之ingress
    LeetCode 面试题 10.01. 合并排序的数组
    vue -权限管理-指令-v-permission
    Asp-Net-Core开发笔记:快速在已有项目中引入EFCore
    Maven下载及相关配置附IDEA更换本地Maven
  • 原文地址:https://blog.csdn.net/Willard1314/article/details/136497553