各位读者大家好呀!今天小卡给大家带来的是JVM调优及故障排查~
1. GC调优策略
1.1. 降低MinorGC频率
通常情况下新生代内存较小,eden很快就会别填满,就会导致MinorGC频繁发生,所以可以适当的增加新生代的大小,来降低MinorGC的频率。
为什么说适当的增加新生代的大小,能够降低MinorGC的频率同时也不会对一次GC速度产生明显的影响。比如说eden总对象新分配的对象,能够存活500ms,但是因为空间新生代空间较小,所以大概300ms就会进行一次MinorGC,那么在GC时新分配的对象总是能够活过第一次GC,那么在对象被清除过后就需要把存活的对象复制到survivor中。
但是如果适当的增加新生代的大小,那么MinorGC就不会那么频繁,大概600ms才会进行一次MinorGC,虽然看上去需要扫描的对象多了,但是最后存活的对象变少了,因为大多数的对象生存时间为500ms,经过调整新生代的大小后它们就再也活不过第一次GC了,那么需要不复制的对象也就变小了,所以综合着来看的话,虽然需要扫描的对象变多了,但是需要复制的对象变少了,所以对于单次MinorGC的速度其实影响并不明显。
但是其实这也需要考虑新生代对象的存活时间,如果新生代中存活的时间比较长的话,提高新生带的大小,可能就会对单次MinorGC的速度产生较大的影响,因为对象的存活时间变长,那么大部分的对象都有可能活过第二第三次GC,而同时新生代的空间被调大,那么在GC时需要被复制的对象也就会变多,所以肯定就会对单次MinorGC的速度产生较大的影响。
同时我们从单次MinorGC的STW和吞吐量上进行分析适当增加新生代的大小,并发标记的时间会有所增加,那么重新标记期间STW的时间也就会有所增加,但是因为需要被复制的对象减少了,那么在清除阶段的STW时间就会有所下降,所以对于单次MinorGC的STW时间不会有太多的影响,同时因为对于单次MinorGC的速度也没有明显的影响,所以相对而言其实吞吐量是有所增加的。
1.2. 降低FullGC频率
通常情况下,因为堆空间的不足或者是老年代的对象太多会触发FullGC,而默认JDK1.8使用的PalleraGC多线程并行的垃圾回收期,那么频繁地FullGC将会导致频繁地上下文切换,导致大量的性能开销,所以我们可以通过降低FullGC的频率来降低垃圾回收对于性能的消耗。
所以我们首先所能想到的就是通过增大对空间的大小来,降低FullGC的频率,或者是通过设置初始化堆内存为最大堆内存来降低FullGC的频率。
其实就是去拆分大对象或者说是避免频繁的创建大对象,来避免老年代对象太多导致频繁的FullGC,因为根据对象的分配规则,为了避免大对象在新生代的频繁复制所带来的性能消耗,大对象会被直接分配到老年代。
1.3. 选择合适的GC
须在 500ms 以内。这个时候我们般会选择响应速度较快的 GC 回收器,CMS(Concurrent Mark Sweep)回收器和G1 回收器都是不错的选择.
而当我们的需求对系统吞吐量有要求时,就可以选择 Parallel Scavenge 回收器来提高系统的吞吐量。
1.4. 查看 & 分析 GC 日志
-XX:+PrintGCDetails
#输出GC的详细日志
-XX:+PrintGCTimeStamps
#输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps
#输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)
-XX:+PrintHeapAtGC
#在进行GC的前后打印出堆的信息
-XX:+PrintGCDetails
# 启用详细的垃圾回收(GC)日志输出,包括GC类型、内存区域的变化、GC耗时等详细信息。
-XX:+PrintGCDateStamps
# 在GC日志中添加时间戳,显示每次GC发生的具体日期和时间,便于分析GC事件的时间分布。
-XX:+PrintTenuringDistribution
# 打印新生代对象晋升到老年代的分布情况,显示不同年龄(Tenuring)阶段的对象数量,有助于分析对象生命周期和内存分配情况。
-XX:+PrintGCApplicationStoppedTime
# 记录应用程序因GC暂停的时间,帮助评估GC对应用程序响应时间的影响。
-Xloggc:logs/gc.log
# 将GC日志输出到指定的文件路径(此处为logs/gc.log),方便后续分析和存档。
-XX:+UseGCLogFileRotation
# 启用GC日志文件的轮转机制,当日志文件达到一定大小时自动创建新的日志文件,防止单个日志文件过大。
-XX:NumberOfGCLogFiles=32
# 设置GC日志文件轮转的数量上限,此处为32个文件。超过此数量时,旧的日志文件会被覆盖或删除。
-XX:GCLogFileSize=64m
# 设置单个GC日志文件的最大大小,此处为64MB。当日志文件大小达到此限制时,触发轮转机制,创建新的日志文件。
使用 jps 命令列出所有 Java 进程及其 PID,并获取JVM堆内存分配快照
jps -l #使用 jps 命令列出所有 Java 进程及其 PID
jmap -heap PID #使用 jmap ,获取 JVM 内存映射信息
Heap Configuration:
MinHeapFreeRatio = 0
# 最小堆空闲比例:垃圾回收后堆中空闲内存的最小百分比。如果低于此比例,JVM 会尝试扩展堆大小。
MaxHeapFreeRatio = 100
# 最大堆空闲比例:垃圾回收后堆中空闲内存的最大百分比。如果超过此比例,JVM 会尝试缩减堆大小。
MaxHeapSize = 1986002944 (1894.0MB)
# 最大堆大小:JVM 可以使用的最大堆内存量。在此例中,最大堆大小为约1.894 GB。
NewSize = 41943040 (40.0MB)
# 新生代初始大小:新生代(Young Generation)的初始内存大小,此处为40 MB。
MaxNewSize = 661651456 (631.0MB)
# 新生代最大大小:新生代可以扩展到的最大内存大小,此处为631 MB。
OldSize = 83886080 (80.0MB)
# 老年代初始大小:老年代(Old Generation)的初始内存大小,此处为80 MB。
NewRatio = 2
# 新生代与老年代的比例:新生代和老年代内存大小的比例关系。这里表示老年代的大小是新生代的2倍。
SurvivorRatio = 8
# Survivor空间与Eden空间的比例:新生代中Survivor空间与Eden空间的比例。这里表示Eden空间的大小是Survivor空间的8倍。
MetaspaceSize = 21807104 (20.796875MB)
# 元空间初始大小:用于存储类元数据(如类的结构信息)的内存空间初始大小,此处为约20.8 MB。
CompressedClassSpaceSize = 1073741824 (1024.0MB)
# 压缩类空间大小:用于存储压缩类元数据的内存空间大小,此处为1 GB。
MaxMetaspaceSize = 17592186044415 MB
# 元空间最大大小:元空间可以扩展到的最大内存大小。此处为极大的数值,实际上没有限制。
G1HeapRegionSize = 0 (0.0MB)
# G1堆区域大小:G1垃圾收集器中堆区域的大小。值为0表示当前未使用G1垃圾收集器。
Heap Usage:
PS Young Generation
Eden Space:
capacity = 91226112 (87.0MB)
# Eden空间容量:新生代中Eden空间的总内存容量,此处为87 MB。
used = 16139768 (15.392082214355469MB)
# Eden空间已用内存:当前Eden空间中已使用的内存量,此处约为15.39 MB。
free = 75086344 (71.60791778564453MB)
# Eden空间空闲内存:当前Eden空间中未使用的内存量,此处约为71.61 MB。
17.692048522247664% used
# Eden空间使用率:Eden空间中已用内存占总容量的百分比,此处约为17.69%。
From Space:
capacity = 11010048 (10.5MB)
# From Survivor空间容量:新生代中From Survivor空间的总内存容量,此处为10.5 MB。
used = 0 (0.0MB)
# From Survivor空间已用内存:当前From Survivor空间中已使用的内存量,此处为0 MB。
free = 11010048 (10.5MB)
# From Survivor空间空闲内存:当前From Survivor空间中未使用的内存量,此处为10.5 MB。
0.0% used
# From Survivor空间使用率:From Survivor空间中已用内存占总容量的百分比,此处为0%。
To Space:
capacity = 11010048 (10.5MB)
# To Survivor空间容量:新生代中To Survivor空间的总内存容量,此处为10.5 MB。
used = 0 (0.0MB)
# To Survivor空间已用内存:当前To Survivor空间中已使用的内存量,此处为0 MB。
free = 11010048 (10.5MB)
# To Survivor空间空闲内存:当前To Survivor空间中未使用的内存量,此处为10.5 MB。
0.0% used
# To Survivor空间使用率:To Survivor空间中已用内存占总容量的百分比,此处为0%。
PS Old Generation
capacity = 70254592 (67.0MB)
# 老年代容量:老年代的总内存容量,此处为67 MB。
used = 14114744 (13.460868835449219MB)
# 老年代已用内存:当前老年代中已使用的内存量,此处约为13.46 MB。
free = 56139848 (53.53913116455078MB)
# 老年代空闲内存:当前老年代中未使用的内存量,此处约为53.54 MB。
20.090849008133162% used
# 老年代使用率:老年代中已用内存占总容量的百分比,此处约为20.09%。
1.5. 常用调整JVM内存分配的参数
-XX:NewRatio = 2 (默认) # 调整年轻代和老年代的比例
-XX:SurvivorRatio = 8 (默认) # 调整Eden 和 To Survivor、From Survivor 的比例
-XX:+UseAdaptiveSizePolicy (默认) # JVM 将会动态调整 Java 堆中各个区域的大小以及进
-Xms: # 堆初始大小
-Xmx: # 堆最大值。
-XX:+UseSerialGC #年轻代和老年代都用串行收集器
-XX:+UseParallelGC #年轻代使用 ParallerGC,老年代使用 Serial Old
-XX:+UseParallelOldGC #新生代和老年代都使用并行收集器
-XX:+UseG1GC #使用 G1 垃圾回收器
-XX:+UseZGC #使用 ZGC 垃圾回收器
-Xss #设置每个虚拟机栈的大小
2. CPU 100% 问题怎么排查?
2.1. 首先通过 top 命令找到找到占用cpu最高到java进程
top
2.2. 找到占用cpu最高的线程
top -Hp PID
2.3. 保存线程栈信息
jstack PID > thread_stack.log
jstack 8547 > home/weiwudi/thread_stack.log
最后将找到的占用cpu最高的线程转换为16进制在线程栈快照中找到对应的信息。
3. 如何定位、修复死锁?
当死锁发生时我们可以先用jps获取进程id
jps -l
获取java进程id后,通过jstack获取java线程栈快照
jstack 27468 > C:/Users/魏攀/Desktop/thread_stack.log
分析线程栈信息,找到死锁位置。
好的本期内容就讲到这里,如果读者们在阅读中有什么不同地意见欢迎到评论区进行指正哈~