JVM调优及故障排查

145 阅读10分钟

各位读者大家好呀!今天小卡给大家带来的是JVM调优及故障排查~

image.png

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

分析线程栈信息,找到死锁位置。

好的本期内容就讲到这里,如果读者们在阅读中有什么不同地意见欢迎到评论区进行指正哈~