起因
生产的某个接口偶尔会出现响应时间超出预期,初步怀疑是full gc导致的,于是尝试在压测环境复现,顺便也测试一下接口的tps。
硬件环境与jvm配置
生产环境使用的是4c8g的机器,使用jdk8默认的垃圾回收器ps marksweep(serial old)和ps scanvenge。
尝试一.启动参数中增加-XX:+UseG1GC,改为使用g1垃圾回收
现象:启动过程中出现full gc,压测过程中未出现full gc,每笔请求的响应时间相比默认垃圾回收器慢了2-3ms。使用promethus和grafana观察了jvm的表现后,发现当前项目对于老年代的利用率极低,绝大多数对象都是使用后立即销毁,压测过程中所有的gc都在年轻代。
结论:启动时触发full gc根据gc日志以及grafana可以发现是元空间扩容导致的,启动参数中加了-XX:MetaspaceSize设置了初始元空间大小后问题解决。g1的垃圾回收策略避免了对象进入老年代,但是代价是降低了吞吐量。考虑到绝大多数情况下增加2-3ms的响应时长都可以接受,因此直接使用g1是更好的选择,降低了jvm调优的难度
尝试二.改回默认配置
现象:每笔请求响应相比g1有提升,年轻代频繁gc,老年代缓慢增加,最后触发Full GC (Ergonomics)。
Full GC (Ergonomics)出现的原因是在jdk8默认垃圾回收下,在Eden区满了,触发年轻代回收时,当前新生代的总空间大于老年代的连续可用空间。
尝试三.默认垃圾回收器,增大年轻代
既然应用基本不使用老年代,所有的gc都在年轻代发生,不妨扩大年轻代,降低年轻代gc的频率,同时也尽可能地让对象都在年轻代被回收,避免进入老年代。通过增加-Xmn,将年轻代与老年代的比例改成差不多3:1,同时设置初始元空间大小。尝试后发现性能表现有略微提升,[Full GC (Ergonomics)频率降低。
使用到的启动参数记录
-Xmn5120m:年轻代大小设置
-XX:MetaspaceSize=128m :元空间初始值设置
-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps :打 印gc详细信息以及时间
-Xloggc:/opt/tomcat8/logs/gc.log: gc日志地址
g1特点和优势
高可扩展性:G1GC能够很好地扩展到多核处理器上,利用多线程进行垃圾回收,从而提高了系统的整体性能。它采用并行与并发的方式执行垃圾回收,使得CPU资源能够得到有效利用。
高吞吐量:G1GC旨在提供高吞吐量,即单位时间内完成的工作量。通过优化内存管理和垃圾回收算法,G1GC能够在不牺牲系统性能的前提下,提高整体的工作输出。
可预测的停顿时间:G1GC允许用户设置期望的最大GC停顿时间,通过精细调整垃圾回收策略,G1GC能够在实际运行中接近这个设定的停顿时间,从而为用户提供了可预测的停顿时间,这对于需要稳定性能的应用来说尤为重要。
良好的内存管理:G1GC采用分代收集的思想,将堆内存划分为多个独立的区域(Regions),这些区域可以独立进行垃圾回收,从而提高了内存管理的灵活性。此外,G1GC还支持Humongous对象,即大于Region大小的对象,这种设计使得G1GC能够更好地适应各种应用场景,提高内存使用效率。
full gc 触发条件记录
-
老年代空间不足
-
元空间不足
-
显式调用System.gc()方法:虽然显式调用System.gc()会请求JVM进行Full GC,但JVM并不保证每次都执行Full GC,除非在某些特定情况下。
-
堆转储生成:在某些情况下,例如生成堆转储以进行内存分析时,JVM会触发Full GC以确保获取准确的堆内存快照。
-
并发回收失败:在使用一些并发垃圾收集器(如CMS)时,如果并发回收过程中未能及时回收足够的空间,可能会触发Full GC作为后备措施。
-
Promotion Failure:在Minor GC时,如果Survivor区放不下对象且对象只能放入老年代,而老年代也放不下时,会发生Promotion Failure,进而触发Full GC。
-
Ergonomice: 在jdk8默认垃圾回收下,在Eden区满了,触发年轻代回收时,如果年轻代平均进入老年代的空间大于老年代的连续可用空间,Full GC (Ergonomics)。