💠

💠 2024-06-13 11:01:29


字节码以及类加载

个人相关代码


书籍

  • 深入理解Java虚拟机 周志明
  • Java 虚拟机字节码 从入门到实战 吴就业

编译优化

由源文件 *.java 编译成 *.class 文件这个过程中做的调优

类中定义的常量 如果是字面值, 其他引用这个常量的地方编译后会被替换成字面值, 该常量属性的 get 方法也是直接返回字面值
字面值就是无需运算的值, 而不是表达式 例如 final int num = 2; 反例 final int num = 3>2?1:2;

JIT 即时编译

目前有三种:C1 C2 Graal

类型 JVM参数 特点
C1 -client 编译耗时短
C2 -server 编译耗时长执行效率好需要收集运行期profile信息来辅助编译也就是PGO
Graal

注意 自JDK8起默认开启分层编译C1 C2混用 -client -server参数无效

Graal Compiler
Deep Dive Into the New Java JIT Compiler – Graal


字节码

参考: 学会阅读Java字节码 参考: 字节码增强技术探索

字节码是程序的中间表达形式,源码和机器码之间的产物 字节码是由源文件执行javac产生的

某些高级语言特性(语法糖)在字节码中会进行简化,例如循环结构,会转换成为分支指令

  • 每个操作都由一个字节表示,因此叫做字节码

  • 字节码是一种抽象表示方法 字节码进一步编译得到机器码

  • javap -c -p class文件 反编译字节码文件,-p 能看到私有属性

    • 输出所有的属性以及类的定义信息
    • 静态块
    • 构造方法
    • 方法信息
    • 静态属性信息
    • 静态方法信息

字节码相关框架

Apache bcel

asm
javassist


常量池

常量池是为类文件中的其他常量元素提供快捷访问方式的区域。对于JVM来说常量池相当于符号表 参考博客

  • javap -v class文件 输出很多额外信息,# 开头的就是常量池信息 图

  • 理解常量池 以及使用场景


类加载机制

  • 类的生命周期
    • 加载 Loading -> 验证 Verification -> 准备 Preparation -> 解析 Resolution -> 初始化 Initialization -> 使用 Using -> 卸载 Unloading
    • 验证、 准备、 解析,统称为 链接 Linking

类加载器

参考: 一文带你深扒ClassLoader内核,揭开它的神秘面纱! 深入源码,举例清晰

双亲委派模型(parents delegation model) 实现代码:java.lang.ClassLoader#loadClass(java.lang.String, boolean) 其工作原理是,如果一个类加载器收到了类加载请求(只讨论首次加载,已经加载过的会走缓存), 它并不会自己先去加载,而是委托给父类的加载器去执行
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载

  • Java平台经典类加载器层级:
    1. BootStrap ClassLoader 根(引导)加载器:通常在VM启动后不久就实例化,最顶层的加载类,主要加载 核心类库 并且不做验证工作
      • 加载目录 %JRE_HOME%\lib 下的rt.jar、resources.jar、charsets.jar 和 class 文件
    2. Extendsion ClassLoader 扩展类加载器:加载安装时自带的标准扩展,一般包括安全性扩展
      • 加载目录 %JRE_HOME%\lib\ext 下的 jar 包和 class 文件。
    3. Application ClassLoader 应用或系统类加载器:应用最广泛的类加载器,负责加载应用类(当前应用的 classpath 的所有类)
    4. 自定义ClassLoader 自定义类载器

注意:

  1. 例如在读取类路径下文件时,可以通过 classA.getClassLoader().getResourceAsStream("app.properties") 但是如果类classA对象是由 BootStrap 类加载器加载的, getClassLoader() 将返回 null
  2. 当出现jar包多版本时,先加载了其中一个版本就不会加载另一个版本,而这个加载顺序往往是由操作系统的文件排序决定的 相关案例

特殊场景

Tomcat

参考: 图解Tomcat类加载机制

  • CommonClassLoader Tomcat最基本的类加载器,加载路径/common/*中的class可以被Tomcat容器本身以及各个Webapp访问;
  • CatalinaClassLoader Tomcat容器私有的类加载器,加载路径/server/*中的class对于Webapp不可见;
  • SharedClassLoader 各个Webapp共享的类加载器,加载路径/shared/*中的class对于所有Webapp可见,但是对于Tomcat容器不可见;
  • WebappClassLoader 各个Webapp私有的类加载器,加载路径/WebApp/WEB-INF/*中的class只对当前Webapp可见;

其中WebApp类加载器和Jsp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器,每一个JSP文件对应一个Jsp类加载器。

WebApp类加载器就为了类隔离而违背了双亲委派模型,仅自身负责加载类,不向上传递


加载和连接

参考: 第七章.虚拟机类加载机制

加载

  • 这个过程就是读取字节码文件,创建一个字节数组装在这些内容,加载结束后这个对象还不能直接调用

连接

  • 加载完成后,类必须连接起来,分为三步:验证,准备,解析。
    • 验证:
      • 验证文件的合理性,完整性检查,检查常量池,方法的字节码检查。主要的:
      • 是否所有方法都遵守访问控制关键字的限定
      • 方法调用的参数个数和静态类型是否正确
      • 确保字节码不会试图滥用堆栈
      • 确保变量使用之前被正确初始化了
      • 检查变量是否仅被赋予恰当类型的值
    • 准备:
      • 分配内存,准备初始化类中的静态变量,但不会现在就初始化,也不会执行任何VM字节码
    • 解析:
      • 促使VM检查类文件中所引用的类型是不是都是已知的类型。如果有运行时有未知的类型,那又要引发一次类加载过程
      • 当需要加载的类全部加载解析完毕后,VM就可以初始化最初那个加载的类了。
      • 这时所有的静态变量都可以进行初始化,所有静态代码块都会运行,这一步完成后,类就能使用了

方法句柄

主要用于反射 用到再学

图


Agent

JDK: Interface Instrumentation

Guide to Java Instrumentation
Java Agent 探针技术

Java Agent 主要有以下功能

  • Java Agent 在加载 Java 字节码之前拦截并对字节码进行修改;
  • Java Agent 在 Jvm 运行期间修改已经加载的字节码;

Java Agent 的应用场景

能力 案例
IDE的调试功能 Eclipse、IntelliJ IDEA ;
热部署功能 JRebel、XRebel、spring-loaded;
各种线上诊断工具 Btrace、Greys, Arthas;
各种性能分析工具 Visual VM、JConsole 等;
全链路性能检测工具 Skywalking、Pinpoint等;

agent线程池监控


反编译

JD
https://varaneckas.com/jad/
Java-Class-Viewer
classpy


热部署

通过替换 class 实现不停机热更新

Spring hot swapping

  1. Instrumentation
  2. 自定义类加载器
  3. OSGI 热插拔接口

Instrumentation 新功能 基于Java Instrument的Agent实现 Java 5 特性 Instrumentation 实践 java组件中的热插拔(osgi) agentmain 方式

相关项目:

game-hot-update | 游戏服务器之Java热更新 groovy hotswap demo

Java系列 | 远程热部署在美团的落地实践未开源