qidao123.com技术社区-IT企服评测·应用市场
标题:
Java核心技能口试精讲 第35讲 | JVM优化Java代码时都做了什么?
[打印本页]
作者:
张国伟
时间:
2025-4-28 15:00
标题:
Java核心技能口试精讲 第35讲 | JVM优化Java代码时都做了什么?
第35讲 | JVM优化Java代码时都做了什么?
我在专栏上一讲介绍了微基准测试和相关的注意事项,其核心就是避免JVM运行中对Java代码的优化导致失真。以是,体系地理解Java代码运行过程,有利于在实践中举行更进一步的调优。
今天我要问你的问题是,JVM优化Java代码时都做了什么?
与以往我来给出典型答复的方式不同,今天我邀请了隔壁专栏 《深入拆解Java虚拟机》 的作者,同样是来自Oracle的郑雨迪博士,让他以JVM专家的身份去思考并答复这个问题。
来自JVM专栏作者郑雨迪博士的答复
JVM在对代码执行的优化可分为运行时(runtime)优化和即时编译器(JIT)优化。运行时优化重要是表明执行和动态编译通用的一些机制,比如说锁机制(如偏斜锁)、内存分配机制(如TLAB)等。除此之外,另有一些专门用于优化表明执行效率的,比如说模版表明器、内联缓存(inline cache,用于优化虚方法调用的动态绑定)。
JVM的即时编译器优化是指将热门代码以方法为单位转换成呆板码,直接运行在底层硬件之上。它采用了多种优化方式,包括静态编译器可以利用的如方法内联、逃逸分析,也包括基于程序运行profile的投机性优化(speculative/optimistic optimization)。这个怎么理解呢?比如我有一条instanceof指令,在编译之前的执行过程中,测试对象的类不停是同一个,那么即时编译器可以假设编译之后的执行过程中还会是这一个类,并且根据这个类直接返回instanceof的结果。如果出现了其他类,那么就扬弃这段编译后的呆板码,并且切换回表明执行。
当然,JVM的优化方式仅仅作用在运行应用代码的时候。如果应用代码本身壅闭了,比如说并发时等待另一线程的结果,这就不在JVM的优化范畴啦。
Java核心技能口试精讲
附上资源MP4下载地址
我用夸克网盘分享了「1001-Java核心技能口试精讲(更新完毕)」,点击链接即可生存。打开「夸克APP」,无需下载在线播放视频,畅享原画5倍速,支持电视投屏。
链接:https://pan.quark.cn/s/9b6c06b216fd
失效访问:https://cowcowit.com/course/324/325
考点分析
感谢郑雨迪博士从JVM的角度给出的答复。今天这道口试题在专栏里有不少同砚问我,也是会在口试时被口试官刨根问底的一个知识点,郑博士的答复已经非常全面和深入啦。
大多数Java工程师并不是JVM工程师,知识点总归是要落地的,口试官很有可能会从实践的角度探究,比方,如安在生产实践中,与JIT等JVM模块举行交互,落实到怎样真正举行实际调优。
在今天这一讲,我会从Java工程师一样平常的角度出发,侧重于:
从整体去了解Java代码编译、执行的过程,目的是对根本机制和流程有个直观的熟悉,以保证可以大概理解调优选择背后的逻辑。
从生产体系调优的角度,谈谈将JIT的知识落实到实际工作中的可能思路。这里包括两部门:怎样网络JIT相关的信息,以及具体的调优手段。
知识扩展
首先,我们从整体的角度来看看Java代码的整个生命周期,你可以参考我提供的示意图。
我在 专栏第1讲 就已经提到过,Java通过引入字节码这种中间表达方式,屏蔽了不同硬件的差别,由JVM负责完成从字节码到呆板码的转化。
通常所说的编译期,是指javac等编译器或者相关API等将源码转换成为字节码的过程,这个阶段也会举行少量类似常量折叠之类的优化,只要利用反编译工具,就可以直接查看细节。
javac优化与JVM内部优化也存在关联,究竟它负责了字节码的天生。比方,Java 9中的字符串拼接,会被javac更换成对StringConcatFactory的调用,进而为JVM举行字符串拼接优化提供了统一的入口。在实际场景中,还可以通过不同的 策略 选项来干预这个过程。
今天我要讲的重点是
JVM运行时的优化
,在通常环境下,编译器和表明器是共同起作用的,具体流程可以参考下面的示意图。
JVM会根据统计信息,动态决定什么方法被编译,什么方法表明执行,即使是已经编译过的代码,也可能在不同的运行阶段不再是热门,JVM有必要将这种代码从Code Cache中移除出去,究竟其大小是有限的。
就如郑博士所答复的,表明器和编译器也会举行一些通用优化,比方:
锁优化,你可以参考我在 专栏第16讲 提供的表明器运行时的源码分析。
Intrinsic机制,或者叫作内建方法,就是针对特别重要的底子方法,JDK团队直接提供定制的实现,利用汇编或者编译器的中间表达方式编写,然后JVM会直接在运行时举行更换。
这么做的理由有许多,比方,不同体系结构的CPU在指令等层面存在着差别,定制才华充分发挥出硬件的本领。我们一样平常利用的典型字符串操作、数组拷贝等底子方法,Hotspot都提供了内建实现。
而
即时编译器(JIT)
,则是更多优化工作的负担者。JIT对Java编译的根本单位是整个方法,通过对方法调用的计数统计,甄别出热门方法,编译为本地代码。别的一个优化场景,则是最针对所谓热门循环代码,利用通常说的栈上更换技能(OSR,On-Stack Replacement,更加细节请参考 R大的文章),如果方法本身的调用频度还不够编译标准,但是内部有大的循环之类,则还是会有进一步优化的价值。
从理论上来看,JIT可以看作就是基于两个计数器实现,方法计数器和回边计数器提供给JVM统计数据,以定位到热门代码。实际中的JIT机制要复杂得多,郑博士提到了 逃逸分析、 循环展开、方法内联等,包括前面提到的Intrinsic等通用机制同样会在JIT阶段发生。
第二,有哪些手段可以探查这些优化的具体发生环境呢?
专栏中已经陆连续续介绍了一些,我来简朴总结一下并补充部门细节。
打印编译发生的细节。
-XX:+PrintCompilation
复制代码
输出更多编译的细节。
-XX:UnlockDiagnosticVMOptions -XX:+LogCompilation -XX:LogFile=<your_file_path>
复制代码
JVM会天生一个xml形式的文件,别的, LogFile选项是可选的,不指定则会输出到
hotspot_pid<pid>.log
复制代码
具体格式可以参考Ben Evans提供的 JitWatch 工具和 分析指南。
打印内联的发生,可利用下面的诊断选项,也必要明确解锁。
-XX:+PrintInlining
复制代码
怎样知晓Code Cache的利用状态呢?
许多工具都已经提供了具体的统计信息,比如,JMC、JConsole之类,我也介绍过利用NMT监控其利用。
第三,我们作为应用开发者,有哪些可以触手可及的调优角度和手段呢?
调整热门代码门限值
我曾经介绍过JIT的默认门限,server模式默认10000次,client是1500次。门限大小也存在着调优的可能,可以利用下面的参数调整;与此同时,该参数还可以变相起到降低预热时间的作用。
-XX:CompileThreshold=N
复制代码
许多人可能会产生疑问,既然是热门,不是早晚会到达门限次数吗?这个还真未必,由于JVM会周期性的对计数的数值举行衰减操作,导致调用计数器永久不能到达门限值,除了可以利用CompileThreshold得当调整大小,另有一个办法就是关闭计数器衰减。
-XX:-UseCounterDecay
复制代码
如果你是利用debug版本的JDK,还可以利用下面的参数举行试验,但是生产版本是不支持这个选项的。
-XX:CounterHalfLifeTime
复制代码
调整Code Cache大小
我们知道JIT编译的代码是存储在Code Cache中的,必要注意的是Code Cache是存在大小限制的,而且不会动态调整。这意味着,如果Code Cache太小,可能只有一小部门代码可以被JIT编译,其他的代码则没有选择,只能表明执行。以是,一个潜在的调优点就是调整其大小限制。
-XX:ReservedCodeCacheSize=<SIZE>
复制代码
当然,也可以调整其初始大小。
-XX:InitialCodeCacheSize=<SIZE>
复制代码
注意,在相对较新版本的Java中,由于分层编译(Tiered-Compilation)的存在,Code Cache的空间需求大大增加,其本身默认大小也被提高了。
调整编译器线程数,或者选择得当的编译器模式
JVM的编译器线程数量与我们选择的模式有关,选择client模式默认只有一个编译线程,而server模式则默认是两个,如果是当前最普遍的分层编译模式,则会根据CPU内核数量计算C1和C2的数值,你可以通过下面的参数指定的编译线程数。
-XX:CICompilerCount=N
复制代码
在强劲的多处理器环境中,增大编译线程数,可能更加充分的利用CPU资源,让预热等过程更加快速;但是,反之也可能导致编译线程争抢过多资源,尤其是当体系非常繁忙时。比方,体系部署了多个Java应用实例的时候,那么减小编译线程数量,则是可以考虑的。
生产实践中,也有人推荐在服务器上关闭分层编译,直接利用server编译器,固然会导致稍慢的预热速度,但是可能在特定工作负载上会有微小的吞吐量提高。
其他一些相对边界比较混淆的所谓“优化”
比如,减少进入安全点。严格说,它远远不但是发生在动态编译的时候,GC阶段发生的更加频仍,你可以利用下面选项诊断安全点的影响。
-XX:+PrintSafepointStatistics ‑XX:+PrintGCApplicationStoppedTime
复制代码
注意,在JDK 9之后,PrintGCApplicationStoppedTime已经被移除了,你必要利用“-Xlog:safepoint”之类方式来指定。
许多优化阶段都可能和安全点相关,比方:
在JIT过程中,逆优化等场景会必要插入安全点。
常规的锁优化阶段也可能发生,比如,偏斜锁的设计目的是为了避免无竞争时的同步开销,但是当真的发生竞争时,打消偏斜锁会触发安全点,是很重的操作。以是,在并发场景中偏斜锁的价值实在是被质疑的,经常会明确发起关闭偏斜锁。
-XX:-UseBiasedLocking
复制代码
重要的优化手段就介绍到这里,这些方法都是平凡Java开发者就可以利用的。如果你想对JVM优化手段有更深入的了解,发起你订阅JVM专家郑雨迪博士的专栏。
一课一练
关于今天我们讨论的题目你做到胸有定见了吗? 请思考一个问题,怎样程序化验证final关键字是否会影响性能?
请你在留言区写写你对这个问题的思考,我会选出经过认真思考的留言,送给你一份学习奖励礼券,欢迎你与我一起讨论。
你的朋友是不是也在预备口试呢?你可以“请朋友读”,把今天的题目分享给好友,大概你能帮到他。
点击下方图片进入JVM专栏
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 qidao123.com技术社区-IT企服评测·应用市场 (https://dis.qidao123.com/)
Powered by Discuz! X3.4