JVM对Java代码做了哪些优化
常见的可以分为运行时优化即时编译器优化JIT
运行时优化,就是内存的分配机制,TLAB等 还有些方便解释器工作的机制,诸如模板解释器 内联缓存
JIT的优化,则是将热点的代码以方法为单位转换为机器码,直接运行在底层硬件上,采用了多种优化,诸如方法内联,逃逸分析等,还有基于程序运行profile(运行时的配置信息) 的投机性优化
比如,有一条指令,如果获取的结果一直,那么可能就编译成一个类,并且可能直接返回,如果出现了其他的类,那么就抛弃这段编译后的机器码,切换回解释执行
我们需要理解Java代码编译 执行的过程,对基本流程有个理解
对JIT的知识尽可能的落地
首先来看Java代码的整个生命周期
在Java中,交由JVM负责从字节码到机器码的转换
编译器,就是javac等编译器或者相关API将源码转换为字节码的过程
javac生成字节码也会对Java程序进行优化,对于jdk9,字节码的拼接被替换为了StringConcatFactory的调用,为JVM进行字符串的拼接优化进行了统一的入口
然后就是JVM运行时候的优化
整体来说,JVM根据统计信息,动态的决定什么方法解释执行,什么方法编译执行,对于编译过得代码,在不同的阶段不是热点的话,JVM也会将这种代码从Code Cache中移除,避免过多占用大小
对于JIT的即时编译器,承担了很多,其需要将整个项目中热点方法进行筛选,然后进行编译成为本地代码,或者是对循环代码,进行一些优化,常见是栈上替换技术,直接将部分固定的变量在初始化的时候分配在栈上
对于其编译,则是看做两个计数器实现,方法计数器和回边计数器提供给了JVM统计数据,定位到热点的代码
对于这些优化的探测,可以有
打印编译时发生的细节
-XX” +PrintCompilation
输出更多编译的细节
-XX:UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:LogFile=<your_file_path>
生成一个XML形式的文件
对Code Cache,可以通过JMC JConsole等工具去进行监控使用
那么作为开发者,我们有什么调优的手段呢?
1.调整热点编译的门阀值
比如,Server模式默认编译时10000次,client是1500次,我们可以通过下面的参数设置调优可能
-XX: CompileThreshold=N
那么,既然是热点,不是早晚会达到门限吗?但是往往JVM会周期性的对数值进行衰减操作,导致计数器增长不是无限的,当然我们可以选择关闭计数器衰减
-XX: -UseCounterDecay
调整Code Cache的大小
毕竟,JIT编译后的代码放在了Code Cache中,如果Code Cache太小,可能导致只有部分的代码被JIT编译,一个潜在的调优点就是调整其大小限制
-XX: ReservedCodeCacheSize=<SIZE>
亦可以设置其初始大小
-XX: InitalCodeCacjeSize=<SIZE>
在不同的情况下,选择不同的编译器模式 也是一种调试的手段
比如,选择client模式就只有一个编译的线程,使用Server模式则有两个线程
不过常见的分层编译模式,则会根据CPU内核数进行计算C1和C2的数值,当然也可以指定编译线程数
-XX:CICompilerCount=N
当然,可以选择关闭分层编译,直接使用Server的编译模式,可以加大一些吞吐量
当然,我们可以去查看安全点的触发时机没查看是否有什么可以调优的方式
查看的方式可以利用如下的选项000000000000000000000000000
-XX:+PrintSafepointStatistics ‑XX:+PrintGCApplicationStoppedTime
但是在JDK9之后,PrintGCApplicationStoppedTime已经被移除了,需要使用 -Xlog:safepoint来查看
在JIT的时候,逆优化需要插入安全点
锁优化阶段,如果撤销偏斜锁会触发安全点,所以偏斜锁往往被建议关闭
-XX:-UseBiasedLocking