提升Java性能的JIT

本文详细阐述了JVM如何通过JIT编译器(如C1和C2)提升Java程序性能,介绍了分层编译策略、热点代码的处理、方法内联优化、锁消除和标量替换等编译优化技术,以及逃逸分析在JIT中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JIT分析

跨语言(语言无关性):JVM只识别字节码,所以JVM其实跟语言是解耦的,也就是没有直接关联,JVM运行不是翻译Java文件,而是识别class文件,这个一般称之为字节码.
在这里插入图片描述
解释执行与JIT
在这里插入图片描述
Java程序在运行的时候,主要就是执行字节码指令,一般这些指令会按照顺序解释执行,这种就是解释执行。
将代码编译成字节码指令,再翻译成机器码,最后计算机根据机器码执行程序。
频繁被调用的代码,比如调用次数很高或者在 for 循环里的那些代码,如果按照解释执行,效率是非常低的.(这个就是Java以前被C、C++开发者吐槽慢的原因)。
以上的这些代码称为热点代码。所以,为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化。
完成这个任务的编译器,就称为即时编译器(Just In Time Compiler),简称 JIT 编译器。

Graal编译器和C1,C2

在这里插入图片描述在jdk1.8 的hotspot虚拟机中,内置了两个JIT,分别为 C1编译器 和C2编译器。

C1编译器

C1 编译器是一个简单快速的编译器,主要的关注点在于局部性的优化,适用于执行时间较短或对启动性能有要求的程序,例如,GUI 应用对界面启动速度就有一定要求,C1也被称为 Client Compiler。
C1编译器几乎不会对代码进行优化

C2编辑器

C2 编译器是为长期运行的服务器端应用程序做性能调优的编译器,适用于执行时间较长或对峰值性能有要求的程序。根据各自的适配性,这种即时编译也被称为Server Compiler。
但是C2代码已超级复杂,无人能维护!所以才会开发Java编写的Graal编译器取代C2(JDK10开始)。

分层编译

在 Java7之前,需要根据程序的特性来选择对应的 JIT,虚拟机默认采用解释器和其中一个编译器配合工作。
Java7及以后引入了分层编译,这种方式综合了 C1 的启动性能优势和 C2 的峰值性能优势,当然我们也可以通过参数强制指定虚拟机的即时编译模式。

Java8 中,默认开启分层编译

java -version 可以直接查看当前系统使用的编译模式(默认分层编译)
在这里插入图片描述

使用 -Xint参数强制虚拟机运行与只有解释器的编译模式
java -Xint -version
在这里插入图片描述
使用 -Xcomp强制虚拟机运行于只有JIT的编译模式下
在这里插入图片描述

JVM的执行状态

第 0 层:程序解释执行,默认开启性能监控功能(Profiling),如果不开启,可触发第二层编译;
第 1 层:可称为 C1 编译,将字节码编译为本地代码,进行简单、可靠的优化,不开启 Profiling;
第 2 层:也称为 C1 编译,开启Profiling,仅执行带方法调用次数和循环回边执行次数 profiling 的 C1 编译;
第 3 层:也称为 C1 编译,执行所有带 Profiling 的 C1 编译;
第 4 层:可称为 C2 编译,也是将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。

热点代码:指频繁被调用的代码,如for循环中的代码。
热点代码再次编译后的机器码会被缓存起来,以备下次使用,但对于那些执行次数很少的代码来说,这种编译动作就纯属浪费。
JVM提供了一个参数“-XX:ReservedCodeCacheSize”,用来限制 CodeCache 的大小。也就是说,JIT 编译后的代码都会放在 CodeCache 里。
如果这个空间不足,JIT 就无法继续编译,编译执行会变成解释执行,性能会降低一个数量级。同时,JIT 编译器会一直尝试去优化代码,从而造成了 CPU 占用上升。
通过 java -XX:+PrintFlagsFinal –version查询:
在这里插入图片描述
热点探测: 在 HotSpot 虚拟机中的热点探测是 JIT 优化的条件,热点探测是基于计数器的热点探测,采用这种方法的虚拟机会为每个方法建立计数器统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法”
虚拟机为每个方法准备了两类计数器方法调用计数器(Invocation Counter)和回边计数器(Back Edge Counter)。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发 JIT 编译。
方法调用计数器
用于统计方法被调用的次数,方法调用计数器的默认阈值在客户端模式下是 1500 次,在服务端模式下是 10000 次(我们用的都是服务端,java –version查询),可通过 -XX: CompileThreshold 来设定,通过 java -XX:+PrintFlagsFinal –version 查询。
在这里插入图片描述
回边计数器
用于统计一个方法中循环体代码执行的次数,在字节码中遇到控制流向后跳转的指令称为“回边”(Back Edge),该值用于计算是否触发 C1 编译的阈值,在不开启分层编译的情况下,在服务端模式下是10700。
回边计数器阈值 =方法调用计数器阈值(CompileThreshold)×(OSR比率(OnStackReplacePercentage)-解释器监控比率(InterpreterProfilePercentage)/100。通过java -XX:+PrintFlagsFinal –version查询相关参数,代入公式进行计算。

编译优化技术

JIT 编译运用了一些经典的编译优化技术来实现代码的优化,即通过一些例行检查优化,可以智能地编译出运行时的最优性能代码。
1.方法内联
方法内联的优化行为就是把目标方法的代码复制到发起调用的方法之中,避免发生真实的方法调用。

 	private int add1(int a, int b,int c,int d) {
        return add2(a, b) + add2(c, d);
    }
    private int add2(int c, int d) {
        return c + d;
    }

    //方法 add1 最终会被优化成:
    private int add(int a, int b, int c, int d) {
        return a + b + c + d;
    }

JVM 会自动识别热点方法,并对它们使用方法内联进行优化。
我们可以通过 -XX:CompileThreshold 来设置热点方法的阈值。
注意: 热点方法不一定会被 JVM 做内联优化,如果这个方法体太大了,JVM 将不执行内联操作。
而方法体的大小阈值,我们也可以通过参数设置来优化。
经常执行的方法,默认情况下,方法体大小小于 325 字节的都会进行内联。
在启动方法时设置参数:-XX:FreqInlineSize=N
在这里插入图片描述
是经常执行的方法,默认情况下,方法大小小于 35 字节才会进行内联,可以通过 -XX:MaxInlineSize=N 来重置大小值。
在这里插入图片描述
热点方法的优化可以有效提高系统性能,可以通过以下几种方式来提高方法内联:

1.通过设置 JVM 参数来减小热点阈值或增加方法体阈值,以便更多的方法可以进行内联,
  但这种方法意味着需要占用更多地内存;
2.在编程中,避免在一个方法中写大量代码,习惯使用小方法体;
3.尽量使用 final、private、static 关键字修饰方法,编码方法因为继承,会需要额外的类型检查。

锁消除
当方法或者类被 synchronize修饰,且方法或者对象只能被当前线程访问,不能被其他线程访问时,JIT编译会对这个对象的方法锁进行锁消除。
标量替换
通过逃逸分析证明一个对象不会被外部方法访问。当这个对象能被拆分是,程序将对象进行拆分,直接创建成员变量来替换这个对象,将这些成员变量分配到 栈或者寄存器中。这样对象就不需要分配内存空间了。
逃逸分析开闭参数:

-XX:+DoEscapeAnalysis开启逃逸分析(jdk1.8默认开启)
-XX:-DoEscapeAnalysis 关闭逃逸分析

标量替换开闭参数:

-XX:+EliminateAllocations开启标量替换(jdk1.8默认开启)
-XX:-EliminateAllocations 关闭标量替换

逃逸分析原理: 分析对象动态作用域,当一个对象在方法中定义后,它可能被外部方法所引用。
比如:调用参数传递到其他方法中,这种称之为方法逃逸。甚至还有可能被外部线程访问到,例如:赋值给其他线程中访问的变量,这个称之为线程逃逸。
从不逃逸到方法逃逸到线程逃逸,称之为对象由低到高的不同逃逸程度。
如果确定一个对象不会逃逸出线程之外,那么让对象在栈上分配内存可以提高JVM的效率。
当然逃逸分析技术属于JIT的优化技术,所以必须要符合热点代码,JIT才会优化,另外对象如果要分配到栈上,需要将对象拆分,这种编译优化就叫做标量替换技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值