"C2 CompilerThread9" #48 daemon prio=9 os_prio=0 tid=0x00007f45f0b80000 nid=0x188 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
补充描述:我的应用类型为后台接口服务,系统秒级调用峰值在10W+,JRE版本如下:
java version "1.8.0_231"
Java(TM) SE Runtime Environment (build 1.8.0_231-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.231-b11, mixed mode)
步骤一:了解JIT编译原理
因为之前对JIT的编译原理并不了解,不敢随意修改线上服务器的编译类型,担心会有一些其他的副作用,所以在网上开始了查阅资料学习的过程。
什么是JIT编译?
编译器在编译过程中通常会考虑很多因素。比如:汇编指令的顺序。假设我们要将两个寄存器的值进行相加,执行这个操作一般只需要一个CPU周期;但是在相加之前需要将数据从内存读到寄存器中,这个操作是需要多个CPU周期的。编译器一般可以做到,先启动数据加载操作,然后执行其它指令,等数据加载完成后,再执行相加操作。由于解释器在解释执行的过程中,每次只能看到一行代码,所以很难生成上述这样的高效指令序列。而编译器可以事先看到所有代码,因此,一般来说,解释性代码比编译性代码要慢。
java 作为静态语言十分特殊,他需要编译,但并不是在执行之前就编译为本地机器码。Java的实现在解释性和编译性之间进行了折中,Java代码是编译性的,它会被编译成一个平台独立的字节码程序。JVM负责加载、解释、执行这些字节码程序,在这个过程中,还可能会将这些字节码实时编译成目标机器码,以便提升性能。
所以,在谈到 java的编译机制的时候,其实应该按时期,分为两个部分。一个是 javac指令 将java源码变为 java字节码的静态编译过程。 另一个是 java字节码编译为本地机器码的过程,并且因为这个过程是在程序运行时期完成的所以称之为即时编译(JIT:Just In Time)。
JIT编译类型:C1编译器、C2编译器、分层编译器
通常我们说即时编译器
有两种类型,Client Compiler(C1编译器)和Server Compiler(C2编译器)。这两种编译器最大的区别就是,编译代码的时间点不一样。C1编译器会更早的对代码进行编译,因此在程序刚启动的时候,C1编译器比C2编译器执行的更快,所以C1编译器适用于一些GUI应用
,可以缩短应用启动时间。C2编译器会收集更多的信息
,然后才对代码进行编译优化,所以从长远角度考虑,C2编译器最终可以产生比C1编译器更优秀的代码,适用于长时间运行
的后台接口服务。
可能大家都有一个困扰,JVM为什么要将编译器分为client和server,为什么不在程序启动时,使用client编译器,在程序运行一段时间后,自动切换为server编译器? 其实,这种技术是存在的,一般称之为 Tiered Compiler(分层编译器)。Java7 和Java 8可以使用选项-XX:+TieredCompilation
来打开(-server
选项也要打开)。在Java 8中,-XX:+TieredCompilation
默认是打开的。
分层编译将 JVM 的执行状态分为了 5 个层次:
在一些特殊情况下,激进优化后的代码并不能有更高的性能。需要进行优化回退,将重新对代码进行解释执行。因此
C2编译器相对于C1编译器更适用于我们系统
分层编译器是综合考虑C1和C2编译器的优点衍生出的一种进化版本编译器,但是由于我们是纯后台应用,这种衍生优化是否有效未可知。
分层编译器在一些特殊情况下可能比较激进、不可靠。
JIT学习参考博文:
https://blog.csdn.net/qq_28674045/article/details/51896129
https://www.cnblogs.com/insistence/p/5901457.html
https://www.cnblogs.com/death00/p/11722130.html
步骤二:关闭分层编译,启用C2编译器
JVM启动脚本中添加如下参数
-XX:-TieredCompilation -server