一、GC性能参数
系统的性能可以从三个维度进行衡量:
- Latency(延迟)
- Throughput(吞吐量)
- Capacity(系统容量)
延迟
- 所有交易必须在10秒内得到响应
以上就是一个延迟指标,对于这类指标,需要确保在交易过程中, GC暂停不能占用太多时间。
2015-06-04T13:34:16.974-0200: 2.578: [Full GC (Ergonomics)
[PSYoungGen: 93677K->70109K(254976K)]
[ParOldGen: 499597K->511230K(761856K)]
593275K->581339K(1016832K),
[Metaspace: 2936K->2936K(1056768K)]
, 0.0713174 secs]
[Times: user=0.21 sys=0.02, real=0.07 secs
从上方的GC日志中可见,当前事件将应用线程暂停了0.0713174秒(real)。
吞吐量
- 解决方案每天必须处理 100万个订单
以上是一个吞吐量指标,吞吐量需求是在给定的时间内, 系统必须完成多少个操作。因此,和延迟需求类似, GC调优也需要确定GC行为所消耗的总时间。在上方的日志中可见,0.23s(user + sys = 0.21 + 0.02 s)是这段时间内 GC 暂停占用 cpu 资源的时间。
系统容量
系统容量(Capacity)需求,是在达成吞吐量和延迟指标的情况下,对硬件环境的额外约束。
二、简单的调优实验
在启动程序时,可以通过以下参数打开GC日志:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -Xloggc:C:\Producer_gc.log
基于日志中的信息, 可以通过三个优化目标来提升性能:
- 确保最坏情况下,GC暂停时间不超过预定阈值(延迟)
- 确保线程暂停的总时间不超过预定阀值(吞吐量)
- 在确保达到延迟和吞吐量指标的情况下, 降低硬件配置以及成本(容量)
堆内存大小 和 GC算法 是程序运行时可选择的部分参数:
| 堆内存大小(Heap) | GC算法(GC Algorithm) | 有效时间比(Useful work) | 最长停顿时间(Longest pause) |
|---|---|---|---|
| -Xmx12g | -XX:+UseConcMarkSweepGC | 89.8% | 560 ms |
| -Xmx12g | -XX:+UseParallelGC | 91.5% | 1,104 ms |
| -Xmx8g | -XX:+UseConcMarkSweepGC | 66.3% | 1,610 ms |
三、JVM分区
JVM监控工具是定位问题的有效方法,在学习监控工具前,需要理解“新生代”和“老生代”的含义。常说的 新生代(Young Generation) 和 老年代(Old Generation) ,本质上是 按对象生命周期特征划分的两块堆内存区域:
- 新生代:主要存放刚创建不久、生命周期短的对象
- 老年代:主要存放经历过多次垃圾回收后仍然存活、生命周期较长的对象
JVM 采用了经典的 分代收集思想(Generational Collection) :
- 对大量短命对象,用频繁但成本较低的方式回收(Young GC)
- 对少量长寿对象,用相对更重但不那么频繁的方式回收(Full GC)
JVM 运行 Java 程序时,内存是按对象生命周期和用途进行分区管理:
- Eden:新对象最先分配的地方
- Survivor:在 Young GC 后仍然存活的对象暂存区
- Old(Old Generation) :存放长期存活对象
- Metaspace:存放类元数据,不在 Java 堆里,使用本地内存
Java Heap
├── Young Generation
│ ├── Eden
│ ├── Survivor From
│ └── Survivor To
└── Old Generation
Non-Heap
└── Metaspace
通过观察不同分区的使用情况,可以帮助我们定位问题。
四、JVM监控工具
以下是常用的JVM监控工具:
1、jps
jps 是 JDK 自带的一个 Java 进程查看工具,常用参数包括:
jps # 只输出PID和主类名
jps -l # 输出完整类名 / Jar 路径
jps -m # 输出传给 main 方法的参数
jps -v # 输出 JVM 参数
jps -q # 只输出 PID,适用于脚本处理
jps 命令只会输出 Java 进程信息,输出内容精简,便于查看。
2、jstat
jstat [option] <vmid> [interval] [count]
- interval:执行间隔时间
- count:执行次数
常用 jstat 命令包括:
-gc
-gc 命令可以查看堆各区域容量与使用量,jstat -gc 12345命令输出的结果如下:
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
16384.0 17920.0 0.0 0.0 262144.0 132572.2 197120.0 24724.2 59160.0 55833.9 8232.0 7676.9 8 0.051 3 0.155 0.206
S0C/S1C:Survivor0 / Survivor1 容量S0U/S1U:Survivor0 / Survivor1 已使用大小EC/EU:Eden 容量 / 已使用大小OC/OU:Old 区容量 / 已使用大小MC/MU:Metaspace 容量 / 已使用大小CCSC/CCSU:Compressed Class Space 容量 / 已使用大小YGC/YGCT:Young GC 次数 / Young GC 总耗时FGC/FGCT:Full GC 次数 / Full GC 总耗时GCT:GC 总耗时
-gcutil
快速查看各区域使用率百分比,相比-gc,该方法输出内容更加简洁直观:
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 50.57 12.54 94.38 93.26 8 0.051 3 0.155 0.206
S0/S1:Survivor 区使用率E:Eden 区使用率O:Old 区使用率M:Metaspace 使用率CCS:Compressed Class Space 使用率YGC/YGCT:Young GC 次数 / 耗时FGC/FGCT:Full GC 次数 / 耗时GCT:GC 总耗时
-gccapacity
查看 JVM 各区域当前容量、最小值、最大值
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
86016.0 1374720.0 280064.0 14848.0 17408.0 245248.0 172032.0 2749952.0 204800.0 204800.0 0.0 1099776.0 59160.0 0.0 1048576.0 8232.0 8 3
NGCMN/NGCMX:年轻代最小容量 / 年轻代最大容量NGC:当前年轻代总容量S0C/S1C:Survivor 0 容量 / Survivor 1 容量EC:Eden 区当前容量OGCMN/OGCMX:老年代最小容量 / 老年代最大容量OGC/OC:当前老年代容量MCMN/MCMX:Metaspace 最小容量 / Metaspace 最大容量MC:当前 Metaspace 容量CCSMN/CCSMX:Compressed Class Space 最小容量 / 最大容量CCSC:当前 Compressed Class Space 容量YGC:Young GC 次数FGC:Full GC 次数
-gcnewcapcity和-gcoldcapacity可以专注于查看新生代、老年代的使用情况
-class
查看类加载情况
Loaded Bytes Unloaded Bytes Time
13233 23485.6 0 0.0 6.95
- Time:已加载类耗时
五、简单的GC监控实验
jstat是监控GC的最常用工具,对于 jstat 常用的参数,可以通过jstat -options查看。
执行 jstat -gc -t 2428 1s 可以让 jstat 每秒向标准输出输出一行新内容:
Timestamp S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
200.0 8448.0 8448.0 8448.0 0.0 67712.0 67712.0 169344.0 169344.0 21248.0 20534.3 3072.0 2807.7 34 0.720 658 133.684 134.404
201.0 8448.0 8448.0 8448.0 0.0 67712.0 67712.0 169344.0 169343.2 21248.0 20534.3 3072.0 2807.7 34 0.720 662 134.712 135.432
202.0 8448.0 8448.0 8102.5 0.0 67712.0 67598.5 169344.0 169343.6 21248.0 20534.3 3072.0 2807.7 34 0.720 667 135.840 136.559
203.0 8448.0 8448.0 8126.3 0.0 67712.0 67702.2 169344.0 169343.6 21248.0 20547.2 3072.0 2807.7 34 0.720 669 136.178 136.898
204.0 8448.0 8448.0 8126.3 0.0 67712.0 67702.2 169344.0 169343.6 21248.0 20547.2 3072.0 2807.7 34 0.720 669 136.178 136.898
205.0 8448.0 8448.0 8134.6 0.0 67712.0 67712.0 169344.0 169343.5 21248.0 20547.2 3072.0 2807.7 34 0.720 671 136.234 136.954
206.0 8448.0 8448.0 8134.6 0.0 67712.0 67712.0 169344.0 169343.5 21248.0 20547.2 3072.0 2807.7 34 0.720 671 136.234 136.954
207.0 8448.0 8448.0 8154.8 0.0 67712.0 67712.0 169344.0 169343.5 21248.0 20547.2 3072.0 2807.7 34 0.720 673 136.289 137.009
208.0 8448.0 8448.0 8154.8 0.0 67712.0 67712.0 169344.0 169343.5 21248.0 20547.2 3072.0 2807.7 34 0.720 673 136.289 137.009
jstat 的输出可以体现很多问题:
- 最后一列 GCT, 与JVM的总运行时间 Timestamp 的比值, 就是GC 的开销。如果每一秒内, GCT 的值都会明显增大, 与总运行时间相比, 就暴露出GC开销过大的问题. 一般来讲, 超过
10%的GC开销都是有问题的。 - YGC 和 FGC 列的快速变化往往也是有问题的征兆。频繁的GC暂停会累积,并导致更多的线程停顿(stop-the-world pauses), 进而影响吞吐量。
- 如果看到 OU 列中,老年代的使用量约等于老年代的最大容量(OC), 并且不降低的话, 就表示虽然执行了老年代GC, 但基本上属于无效GC。