JVM—>类加载篇

循循善诱
• 阅读 1665

why

为什么要进行类加载?

  • 编译后的Class文件并不能直接被JVM使用
  • Class文件是对类描述的一段二进制字节流
  • JVM是一个进程,只能对内存中的数据进行操作

要将Class文件加载到JVM中,然后根据描述在不同的内存空间给它分配内存

类加载步骤

  • 加载
  • 连接

    • 验证
    • 准备
    • 解析
  • 初始化
  • 使用
  • 卸载

一、加载

作用
将二进制字节流存储在方法区中,然后在堆内存中实例化一个Class类对象,这个对象作为访问方法区中的类型数据的外部接口

特性

  • 这是可控性最强的阶段,这个阶段可以自定义很多自己的东西

    • 安全:加载加密Class文件,避免程序逻辑曝光
    • 动态代理技术:编写反射接口,在运行时计算生成对象
    • 从其他文件生成:由JSP文件生成对应的Class文件
  • Class文件不一定是存在磁盘,只是指一段二进制字节流
  • RPC框架的原理是将Class文件传过去,在另外一端进行加载并使用
  • 同一个Class文件经过不同的类加载器加载,得到的对象是不同的
  • 加载阶段和连接阶段是交叉进行的

类加载条件

  • 通过new实例化对象
  • 反射调用
  • 实例化子类的时候,会先实例化父类
  • JVM启动的时候会先加载main函数所在的主类

证明间接引用不会触发类加载

添加虚拟机启动参数打印加载阶段的信息

-XX:+TraceClassLoading

1. 通过子类引用继承的静态成员变量,不会触发子类初始化

public class NotInitialization{
    public static void main(String []args){
        System.out.print(SubClass.value);
    }
}

class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }
    public final static int value = 123;
}

class SubClass extends SuperClass {
    static {
        System.out.println("SubClass init!");
    }
}

结果打印:
SuperClass init!

2. 通过定义数组,并不会初始化对象

public class NotInitialization{
    public static void main(String []args){
        SuperClass []superClasses = new SuperClass[10];
    }
}

结果:没有显示init

3. 应用类的静态常量,不会触发该类的加载

public class NotInitialization {
    public static void main(String[] args) {
        System.out.print(SuperClass.value);
    }
}

class SuperClass {
    static {
        System.out.println("SuperClass init!");
    }
    public final  static    int value = 123;
}

结果:没有显示init
原因:编译阶段会做常量传播优化,将vale的值存在NotInitialization类对应的常量池中,执行main方法相当于仅调用自身常量池的引用,且不持有SuperClass的引用

二、连接

1. 验证

确保加载进来的Class文件字节流符合规范,不会危害JVM安全
可通过 -Xverify:none 参数来关闭大部分类验证,缩短短类加载时间

特性
与加载阶段交叉运行,最耗费时间的

① 文件格式校验

  • 保证字节流符合Class文件规范,并且可以被当前JVM处理
  • 只有文件格式校验通过,这段字节流才允许存储在方法区中
  • 后面三个验证阶段都是直接验证的方法区,不会再操作字节流

② 元数据验证
对字节码的描述进行语义校验

  • 是否有父类
  • 是否继承了final类
  • 如果这个类不是抽象类,是否实现了父类/接口要求实现的方法
  • 重载/复写是否符合规范,如覆盖父类的final字段、参数列表相同但返回类型不同

③ 字节码验证
通过数据流和控制流分析确保语义合法且符合逻辑,确保其不会危害虚拟机

  • 确保所有跳转指令不会跳转到方法体意外的字节码指令
  • 确保方法体中类型转化是有效的,避免把对象赋值给跟它不相干的数据类型

特性
① 最耗费时间
② 无法确保字节码验证后的代码没问题,程序无法准确判断
③ 在javac编译器里加了StackMapTable优化字节码验证时间

④ 符号引用验证
保证解析行为能够正常执行

特性
① 检查是否能根据符号引用中的全限定名找到对应的类
② 符号引用中的类、字段、方法的是否可被当前类访问(权限规则)

2. 准备

给静态变量分配内存并进行初始化

特性
① java7以及以前是在方法区中分配,之后是配置在java堆中
② 静态常量的话会在这里进行初始化并且赋值,静态变量的话仅初始化(赋0值)

3. 解析(重点)

将常量池中的符号引用转为直接引用

  • 符号引用:java类在编译时并不知道引用对象的内存地址,就用符号表示

    • 即便是引用自身的成员变量也是符号引用
    • 引用的目标不一定是已经加载到内存的内容
  • 直接引用:直接指向目标的指针

    • 引用的目标一定在虚拟机中存在
    • 性能比符号引用快

三. 初始化

真正执行类中编写的java代码

特性
① 初始化前由类加载器主导,从初始化开始由程序主导
② 初始化阶段就是执行类构造器的构成

双亲委派模型

每个类加载器在收到类加载请求时,会优先委派给父类加载器,只有当父加载器在自己的搜索范围没有找到所需的类,子加载器才会尝试自己去加载

作用
确保基础类是相同的类加载器加载,保证java程序的稳定运行
避免不同的类加载器去加载常用的类如Object,会导致应用程序混乱

三层类加载器

  • 启动类加载器
    负责加载lib下jvm能识别的jar
  • 扩展类加载器
    负责加载libext目录中的jar包,一般存放通用的jar
  • 应用程序类加载器
    负责加载用户类路径所有的类库

特性

  • java一直保持三层类加载器,双亲委派的类加载架构
  • 类加载器由两种组成
    ① 启动类加载器:Bootstrap 有C++实现,是虚拟机的一部分和其他类加载器
    ② 其他类加载器由Java语言实现,并且都继承java.lang.ClassLoader组成

破坏双亲委派模型

为什么要破坏
弄懂OSGi的实现

应用场景

  • OSGi热部署
  • 代码热替换

问题

1. 类在什么情况下会卸载?

答:同时满足这三个条件 ① 这个类的所有实例都被回收 ② 加载该类的ClassLoader已被回收 ③ 该类对应的java.lang.Class对象没有被任何地方引用,无法通过反射创建对象

类卸载就是在方法区中清空该类的信息,java8及之后永生带消除,里面存放的出数据也就是类的信息被移动到java堆

2. 连接的阶段的各个细节到底都做了些什么?

答:验证、准备、解析

3. 什么是双亲委托机制?好处?什么场景下需要避开这个机制?

答:因为不同加载器加载同一个类出来的对象是不相等的,假如用多个加载器加载Object,会导致代码比较混乱。所以需要确保基础用的类都是相同的类加载器加载。双亲委派模型就是指每个类加载器在收到加载类的请求时,会优先委托父加载器加载,除非父加载器无法加载,才会自己尝试加载。热部署的情况下需要避开

4. 怎么证明类就是按照你说的那样加载?如何证明?

答:具体加载的步骤无法看到,只能通过加-XX:+TraceClassLoading查看是否被加载

小结

  1. 熟悉类加载机制可以引导开发者更规范的编写代码
  2. 熟悉类加载机制可以根据业务编写更灵活的代码(OSGi)
  3. 熟悉类加载机制可以快速定位问题
点赞
收藏
评论区
推荐文章
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
2021年度最全面JVM虚拟机,类加载过程与类加载器
前言类装载器子系统是JVM中非常重要的部分,是学习JVM绕不开的一关。一般来说,Java类的虚拟机使用Java方式如下:Java源程序(.java文件)在经过Java编译器编译之后就被转换成Java字节代码(.class文件)。类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表
2021年度最全面JVM虚拟机,类加载过程与类加载器
前言类装载器子系统是JVM中非常重要的部分,是学习JVM绕不开的一关。一般来说,Java类的虚拟机使用Java方式如下:Java源程序(.java文件)在经过Java编译器编译之后就被转换成Java字节代码(.class文件)。类加载器负责读取Java字节代码,并转换成java.lang.Class类的一个实例。每个这样的实例用来表
Wesley13 Wesley13
4年前
Java高级篇——深入浅出Java类加载机制
类加载器简单讲,类加载器ClassLoader的功能就是负责将class文件加载到jvm内存。类加载器分类从虚拟机层面讲分为两大类型的类加载器,一是BootstrapClassloader即启动类加载器(C实现),它是虚拟机的一部分,二是其他类型类加载器(JAVA实现),在虚拟机外部,并全部继
Stella981 Stella981
4年前
ClassLoader解惑
一、什么是Classloader    一个Java程序要想运行起来,首先需要经过编译生成.class文件,然后创建一个运行环境(jvm)来加载字节码文件到内存运行,而.class文件是怎样被加载中jvm中的就是JavaClassloader所做的事情。    那么.class文件什么时候会被类加载器加载到j
Stella981 Stella981
4年前
JVM运行时内存理论实践结合
原创JudyGril!(https://oscimg.oschina.net/oscnet/upc7cf37f1c2cc811f20fd1fb67534573ca70.png)JVM内存区域程序运行会将编译好的.class文件(静态),想要运行必须装载到JVM内存中,通过ClassLoad加载到JVM内存区域,将.clas
Stella981 Stella981
4年前
Jvm类的加载机制
1.概述虚拟机加载Class文件(二进制字节流)到内存,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型,这一系列过程就是类的加载机制。2.类的加载时机类从被虚拟机加载到内存开始,直到卸载出内存为止,整个生命周期包括:加载——验证——准备——解析——初始化——使用——卸载这7个阶段。其中验
Stella981 Stella981
4年前
JVM系列【6】GC与调优1
JVM系列笔记目录虚拟机的基础概念class文件结构class文件加载过程jvm内存模型JVM常用指令GC与调优GC基础知识什么是垃圾​没有任何引用指向的一个对象或多个对象(循环引用)!file(https:
Stella981 Stella981
4年前
JVM系列【6】GC与调优6
JVM系列笔记目录虚拟机的基础概念class文件结构class文件加载过程jvm内存模型JVM常用指令GC与调优GC常用参数\XmnXmsXmxXss年轻代最小堆最大堆栈空间\XX:UseTLAB使用
Stella981 Stella981
4年前
JVM概述和类装载器
1.JVM是运行在操作系统之上的,与硬件没有半毛钱关系。2.我就不用说jvm是什么意思,接下来我来说我对jvm的理解3.双亲委派机制4.ClassLoader负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,至于它是否可以运行,则由ExecutionEngine决定!
Stella981 Stella981
4年前
EKT Java企业级关键技术强化 Enterprise Edition
EKTenterprisekeytechlology企业关键技术本章目标:1.理解Class类2.理解JAVA类加载体系结构3.理解类的加载过程Class对象由JVM自动产生,每当一个类被加载时,JVM就自动为其生成一个Class对象,通过Class对象可以获得类的相关信息。将类信息读到内存中过程,称为类加载