初识JIT编译

353 阅读5分钟

引入

先看一段特别简单的Java代码:每次创建1万个对象并打印耗时,重复100次。

测试代码:

static final int CHUNK_SIZE = 10_000;

public static void main(String[] args) {
    for (int i = 0; i < 100; ++i) {
        long startTime = System.nanoTime();

        for (int j = 0; j < CHUNK_SIZE; ++j) {
            new Object();
        }

        long endTime = System.nanoTime();
        System.out.printf("%d\t%d%n", i, endTime - startTime);
    }
}

执行结果如下:

0	351833
1	305291
2	305375
3	305708
4	310625
5	308250
6	319709
7	108333
8	38667
9	37791
10	38791
11	38916
12	37791
...
31	38708
32	40333
33	63834
34	20958
35	3583
36	3250
37	3209
38	3125
39	3250
40	3125
...
96	3167
97	3125
98	3125
99	3167

使用Python分析执行结果的代码如下:

import matplotlib.pyplot as plt

# 数据
indices = list(range(100))
values = [
    351833, 305291, 305375, 305708, 310625, 308250, 319709, 108333, 38667, 37791,
    38791, 38916, 37791, 68500, 84833, 54792, 39167, 44416, 126375, 83584,
    55291, 41625, 46584, 37792, 37667, 38750, 38375, 38542, 47042, 39292,
    39417, 38708, 40333, 63834, 20958, 3583, 3250, 3209, 3125, 3250,
    3125, 3166, 3167, 3166, 3125, 3125, 3209, 3209, 3166, 3208,
    3125, 3167, 3125, 3125, 3167, 3209, 3125, 3250, 3167, 3167,
    3166, 3125, 3166, 3125, 3083, 3125, 3125, 3167, 3166, 3166,
    3208, 3167, 3125, 3167, 3208, 3166, 3125, 3125, 3250, 3208,
    3125, 3166, 3125, 3709, 3125, 3125, 3125, 3125, 3167, 3125,
    3166, 3167, 3125, 3208, 3167, 3084, 3167, 3125, 3125, 3167
]

# 绘制折线图
plt.figure(figsize=(12, 6))
plt.plot(indices, values, marker='o', linestyle='-', color='b')
plt.title('Trend Analysis')
plt.xlabel('Index')
plt.ylabel('Value')
plt.grid(True)

# 显示图表
plt.show()

结果如下:

Figure_1.png 能看出来什么呢?相同的代码随着程序不断执行,执行耗时共经历了三个平台期,并在第三个平台期趋于稳定。

JIT编译快速上手

JIT编译是JVM中的一种优化技术。简单说就是JVM会针对热点代码进行优化,经过JIT编译的代码执行效率会更高。热点代码是执行次数超过阈值的代码,该阈值可以通过JVM参数设置

  • 优点:提高执行代码执行效率
  • 缺点:应用刚启动时,JVM会占用CPU对热点代码进行优化,导致执行业务代码的CPU核心数减少,通常表现为应用启动时机器load增高。

阅读

读后应该了解的:

  • JVM中编译器的种类
  • 分层编译
  • 编译优化的方式有哪些
  • CodeCache是什么

JIT相关JVM参数

由于编译情况复杂,JVM也会动态调整相关的阈值来保证JVM的性能,所以不建议手动调整编译相关的参数。

基本功 | Java即时编译器原理解析及实践

常规参数

参数解释
-XX:+TieredCompilation开启分层编译,JDK8之后默认开启
-XX:+CICompilerCount=N编译线程数,设置数量后,JVM会自动分配线程数,C1:C2 = 1:2 。以4核CPU为例,N=3时C1编译器会被分配1个CPU核心,C2编译器会被分配2个CPU核心数
-XX:ReservedCodeCacheSizeCodeCache的最大大小
-XX:InitialCodeCacheSizeCodeCache的初始大小

编译阈值

关于编译阈值,网上很多资料都说由参数-XX:CompileThreshold确定,而在开启分层编译后该参数就会失效,并使用另外三个参数,通过更复杂的方式计算得出变异阈值。

而在分层编译的情况下,-XX: CompileThreshold 指定的阈值将失效,此时将会根据当前待编译的方法数以及编译线程数来动态调整

【得物技术】服务发布时“网络抖动”

分层编译触发条件公式

i > TierXInvocationThreshold * s || (i > TierXMinInvocationThreshold * s  >&& i + b > TierXCompileThreshold * s)  
i为调用次数,b是循环回边次数

公式中的s是JVM通过待编译方法数和编译线程数动态调整的,所以我们很难通过手动调整JVM参数确定具体的编译阈值,只能通过不断调大or调小参数,以期找到合适的参数。