C2 CompilerThread9 长时间占用CPU解决方案

一、描述:异常线程的堆栈如下:

"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的编译原理并不了解,不敢随意修改线上服务器的编译类型,担心会有一些其他的副作用,所以在网上开始了查阅资料学习的过程。

什么是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 个层次:

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

在一些特殊情况下,激进优化后的代码并不能有更高的性能。需要进行优化回退,将重新对代码进行解释执行。因此

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章