章二 jvm执行引擎
本文是jvm系列第二篇,在 JVM 中,执行引擎(Execution Engine)是将字节码转换为机器码并执行的核心组件。它是 Java 虚拟机实现跨平台性和高性能的关键所在。本篇文章将全面解析 JVM 执行引擎的组成、工作原理以及相关优化机制。
解释器和编译器
- 解释执行:解释执行是指jvm逐条读取并解释执行java字节码指令
- 启动速度快:无需预编译,程序可以迅速启动
- 执行效率低:逐条解释执行带来额外的开销,运行性能不如编译执行
- 适用场景:适合短生命周期和启动时间要求高的应用
- 编译执行:将java字节码预编译成本地机器码后执行
- 执行效率高:机器代码更偏向于计算机底层,执行效率快
- 启动时间慢:预编译过程将带来额外时间消耗
- 适用场景:适合长时间运行,对性能要求高的应用
- jvm工作模式:默认为混合编译方式
- 混合模式:jvm在启动时使用解释器执行字节码,随着程序运行,通过JIT编译器监测并编译热点代码,兼顾启动速度和运行性能
- 纯解释模式:jvm仅使用解释模式执行所有字节码,不进行任何编译,适用在调试、分析jvm行为或资源受限的环境中执行,通过参数 -Xint,强制jvm适用纯解释模式,不启动jit编译器
java -Xint -jar your_application.jar
- 纯编译模式:jvm尝试将所有代码编译成本地机器码,不是用解释器,适用于高性能的应用,但会增加启动时间和内存开销,通过参数 -Xcomp,设置为纯编译模式
java -Xcomp -jar your_application.jar
- JIT技术:JIT编译是jvm中的核心技术,主要用于动态将字节码转换成机器码来提高程序运行效率,相比于解释执行,JIT通过将热点代码直接编译成机器码,可以避免字节码多次解析带来的性能开销
- 工作流程
- 识别热点代码
- 方法调用计数:记录方法被调用的次数
- 循环体计数:记录循环体被执行的次数
- 当计数次数超过设置的阈值,即触发JIT编译
- 参数
- -XX:CompileThreshold:设置触发JIT编译的阈值(默认10000次)
- -XX:PrintCompilation:打印JIT编译的信息
- 识别热点代码
- 编译和优化:JVM中主要有两种JIT编译器
- C1编译器:注重编译速度,进行基础优化
- C2编译器:注重执行效率,进行复杂优化
- JDK11以上,引入Graal JIT,支持更高效的代码优化
- 优化技术
- 方法内联:讲方法调用替换为方法体,减少方法调用的开销,方法体过大时不会内联,避免增加编译时间和代码膨胀
- 逃逸分析:前文描述过
- 循环展开:将循环体中的多次迭代合并为一组,减少循环的次数和边界检查
- 分支预测:在if/switch中,JIT编译器优先优化频率更高的分支
- 常量传播:将编译器可确定的常量直接替换为具体值,减少运行是的计算
int x=10; int y = x*2; //JIT编译直接计算 y=20
- 工作流程
垃圾收集
- GC分类
- Minor Gc:年轻代GC,触发频率高但回收速度快
- Major GC:老年代GC,停顿时间长,触发频率低
- Full GC:全堆GC
- 对象回收
- 引用计数法
- 每个对象维护一个引用计数器,计数为0的对象可回收
- 无法处理循环引用
- 可达性分析(java采用可达性算法,避免循环依赖问题
- 从一组GC Roots对象出发,不可达对象可被回收
- GC Roots
- 栈中引用的对象
- 方法区中静态变量引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象
- 引用计数法
- 垃圾收集算法
- 标记-清除算法
- 过程:
- 标记阶段:标记所有可达对象
- 清除阶段:回收未被标记的对象
- 缺点:清除后产生大量不连续内存碎片,可能导致大对象无法分配
- 过程:
- 复制算法:
- 过程:
- 将对象分为两个区域
- 每次GC将存活的对象从一个区域复制到另一个区域,并清空原区域
- 无碎片化问题,分配速度快
- 缺点:空间利用率低,需要两块内存区域,只有一块区域可用于对象分配
- 应用:年轻代垃圾回收使用该算法
- 过程:
- 标记-整理算法
- 过程
- 标记阶段:标记所有可达对象
- 整理阶段:将存活对象移动到一端,清理剩余空间
- 缺点:对象移动成本高
- 应用:主要用于老年代垃圾回收
- 过程
- 分代收集算法:
- 核心:根据对象的生命周期将堆内存分为年轻代和老年代,不同区域使用不同垃圾算法
- 年轻代:复制算法
- 老年代:标记整理算法
- 核心:根据对象的生命周期将堆内存分为年轻代和老年代,不同区域使用不同垃圾算法
- 标记-清除算法
- GC类型和触发条件
- Minor GG
- 回收区域:年轻代
- 触发条件:eden区满时触发
- 特点:频率高、回收速度快,存活对象较少
- Major GC/Full GC
- 回收区域:Major GC 仅回收老年代,Full GC 回收整个堆
- 触发条件:老年代空间不足、元空间内存不足
- 特点:停顿时间长,触发频率低但对性能影响大
- Minor GG
- GC 停顿
- GC需要暂停所有应用线程以确保内存回收的安全性,这种暂停即GC停顿
- 影响:对用户体验影响较大,特别在实时行要求高的场景(如交易系统),减少GC停顿是垃圾回收优化的核心目标之一
- 优化策略:
- 使用低停顿的GC回收器(G1或ZGC)
- 调整GC参数,降低STW的频率和时长
垃圾回收流程
动态代理的JVM实现差异
JDK动态代理类加载过程
CGLIB的字节码生成
// 使用ASM框架生成字节码示例
public class ProxyGenerator {
public byte[] generateProxyClass() {
ClassWriter cw = new ClassWriter(0);
cw.visit(V1_8, ACC_PUBLIC, "ProxyClass",
null, "java/lang/Object",
new String[]{"TargetInterface"});
// 生成方法
MethodVisitor mv = cw.visitMethod(
ACC_PUBLIC, "targetMethod", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL,
"java/lang/Object", "<init>", "()V");
mv.visitInsn(RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
return cw.toByteArray();
}
}