1.调优调的什么
调的大部分都是堆,虽然偶尔也会因为虚拟机栈或元空间导致的OOM异常,但这是极少数故不做讨论
目的:多做YGC,少做Full GC。尽可能的将短命对象在年轻代时就被回收
2.个人常用调优参数
标准: - 开头,所有的hotspot 支持
非标准:-X 开头,特定hotspot支持
不稳定:-XX开头,下个版本可能取消
参数 | 含义 |
---|---|
-Xmx50m | 最大堆小设置为50m (默认系统的1/4) |
-Xms20m | 最小堆小设置为20m (默认操作系统的1/64) |
-Xmn20m | 新生代大小设置为20m |
-Xss1m | 栈内存设置为1m |
-XX:MetaspaceSize=128m | 初始化元空间大小为128m |
-XX:MaxMetaspaceSize=128m | 最大元空间大小 |
-XX:MaxTenuringThreshold=15 | 新生代设置年龄为15。CMS默认为6,其他默认为15 |
-XX:SurvivorRatio=8 | 新生代设置为8:1:1 |
-XX:-UseAdaptiveSizePolicy | 禁用Survivor区自适应策略 |
-XX:TargetSurvivorRatio=80 | Survivor区对象使用率80%,默认是50%,如果超过则将超过某个年龄的对象一起放入老年代 |
-XX:+DisableExplicitGC | 禁止用System.gc(),防止频繁Full GC |
-XX:PretenureSizeThreshold=5242880 | 指定对象超过5M后直接进入老年代。前提是:ParNew + CMS组合 |
–XX:+PrintGC | 打印GC日志 |
-XX:+PrintGCDetails | 打印详细GC日志 |
-XX:+PrintGCDateStamps | 打印发生的GC时间 34.629 |
-XX:+PrintGCDateStamps | 打印发生的GC时间戳 2021-05-14T17:23:06.324+0800 。注如果2个时间一起使用,以当前这个为准 |
-XX:+PrintHeapAtGC | 打印GC前后的详细堆栈信息 |
-XX:PrintFLSStatistics=1 | 打印每次GC前后内存碎片的统计信息 |
-XX:+HeapDumpOnOutOfMemoryError | 当发生OOM时,dump内存文件 |
-XX:HeapDumpPath=D:\workspace\gc-dump.hprof dump | 将dump文件输出到某目录 |
-Xloggc:D:\workspace\fsk-workorder\gc-%t.log | 将GC日志输出到文件中 |
-XX:+UseGCLogFileRotation | 开启GC日志分割 |
-XX:NumberOfGCLogFiles=14 | 最多分割几个文件,超过之后从头文件开始写 |
-XX:GCLogFileSize=100M | 每个文件上限大小,超过就触发分割 |
-XX:+PrintTenuringDistribution | 打印对象分布 |
-XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p | 发生OOM时触发脚本,%p是线程ID |
2.1 针对CMS参数配置
以下内容摘抄《深入理解Java虚拟机》p98.
由于CMS是一款基于 “标记-清除”算法实现的收集器,就意味着有大量内存碎片产生,空间碎片过多时,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很多的剩余空间,但就是无法找到足够大的连续空间来分配对象,而不得不提前触发一次Full GC的情况。为了这决这个问题,CMS收集器提供了一个 -XX:+UseCMSCompactAtFullCollection 开关参数(默认开启,此参数从JDK1.9开始废弃),用于在CMS收集器不得不进行Full GC时开启内存碎片的合并整理过程,由于这个内存整理需要移动对象,在(Shenandoah 和 ZGC出现前)是无法并发的,这样空间碎片文件是解决了,但停顿时间又会变长,因此虚拟机又设置了另外一个参数:-XX:CMSFullGCsBeforeCompaction(JDK1.9后废弃),这个参数作用是要求CMS收集器在执行过若干次(数量由数值绝对)不整理空间的Full GC之后,下一次进行Full GC前会先进行碎片整理(默认值为0,表示每次进入Full GC时都会进行碎片整理)
参数 | 含义 |
---|---|
-XX:+UseConcMarkSweepGC | 新生代使用并行收集器,老年代使用CMS+串行收集器 |
-XX:ParallelCMSThreads | 设定CMS的线程数量 |
-XX:CMSInitiatingOccupancyFraction | 设置CMS收集器在老年代空间被使用多少后触发,默认92%。要是CMS运行期间预留的内存无法满足程序分配新对象的需要,就会出现一次“并发失败 (Concurrent Mode Failure”),这时候虚拟机将不得不启动后备预案:冻结用户线程的执行,临时启用 Serial Old收集器来重新进行老年代的垃圾收集,但这样停顿时间就很长了。所以该参数设置的过高将会容易导致大量的并发失败产生,性能反而降低,用户应在生产环境中根据实际应用情况还权衡配置 |
-XX:+UseCMSCompactAtFullCollection | 设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理,默认开启,JDK1.9后废弃。由于这个内存整理需要移动对象,在(Shenandoah 和 ZGC出现前)是无法并发的,这样空间碎片文件是解决了,但停顿时间又会变长,因此虚拟机又设置了另外一个参数:-XX:CMSFullGCsBeforeCompaction |
-XX:CMSFullGCsBeforeCompaction | JDK1.9后废弃。设定进行多少次CMS垃圾回收后,进行一次内存压缩,默认为0,表示每次进行FULL GC时都会进行碎片处理 |
-XX:+CMSClassUnloadingEnabled | 允许对类元数据进行回收 |
-XX:CMSInitiatingPermOccupancyFraction | 当永久区占用率达到这一百分比时,启动CMS回收 |
-XX:UseCMSInitiatingOccupancyOnly | 表示只在到达阀值的时候,才进行CMS回收 |
个人服务器JVM配置:
-Xmx2048M -Xms2048m -Xmn600m -XX:MetaspaceSize=128m -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:PretenureSizeThreshold=5242880
-XX:CMSInitiatingOccupancyFraction=80 -XX:CMSFullGCsBeforeCompaction=3 -XX:+UseCMSInitiatingOccupancyOnly
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\workspace\fsk-workorder\gc-dump.hprof -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-Xloggc:D:\workspace\fsk-workorder\gc-%t.log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=2 -XX:GCLogFileSize=50M
垃圾收集器组合
参数 | 含义 | 优劣 |
---|---|---|
-XX:+UseSerialGC | Serial + Serial Old: DefNew:新生代,老年代都使用串行回收收集器 | 单核服务器推荐使用 |
-XX:+UseParallelGC -XX:+UseParallelOldGC | PS +PO:新生代,老年代都使用并行回收收集器 | 注重吞吐量 |
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC | ParNew + CMS:新生代使用并行收集器,老年代使用串行回收收集器 | 注重响应时间 |
-XX:+UseG1GC | G1垃圾收集器 |
常用命令
参数 | 含义 |
---|---|
java -XX:+PrintCommandLineFlags | 查看GC信息及采用的垃圾收集器 |
java -XX:+PrintFlagsInitial | 查看ava 环境初始默认值 |
2.模拟OOM
这里结合 JPofiler 分析工具进行JVM分析。JProfiler下载地址
下面代码模拟OOM异常,并设置jvm运行参数
-Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails
//-Xmx10m -Xms10m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails
public class HellocGC {
public static void main(String[] args) {
List cList = new LinkedList();
for (;;) {
byte[] b = new byte[1024 * 1024];
cList.add(b);
}
}
}
程序运行之后:
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->1013K(9728K), 0.0011957 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 1765K->504K(2560K)] 8419K->7381K(9728K), 0.0014081 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Ergonomics) [PSYoungGen: 504K->502K(2560K)] [ParOldGen: 6877K->6813K(7168K)] 7381K->7315K(9728K), [Metaspace: 3240K->3240K(1056768K)], 0.0083864 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 1526K->1526K(2560K)] [ParOldGen: 6813K->6813K(7168K)] 8339K->8339K(9728K), [Metaspace: 3240K->3240K(1056768K)], 0.0070806 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen: 1526K->1514K(2560K)] [ParOldGen: 6813K->6804K(7168K)] 8339K->8319K(9728K), [Metaspace: 3240K->3240K(1056768K)], 0.0095279 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid1972.hprof ...
Heap dump file created [9480656 bytes in 0.015 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at cn.sys.Test.test.HellocGC.main(HellocGC.java:17)
Heap
PSYoungGen total 2560K, used 1661K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 81% used [0x00000000ffd00000,0x00000000ffe9f488,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 6804K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 94% used [0x00000000ff600000,0x00000000ffca5240,0x00000000ffd00000)
Metaspace used 3323K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 358K, capacity 388K, committed 512K, reserved 1048576K
2.1分析GC日志
Minor GC:
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->1013K(9728K), 0.0011957 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
参数 | 含义 |
---|---|
GC | 年轻代中的minor GC |
Allocation Failure | 年轻代中没有足够的区域用于存放需要分配的数据而导致的异常 |
PSYoungGen | 收集器方式:采用PS+PO方式 |
2048K->504K(2560K) | 2048k:GC前新生代占用空间大小; 504K:GC后新生代占用空间大小,这里指Survivor幸存者区,因为Ygc会将Eden区存活的对象复制到Survivir幸存者区,此时Eden区空间大小为0k。2560K:年轻代总容量 |
2048K->1013K(9728K) | 2048k:GC前堆占用空间大小(新生代 + 老年代); 1013K:GC后堆占用空间大小(新生代 + 老年代) 9728K:堆总空间大小(新生代 + 老年代) |
[Times: user=0.00 sys=0.00, real=0.01 secs] | 用户态耗时、内核态耗时、总耗时 |
生产环境GC信息:
[GC [PSYoungGen: 300865K->6577K(310720K)] 392829K->108873K(417472K), 0.0176464 secs] [Times: user=0.06 sys=0.00, real=0.01 secs]
PSYoungGen: 新生代使用多线程垃圾收集器
300865K:GC前整个新生代占用空间大小。当GC后,这里为0kb
6577K: GC后新生代占用空间大小,这里指幸存者区。
310720K:新生代总大小
392829K:GC前堆占用空间大小。新生代 + 老年代
108873K: GC后堆占用空间大小。新生代 + 老年代
417472K:堆空间大小。新生代 + 老年代
GC前后对比:
GC前老年代占用空间大小:392829-300865=91964 约等于老年代占用了堆大小为89M
GC前新生代占用空间大小:300865 新生代大小为:383M
GC后老年代占用空间大小:108873-6577=102296 99M
垃圾:392829 - 108873 = 381946 垃圾为:372M
Full GC
[Full GC (Ergonomics) [PSYoungGen: 1526K->1526K(2560K)] [ParOldGen: 6813K->6813K(7168K)] 8339K->8339K(9728K), [Metaspace: 3240K->3240K(1056768K)], 0.0070806 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
参数 | 含义 |
---|---|
Full GC | 年轻代 + 老年代的中的Major GC |
Ergonomics | 晋升到老生代的平均数据大小大于老生代的剩余内存大小 |
其它 | 其它参数同上 |
汇总GC原因
参数 | 含义 |
---|---|
GC | 年轻代中的minor GC |
Full GC | 年轻代 + 老年代的饿中的major GC 。 指STW |
Allocation Failure | 年轻代中没有足够的区域用于存放需要分配的数据而导致的异常。加大年轻代大小:-xmn400m |
Ergonomics | 晋升到老生代的平均数据大小大于老生代的剩余内存大小。加大年轻代大小:-xmn400m |
promotion failed | DefNew:晋升到老生代的平均数据大小大于老生代的剩余内存大小。加大年轻代大小:-xmn400m |
Metadata GC Threshold | 元空间引发的GC。元空间指向的是本地内存,默认大小为21M 加大初始化元空间大小: -XX:MetaspaceSize=128m |
堆详细日志
Heap
PSYoungGen total 2560K, used 1661K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 81% used [0x00000000ffd00000,0x00000000ffe9f488,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 6804K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 94% used [0x00000000ff600000,0x00000000ffca5240,0x00000000ffd00000)
Metaspace used 3323K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 358K, capacity 388K, committed 512K, reserved 1048576K
参数 | 含义 |
---|---|
PSYoungGen total 2560K, used 1661K | 年轻代中的总大小为2560k、已使用1661k |
eden space 2048K, 81% used | eden区 总大小 2048k、已使用81% |
from(to) space 512K, 0% | 幸存者区 总大小 512k、已使用 0% |
ParOldGen total 7168K, used 6804K | 老轻代中的总大小为7168k、已使用6804k |
object space 7168K, 94% used | |
Metaspace used 3323K, capacity 4496K committed 4864K, reserved 1056768K | 元空间总大小为3323k、已使用4496k |
class space used 358K, capacity 388K, capacity 388K, committed 512K, reserved 1048576K |
3.dump分析
我们已经有了dump文件,那我们应该如何分析它呢,这里采用JProfiler工具打开,可以快速定位问题
由以上信息得出具体出错的位置。
JProfiler + Java VisualVM 工具分析dump
1.场景及jvm配置:
启动一个spring boot项目,并设置 -Xmx100m -Xms100m -Xmn50m -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:HeapDumpPath=D:\workspace\fsk-workorder\gc-dump.hprof
2.代码示例
现在模拟一个OOM,代码如下所示:
3.Java VisualVM监控图:
4.dump文件分析
文章内容皆是自己理解所写,可能会不严谨,如有错误,欢迎指出,谢谢