本文主要分析JVM的功能《JIT》从java日志中分析
👉👉运行环境JDK17👈👈
本文侧重点不是提前编译与即时编译的优缺点,而是在JVM中的执行
Java第一课都告诉我们:Java是无关平台的。换一种描述Java是面向JVM的,通常我们写出的程序要像被计算机识别并运行,需要编译成机器码,而Java代码并不是直接编译成机器码而是字节码,这意味着如果想要被计算机运行代码需要经过一层编译:。
那么每执行一个方法,都需要从字节码编译成机器码才能被运行,这样子的执行效率是较低的,因为每次都需要编译。
由此JIT诞生,JIT(just in time)的缩写, 也就是即时编译,在运行Java程序的时候,通过判断热点代码,对执行频率高的代码进行即时编译,直接编译成机器码,后续不需要再从字节码编译成机器码才能执行,从而提高执行效率。
上问聊到热点代码会被"即时编译"成机器码,下文解答什么条件下成为热点代码。
主要有两种方案
采用这种方法的虚拟机会周期性地检查各个线程的栈顶,如果发现某些方法经常出现在栈顶,那这个方法就是“热点方法”。这种探测方法的好处是实现简单高效,还可以很容易地获取方法调用关系(将调用堆栈展开即可),缺点是很难精确地确认一个方法的热度,容易因为受到线程阻塞或别的外界因素的影响而扰乱热点探测。
采用这种方法的虚拟机会为每个方法(甚至是代码块)建立计数器,统计方法的执行次数,如果执行次数超过一定的阀值,就认为它是“热点方法”。这种统计方法实现复杂一些,需要为每个方法建立并维护计数器,而且不能直接获取到方法的调用关系,但是它的统计结果相对更加精确严谨。
在HotSpot虚拟机中使用的是第二种——基于计数器的热点探测方法,因此它为每个方法准备了两个计数器:方法调用计数器和回边计数器。在确定虚拟机运行参数的前提下,这两个计数器都有一个确定的阈值,当计数器超过阈值溢出了,就会触发JIT编译。
👉https://blog.csdn.net/Genmer/article/details/119355224
编译阈值(-XX:CompileThreshold,client模式下默认是1500,server模式默认是10000)
首先我们看看JVM的JIT日志:1w个HTTP请求后,打印出的日志信息
👇JVM配置:打印jit日志
-Xlog:jit+compilation=debug:file=D:\gclog\gc.log:level,tags,time,uptime,pid
在1w个请求后的日志里可以直接看出,有8884条即时编译记录,因为使用的是Netty作为容易所以能看到netty的编译记录较多,当然排名靠前的基本都是JDK相关。
⭐1w条http请求共花费了66秒,但Jit高达8千多个方法⭐
再看看Jit的空间:JVM打印出的日志可以看出真个JIT空间是很大了,48M对热点代码全部编译成机器码,我像也是绰绰有余
👇JVM打印配置
-Xms500M -Xms500M -Xlog:codecache+sweep*=trace:file=D:\gclog\gc.log:level,tags,time,uptime,pid
在调用4w次接口后也未发现清理日志,可见是空间足够无需过度优化
判断条件主要有两,被判定为热点代码的阈值,卸载阈
-XX:CompileThreshold=1000 //方法调用计数阈值
-XX:-TieredCompilation //关闭分层编译
-XX:OnStackReplacePercentage=1000 //回边计数阈值
-XX:InitialCodeCacheSize=80M //初始CodeCache大小
-XX:ReservedCodeCacheSize=200M //最大大小
在笔者花费5小时方向优化后,性能反倒是倒退👆,有些参数概念还需读者自己去阅读文章
结论:或许关闭分层编译峰值会更好,但均衡下分层编译(Jit)反而对接口TPS更高,无需过度优化
如果希望项目拥有更好的峰值性能,全项目静态编译,读者可以去了解一下GraalVM、Spring Native、
-EOF-