一 jvm优化
1.1 优化实施步骤*
1)减少使用全局变量和大对象;
2)调整新生代的大小到最合适;
3)设置老年代的大小为最合适;
4)选择合适的GC收集器;
1.2 关于GC优化原则
多数的Java应用不需要在服务器上进行GC优化;
多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;
在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合);
减少创建对象的数量;
减少使用全局变量和大对象;
GC优化是到最后不得已才采用的手段;
在实际使用中,分析GC情况优化代码比优化GC参数要多得多;
1.3 GC优化目的
将转移到老年代的对象数量降低到最小;
减少full GC的执行时间;
B)一般如果达到以下的指标,就不需要进行GC了。
Minor GC执行时间不到50ms,Minor GC执行不频繁,约10秒一次;
Full GC执行时间不到1s,Full GC执行频率不算频繁,不低于10分钟1次(>=10min/次);
https://www.cnblogs.com/myseries/p/12084444.html
visualvm工具 Visual GC分析例子 (二)-CSDN博客
1.4 G1日志解析
1.G1日志中断
2.G1的日志
2024-08-07T19:05:30.973+0800: [GC pause (G1 Evacuation Pause) (young) 83M->78M(100M), 0.0010787 secs]
2024-08-07T19:05:30.989+0800: [GC pause (G1 Evacuation Pause) (young) (initial-mark) 85M->82M(100M), 0.0012699 secs]
2024-08-07T19:05:30.990+0800: [GC concurrent-root-region-scan-start]
2024-08-07T19:05:30.990+0800: [GC concurrent-root-region-scan-end, 0.0000719 secs]
2024-08-07T19:05:30.990+0800: [GC concurrent-mark-start]
2024-08-07T19:05:30.991+0800: [GC concurrent-mark-end, 0.0007155 secs]
2024-08-07T19:05:30.991+0800: [GC remark, 0.0007150 secs]
2024-08-07T19:05:30.992+0800: [GC cleanup 84M->84M(100M), 0.0003412 secs]
2024-08-07T19:05:31.004+0800: [GC pause (G1 Evacuation Pause) (young) 86M->84M(100M), 0.0012786 secs]
2024-08-07T19:05:31.005+0800: [GC pause (G1 Evacuation Pause) (mixed) 88M->81M(100M), 0.0010569 secs]
2024-08-07T19:05:31.034+0800: [GC pause (G1 Evacuation Pause) (mixed)-- 89M->91M(100M), 0.0017680 secs]
2024-08-07T19:05:31.050+0800: [GC pause (G1 Evacuation Pause) (mixed)-- 99M->100M(100M), 0.0011720 secs]
2024-08-07T19:05:31.051+0800: [Full GC (Allocation Failure) 100M->59M(100M), 0.0096012 secs]
3.解析
[GC pause (G1 Evacuation Pause) (young) 83M->78M(100M), 0.0010787 secs]
GC pause (G1 Evacuation Pause) (young) 表示 G1 收集器在年轻代进行正常垃圾收集时发生的暂停。每一次 YGC 回收整个新生代分区。
83M->78M(100M) 表示垃圾收集前后,年轻代已使用内存从 83M 降低到 78M,年轻代总内存 100M。
0.0010787 secs 表示这次 YGC 暂停持续了大约 1.0787 毫秒。
[GC pause (G1 Evacuation Pause) (young) (initial-mark) 85M->82M(100M), 0.0012699 secs]
这是 G1 收集器同时进行年轻代垃圾收集 (YGC) 和初始标记的暂停。(初始标记在 YGC 的同时完成)
年轻代已使用内存从 85M 降低到 82M,年轻代总内存 100M;耗时大约 1.2699 毫秒。
初始标记是 G1 收集器 GC 流程的一个重要步骤,用于快速找到标记根对象。
[GC concurrent-root-region-scan-start]
这表示 G1 收集器开始并发扫描根区域。
并发根区域扫描可以在应用程序运行的同时快速找出哪些区域包含了根对象。
[GC concurrent-root-region-scan-end, 0.0000719 secs]
这表示 G1 收集器的并发根区域扫描操作已经结束。
这个并发扫描根区域操作总共持续了 0.0719 毫秒。
[GC concurrent-mark-start]
这表示 G1 收集器开始并发标记阶段。
[GC concurrent-mark-end, 0.0007155 secs]
这表示 G1 收集器的并发标记操作已经结束。
这个并发标记操作总共持续了 0.7155 毫秒。
[GC remark, 0.0007150 secs]
这表示 G1 收集器进入了 Remark(重新标记)阶段,这个阶段持续了 0.7150 毫秒。
[GC cleanup 84M->84M(100M), 0.0003412 secs]
这表示 G1 收集器进入了 Cleanup 清理阶段的暂停。
该阶段主要作用:统计存活对象、交换标记位图、重置RSet、把空闲分区放到空闲分区列表中。
清理阶段并不会清理垃圾对象,也不会执行存活对象的拷贝,所以内存几乎不会变化。
堆已用内存从 84M 降为 84M,总内存为 100M,整个清理阶段持续了 0.3412 毫秒。
[GC pause (G1 Evacuation Pause) (mixed) 88M->81M(100M), 0.0010569 secs]
这是 G1 收集器在进行混合(年轻代和老年代)垃圾收集时发生的暂停。
堆已用内存从 88M 降为 81M,总堆内存为 100M,整个 Mixed GC 持续了 1.0569 毫秒。
[Full GC (Allocation Failure) 100M->59M(100M), 0.0096012 secs]
这是一次完整的垃圾收集的暂停,发生在无法在年轻代和老年代找到足够内存分配新对象时。
导致这次 Full GC 的原因是发生了内存分配失败(Allocation Failure)。
在这个 Full GC 中,G1 会回收整个堆内存,从 100MB 压缩到 59MB,整个 Full GC 操作持续了约 9.6 毫秒。
二 堆空间优化案例
2.1 代码
2.2 设置启动参数配置
jdk8:
-XX:+PrintGCDetails -XX:MetaspaceSize=64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heap/heapdump.hprof -XX:+PrintGCDateStamps -Xms200M -Xmx200M -Xloggc:log/gc-oomHeap.log
jdk17:
-XX:+PrintGCDetails -XX:MetaspaceSize=64m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=e:/heapdump.hprof -Xlog:gc* -Xms200M -Xmx200M -Xlog:gc:e:/gc-oomHeap.log
这里使用jdk17的配置,如下图所示:
2.3 触发模拟java heap space
1.服务启动
2.进行访问
3.查看console
2.4 通过visul vm分析排查
2.4.1 实时在线分析
1.基本信息
2.堆变化
2.4.2.离线dump分析
1)选择dump文件
2. 查看报oom异常的具体位置
3.通过jvisualvm工具查看,占用最多实例的类是哪个,这样就可以定位到我们的问题所在。
太多的people对象。
2.4.3 通过log日常查看
1.通过日志查看
Full GC产生的根本原因是老年代堆空间被大量占用而得不到及时释放,在GC监控日志中会出现字段Pause Full(G1 Compaction Pause)。
2.4.4 原因以及解决方案*
1、代码中可能存在大对象分配
2、可能存在内存泄漏,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象
解决方案:
1、检查是否存在大对象的分配,最有可能的是大数组分配
2、通过jmap命令,把堆内存dump下来,使用MAT、visualvm等工具分析一下,检查是否存在内存泄漏的问题
3、如果没有找到明显的内存泄漏,使用 -Xmx 加大堆内存