(9)巨型对象
巨型对象指大小大于region的一半的对象,在jdk8u60后,可以回收巨型对象。
巨型对象有如下特别之处。
G1不会复制巨型对象
垃圾回收时优先回收
当某个巨型对象被老年代的incoming引用为0时,将会在新生代垃圾回收时被回收(参考下图)。
(10)动态调整阈值
并发标记必须在堆占满前完成,否则将退化为full gc(注:在新版本的jvm中,full gc已经不是前文所提到的单线程,但是仍然有很长的STW时间,需要避免)。在jdk9之前我们采用-XX:InitiatingHeapOccupancyPercent来设置开始并发标记的阈值,但是阈值如果设置过低则频繁GC,如果设置过高则易Full GC。jdk9中可以对于阈值进行动态调整。
jdk中还有很多对于垃圾回收器的改进。建议多读官网文档:Java Platform, Standard Edition HotSpot Virtual Machine Garbage Collection Tuning Guide, Release 16 (oracle.com)。
6.9 GC调优
6.9.1 预备知识
(1) 常用命令
jvm调优需要对于一些常用的内存设置参数熟悉,可以查阅oracle官网。或使用命令java -XX:+PrintFlagsFinal -version | findstr "GC"查看jvm中与GC相关的虚拟机参数。
查看的结果示例如下。
java version "16.0.2" 2021-07-20 Java(TM) SE Runtime Environment (build 16.0.2+7-67) Java HotSpot(TM) 64-Bit Server VM (build 16.0.2+7-67, mixed mode, sharing) uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product} {default} uint ConcGCThreads = 3 {product} {ergonomic} bool DisableExplicitGC = false {product} {default} bool ExplicitGCInvokesConcurrent = false {product} {default} uintx G1MixedGCCountTarget = 8 {product} {default} uintx G1PeriodicGCInterval = 0 {manageable} {default} bool G1PeriodicGCInvokesConcurrent = true {product} {default} double G1PeriodicGCSystemLoadThreshold = 0.000000 {manageable} {default} uintx GCDrainStackTargetSize = 64 {product} {ergonomic} uintx GCHeapFreeLimit = 2 {product} {default} uintx GCLockerEdenExpansionPercent = 5 {product} {default} uintx GCPauseIntervalMillis = 201 {product} {default} uintx GCTimeLimit = 98 {product} {default} uintx GCTimeRatio = 12 {product} {default} bool HeapDumpAfterFullGC = false {manageable} {default} bool HeapDumpBeforeFullGC = false {manageable} {default} size_t HeapSizePerGCThread = 43620760 {product} {default} uintx MaxGCMinorPauseMillis = 18446744073709551615 {product} {default} uintx MaxGCPauseMillis = 200 {product} {default} int ParGCArrayScanChunk = 50 {product} {default} uintx ParallelGCBufferWastePct = 10 {product} {default} uint ParallelGCThreads = 13 {product} {default} bool PrintGC = false {product} {default} bool PrintGCDetails = false {product} {default} bool ScavengeBeforeFullGC = false {product} {default} bool UseAdaptiveSizeDecayMajorGCCost = true {product} {default} bool UseAdaptiveSizePolicyWithSystemGC = false {product} {default} bool UseDynamicNumberOfGCThreads = true {product} {default} bool UseG1GC = true {product} {ergonomic} bool UseGCOverheadLimit = true {product} {default} bool UseMaximumCompactionOnSystemGC = true {product} {default} bool UseParallelGC = false {product} {default} bool UseSerialGC = false {product} {default} bool UseShenandoahGC = false {product} {default} bool UseZGC = false {product} {default}
(2) 掌握常用工具
(3) 调优与代码、平台相关,无万能范式。
6.9.2 调优内容
(1)调优领域
内存
锁竞争
cpu占用
io
(2) 调优目标
高吞吐量(科学运算):ParrellelGC
还是低延迟(互联网项目):CMS、G1、ZGC
hotspot外的虚拟机:zing…
6.9.3 代码复核
查看full gc前后的内存占用,考虑以下几个问题。
数据量是不是太多?下面代码就会加载大量数据到堆内存中。
resultSet = statement.executeQuery("select * from xxx"); 1
应该改为:
resultSet = statement.executeQuery("select * from xxx limit n"); 1
数据表示太臃肿?
对象用到什么数据项查什么数据项
对象大小 Integer24byte ,int24byte
是否存在内存泄漏?如:
static Map map = new HashMap(); 1
对长期存活的对象建议使用弱引用、软引用。
对于缓存类型的数据建议使用第三方缓存实现,如redis。
6.9.4 新生区内存调优
(1) 新生代的特点
对象分配极其廉价:使用TLAB,即Thread Local allocation buffer(参考:浅析java中的TLAB - 简书 (jianshu.com)),避免了线程竞争,提高了内存分配的效率。
对象的销毁代价小:采用复制算法整理内存,对于垃圾对象销毁代价小。
大部分对象朝生夕死,minor gc时间远低于full gc
由于新生代具有以上特点,对于新生代进行内存调优效果更明显,往往进行内存调优时先考虑新生代的内存调优。
(2) 设置新生代内存大小
参数-Xmn可以设置新生代大小。如果新生代设置过小,将会导致minor gc频繁发生,耗费stw时间。如果新生代设置过大,或将导致只发生Full GC,占用的时间同样会很高,Oracle官方推荐设置为25%-50%。根据经验,我们一般将新生代的内存空间设置为:所容纳的最大并发量 * 一次请求响应的数据量。这样一次请求响应完成后大部分的内存将可以被释放,可以有效的减少GC的触发次数。
6.9.5 幸存区调优
(1) 设置幸存区大小
幸存区要至少能够存放当前活跃(将被回收)的对象+即将晋升(不被回收)的对象,如果幸存区的对象容纳不下,当前活跃的对象可能会被晋升到老年代,这就使一个本来拥有较短生命周期的对象在Full GC时才会被垃圾回收。
(2)设置合理晋升阈值
通过参数``-XX:+PrintTenuringDistribution可以打印各个年龄的对象在内存中的占用,-XX:+MaxTenuringThreshold=threshold`调整晋升阈值。如果幸存区的晋升阈值设置过大,则需要晋升到老年代的对象可能不会被及时晋升。而新生代进行Minor GC时耗费时间主要发生在复制对象上,这就会导致STW时间变长。
6.9.6 老年代调优
以CMS为例。
CMS的老年代的内存要尽可能大,避免浮动垃圾又导致内存溢出,使老年代退化。一般先进行新生代调优,有必要再考虑老年代调优。如果没有发生Full GC,一般无需对老年代进行调优,如果发生了Full GC,可以观察发生Full GC时老年代的内存占用超过的阈值,将老年代内存大小调大1/41/3.另外也可以用`-XX:+CMSInitiatingOccupyFraction=percent`设置老年代垃圾回收的时机,一般推荐设置为内存占用75%80%时。
6.9.7 调优案例
Full GC和Minor GC特别频繁
考虑新生代内存设置过小,通过增加新生代内存大小,避免频繁触发Minor GC,以及将生命周期较短的对象带入老年代进而引发Full GC。
请求高峰期发生Full GC,单次占用时间特别长(CMS)
查看GC日志,查看到底时CMS各阶段耗费时间。CMS再重新标记时耗时最多,根据日志发现有1s。由于在重新标记阶段,不仅会扫描老年代的对象,还会扫描新生代的对象,并根据根可达算法进行扫描,考虑在业务高峰器,新生代对象较多。可以将CMS重新标记前先将新生代内存进行一次整理。
在这里插入图片描述
老年代充足情况发生Full GC(CMS,jdk1.7)
查看输出提示信息并无并发失败、晋升失败等,说明老年代空间充裕,确认jdk版本为1.7,非jdk1.8。在jdk1.7及以前,方法区由元空间来管理,考虑元空间不足导致Full GC。