JVM c1、c2 编译线程——CPU 消耗高?


c1, c2编译器线程是由Java虚拟机创建的,以优化你的应用程序的性能。偶尔,这些线程会倾向于消耗高CPU。在这篇文章中,让我们了解更多关于c1、c2编译器线程的情况,以及如何解决它们的高CPU消耗。

读完这篇文章后,像Hotspot JIT、c1编译器线程、c2编译器线程、代码缓存这样的术语可能不会让你感到恐惧(就像过去让我感到恐惧一样)。

什么是Hotspot JIT编译器?
你的应用程序可能有数百万行的代码。然而,只有一小部分代码会被反复执行。这个小的代码子集(也被称为 "热点")负责你的应用程序的性能。在运行时,JVM使用这个JIT(Just in time)编译器来优化这个热点代码。大多数时候,由应用程序开发人员编写的代码并不是最佳的。因此,JVM的JIT编译器对开发者的代码进行优化,以提高性能。为了进行这种优化,JIT编译器使用C1、C2编译器线程。

什么是代码缓存?
JIT编译器用于代码编译的内存区域被称为 "代码缓存"。这个区域位于JVM堆和元空间之外。要了解不同的JVM内存区域,你可以参考这个视频片段。

c1和c2编译器线程之间的区别是什么?
 在Java的早期,有两种类型的JIT编译器。

  •  a. 客户端
  •  b. 服务器

根据你想使用的JIT编译器的类型,必须下载和安装适当的JDK。例如,如果你正在构建一个桌面应用程序,那么需要下载具有 "客户端 "JIT编译器的JDK。如果你要构建一个服务器应用程序,那么就需要下载具有 "服务器 "JIT编译器的JDK。

客户端 JIT 编译器会在应用程序启动后立即开始编译代码。服务器JIT编译器将观察代码的执行情况相当一段时间。基于它获得的执行知识,它将开始进行JIT编译。尽管服务器JIT编译速度很慢,但它产生的代码将比客户端JIT编译器产生的代码更优秀、更有性能。

今天,现代的JDK同时带有客户端和服务器JIT编译器。这两种编译器都试图优化应用程序的代码。在应用程序启动时,代码是用客户端JIT编译器编译的。后来,随着知识的增加,代码就用服务器JIT编译器进行编译。这被称为JVM中的分层编译。

 JDK的开发者称他们为客户端和服务器JIT编译器,内部称为c1和c2编译器。因此,客户端JIT编译器使用的线程被称为c1编译器线程。服务器JIT编译器使用的线程被称为c2编译器线程。

c1、c2编译器线程的默认大小
c1、c2编译器线程的默认数量是根据运行应用程序的容器/设备上可用的CPU数量决定的。下面的表格总结了c1、c2编译器线程的默认数量。

CPUs    c1 threads    c2 threads
1    1    1
2    1    1
4    1    2
8    1    2
16    2    6
32    3    7
64    4    8
128    4    10

你可以通过向你的应用程序传递'-XX:CICompilerCount=N'JVM参数来改变编译器线程数。你在'-XX:CICompilerCount'中指定的数量的三分之一将被分配给c1编译器线程。剩余的线程数将被分配给c2编译器线程。假设你要用6个线程(即'-XX:CICompilerCount=6'),那么2个线程将被分配给c1编译器线程,4个线程将被分配给c2编译器线程。

c1, c2编译器线程高CPU消耗 - 潜在的解决方案
 有时你可能会看到c1、c2编译器线程消耗了大量的CPU。当这种类型的问题出现时,下面是解决它的潜在方案。

1. 什么都不做(如果是间歇性的)
在你的案例中,如果你的C2编译器线程的CPU消耗只是间歇性的高,而不是持续的高,并且它没有损害你的应用程序的性能,那么你可以考虑忽略这个问题。

2. -XX:-TieredCompilation
将这个'-XX:-TieredCompilation'JVM参数传递给你的应用程序。这个参数将禁用JIT热点编译。因此,CPU的消耗将下降。然而,作为副作用,你的应用程序的性能会下降。

3. -XX:TieredStopAtLevel=N
如果CPU峰值是由c2编译器线程单独造成的,你可以单独关闭c2编译。你可以通过'-XX:TieredStopAtLevel=3'。当你传递这个'-XX:TieredStopAtLevel'参数的值为3时,那么只有c1编译会被启用,c2编译会被禁用。

有四个层次的编译:

Compilation Level    Description
0    Interpreted Code
1    Simple c1 compiled code
2    Limited c1 compiled code
3    Full c1 compiled code
4    C2 compiled code

当你说'-XX:TieredStopAtLevel=3'时,代码将只编译到'Full c1 compiled code'级别。C2的编译将被停止。

4. -XX:+PrintCompilation
你可以向你的应用程序传递'-XX:+PrintCompilation'JVM参数。它将打印关于你的应用程序的编译过程的细节。这将有助于你进一步调整编译过程。

5. -XX:ReservedCodeCacheSize=N
Hotspot JIT编译器编译/优化的代码被存储在JVM内存的代码缓存区。该代码缓存区的默认大小为240MB。你可以通过向你的应用程序传递'-XX:ReservedCodeCacheSize=N'来增加它。例如,如果你想让它变成512MB,你可以这样指定。'-XX:ReservedCodeCacheSize=512m'。增加代码缓存大小有可能减少编译器线程的CPU消耗。

6. -XX:CICompilerCount
你可以考虑通过使用参数'-XX:CICompilerCount'增加C2编译器线程。你可以捕获线程转储并将其上传到fastThread等工具,在那里你可以看到C2编译器线程的数量。如果你看到较少的C2编译器线程数,而你有更多的CPU处理器/核,你可以通过指定'-XX:CICompilerCount=8'参数增加C2编译器线程数。