内存监控
参考:《JVM学习-内存监控(五)》
GC 性能衡量指标
吞吐量
这里的吞吐量是指应用程序所花费的时间和系统总运行时间的比值。我们可以按照这个公式来计算 GC 的吞吐量:系统总运行时间 = 应用程序耗时 +GC 耗时。如果系统运行了 100 分,GC 耗时 1 分钟,则系统吞吐量为 99%。GC 的吞吐量一般不能低于 95%。
停顿时间
指垃圾收集器正在运行时,应用程序的暂停时间。对于串行回收器而言,停顿时间可能会比较长;而使用并发回收器,由于垃圾收集器和应用程序交替运行,程序的停顿时间就会变短,但其效率很可能不如独占垃圾收集器,系统的吞吐量也很可能会降低
垃圾回收频率
多久发生一次指垃圾回收呢?通常垃圾回收的频率越低越好,增大堆内存空间可以有效降低垃圾回收发生的频率,但同时也意味着堆积的回收对象越多,最终也会增加回收时的停顿时间。所以我们只要适当地增大堆内存空间,保证正常的垃圾回收频率即可。
GC 调优策略
1. 降低 Minor GC 频率
通常情况下,由于新生代空间较小,Eden 区很快被填满,就会导致频繁 Minor GC,因此我们可以通过增大新生代空间来降低 Minor GC 的频率。
但是可能有这样的以为,增加Eden区的大小,可以降低Minor GC的次数但是会增加每次Minor的时间
通常Minor的回收步骤是:T1回收Eden区垃圾对象 T2复制存活对象
例如:一个对象的存活时间500ms minorGC间隔为300ms 那么这个对象将被复制
如果增加了eden区大小 minorGC 间隔到500ms 那么这个对象在eden区就会被回收 而不会被复制
复制消耗时间远远大于扫描时间 所以可以适量增加eden区大小
2.降低 Full GC 的频率
由于堆内存空间不足或老年代对象太多,会触发 Full GC,频繁的 Full GC 会带来上下文切换,增加系统的性能开销。我们可以使用哪些方法来降低 Full GC 的频率呢?
1.减少创建大对象
在平常的业务场景中,我们习惯一次性从数据库中查询出一个大对象用于 web 端显示。例如,我之前碰到过一个一次性查询出 60 个字段的业务操作,这种大对象如果超过年轻代最大对象阈值,会被直接创建在老年代;即使被创建在了年轻代,由于年轻代的内存空间有限,通过 Minor GC 之后也会进入到老年代。这种大对象很容易产生较多的 Full GC。我们可以将这种大对象拆解出来,首次只查询一些比较重要的字段,如果还需要其它字段辅助查看,再通过第二次查询显示剩余的字段。
2.增大堆内存空间
在堆内存不足的情况下,增大堆内存空间,且设置初始化堆内存为最大堆内存,也可以降低 Full GC 的频率。
3.选择合适的 GC 回收器
假设我们有这样一个需求,要求每次操作的响应时间必须在 500ms 以内。这个时候我们一般会选择响应速度较快的 GC 回收器,CMS(Concurrent Mark Sweep)回收器和 G1 回收器都是不错的选择。而当我们的需求对系统吞吐量有要求时,就可以选择 Parallel Scavenge 回收器来提高系统的吞吐量。
对象在堆中的生命周期
具体详见:《内存模型》
1.new 创建一个对象会优先分配到新生代的eden区 这时虚拟机会给对象定义一个对象年龄计数器(通过参数 -XX:MaxTenuringThreshold 设置)
2.当eden区空间不足会触发一次minor GC 这时候存活对象转移到Survivor 同时年龄计数器+1,在 Survivor 中同样也会经历 MinorGC,每经过一次 MinorGC,对象的年龄将会 +1。
当然了,内存空间也是有设置阈值的,可以通过参数 -XX:PetenureSizeThreshold 设置直接被分配到老年代的最大对象,这时如果分配的对象超过了设置的阀值,对象就会直接被分配到老年代,这样做的好处就是可以减少新生代的垃圾回收。
查看jvm默认配置
其中InitialHeapSize为最开始的堆的大小,MaxHeapSize为堆的最大值。
java -XX:+PrintFlagsFinal -version | grep HeapSize
查看程序jvm配置
具体调优方法
1.开启jvm gc日志 程序运行一段时间后 导入工具分析
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:/home/ewei/dump/gc_helpcenter.log
工具1.使用gcviewer分析日志:参考<<JVM学习-内存监控(五) GCviewer>>
工具2.使用gceasy https://gceasy.ycrash.cn/gc-index.jsp
1.观察吞吐量 和gc停顿的时间分布
大于99%正常
2.观察full gc回收频率
这里看到大量的full gc 肯定不正常,可以提高整堆大小,或者调整比例 提高老年代大小
如优化后
3.观察最大回收时间和平均回收时间
4.观察峰值,主要看young代,比如我们设置的是1:1:1 按照form to区一个区域不可用 750-225 峰值应该到525 但是因为from和to区太大,导致eden变小,频繁满了 导致young gc 可以减小年轻代大小
5.切换为中文版参考建议
合适的堆空间和比例
堆空间太小,对应老年代也会变小,导致老年代迅速变满,频繁触发full gc
堆空间太大,对应老年代也会变大,导致老年代需要很长时间才会变满,但是因为空间太大,垃圾回收时间也会变长,全局停顿时间增加
所以应该选择一个合适的值
堆空间太大频繁的full gc 有2种情况
1.每次full gc后空闲内存很低,一般这种是有内存泄露需要排查
2.如果回收后空闲内存很高,或者大量的大对象,因为高并发,导致大量对象在年轻代,因为年轻代太小 没有足够空间 被托管到了老年代
可以通过jstat 观察full gc 后回收量 和回收后空闲空间大小
合适的Eden Survivor比例
jstat -gc 1
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
11776.0 12288.0 0.0 936.9 230400.0 23149.1 512000.0 444459.2 169216.0 158119.2 14592.0 12614.8 7015 63.953 10 6.341 70.294
如以下 每次回收后 S0U或者S1U的峰值都比分配的11776.0小很多,则可以降低Survivor的大小.避免浪费