内存模型
对内存模型
内存模型几个重要点:
- JVM内存会划分为堆内存和非堆内存,堆内存中也会划分为年轻代和老年代,而非堆内存则为永久代。
- 新生代Young和老年代Old默认占比是1:3。
- 年轻代又会分为Eden和Survivor区,Survivor也会分为FromPlace和ToPlace,Eden、FromPlace和ToPlace的默认占比为 8:1:1。
GC类型
- Minor GC/Young GC:针对新生代的垃圾收集;
- Major GC/Old GC:针对老年代的垃圾收集。
- Full GC:针对整个Java堆以及方法区的垃圾收集。
Minor GC工作原理
通常情况下,初次被创建的对象存放在新生代的Eden区,当第一次触发Minor GC,Eden区存活的对象被转移到Survivor区的某一块区域。以后再次触发Minor GC的时候,Eden区的对象连同一块Survivor区的对象一起,被转移到了另一块Survivor区。可以看到,这两块Survivor区我们每一次只使用其中的一块,这样也仅仅是浪费了一块Survivor区。
需要注意的2点:
- 每经历过一次垃圾回收的对象,它的分代年龄就加1,当分代年龄达到15以后,就直接被存放到老年代中。
- 给大对象分配内存的时候,Eden区已经没有足够的内存空间了,大对象就会直接进入老年代。
Full GC工作原理
老年代是存储长期存活的对象的,占满时就会触发我们最常听说的Full GC,期间会停止所有线程等待GC的完成。所以对于响应要求高的应用应该尽量去减少发生Full GC从而避免响应超时的问题。
需要注意的几点:
- Full GC耗时较长,发生次数远没有Minor GC频繁,太频繁意味着性能出现问题。
- 标记-清除算法会产生大量内存碎片,以后如果需要为大对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次GC回收操作。
无论是Minor GC,还是Full GC,都会产生停顿现象,即Stop-The-World。Minor GC停顿时间较短,而Full GC耗时较长将导致长时间停顿、系统无响应,极大影响系统的性能。因此,Full GC日志的监控和性能分析在性能优化中极为重要。
GC日志
GC日志开启
偷个懒,直接贴网上的内容:
理解GC日志
Minor GC日志:
Full GC日志:
JVM的常用参数
其实还有一些打印及CMS方面的参数,这里就不以一一列举了。
GC日志分析与优化
线上机器配置:
- 内存是16G
- cpu 4核
优化前
再回到我们刚开始的截图:
通过分析和计算,可以得到如下数据:
- 老生代:5870976/(1024*1024) = 5.6G
- 新生代:546176/1024 = 533M
- Eden:273152/1024 = 266M
- From:273024/1024 = 266M
- To:273024/1024 = 266M
得出如下结论:
- 新生代+老生代 = 5.6 + 533/1024 = 6.1G
- 新生代:老生代 = 533 : (5.6*1024) = 1 : 10.7
- Edem:From:To = 1:1:1
我们再看一下线上的配置:
通过该配置再验证刚才的计算结果:
- “-Xmx6000M -Xms6000M”,可以确定JVM内存大小为6000/1024=5.8G,之前计算的堆内存大小为6.1G,基本匹配(多余的可能分配给了永生代)
- “ -Xmn800M”,可以确定新生代是800M,Edem+From+To为798M,基本匹配(为什么新生代533M和“Edem+From+To”798M有出入呢?)
- “XX:SurvivorRatio=1”,这里有一个计算公式,大家可以自己百度一下,通过公式得到的结论是Edem:From:To = 1:1:1,和我们的计算结果完全匹配。
SurvivorRatio计算公式可见:https://blog.csdn.net/flyfhj/article/details/86630105
优化后
需要优化的点:
- 目前内存使用不到一半,需要调整JVM内存大小;
- Edem的内存太小,只有266M,这个是频繁Minor GC的主要原因,需要扩大改值;
- 新生代:老生代的比值,需要从之前的1 : 10.7,调整到1:2
- 新生代的Edem:From:To比值,需要从之前的1:1:1,调整到8:1:1
优化后的配置:
优化后的线上日志:
Heap before GC invocations=3 (full 1): par new generation total 2764800K, used 2524705K [0x00000005cc000000, 0x0000000687800000, 0x0000000687800000) eden space 2457600K, 100% used [0x00000005cc000000, 0x0000000662000000, 0x0000000662000000) from space 307200K, 21% used [0x0000000674c00000, 0x0000000678d885c0, 0x0000000687800000) to space 307200K, 0% used [0x0000000662000000, 0x0000000662000000, 0x0000000674c00000) concurrent mark-sweep generation total 5120000K, used 15613K [0x0000000687800000, 0x00000007c0000000, 0x00000007c0000000) Metaspace used 62116K, capacity 62680K, committed 63288K, reserved 1105920K class space used 6639K, capacity 6781K, committed 6816K, reserved 1048576K 35.225: [GC (Allocation Failure) 35.225: [ParNew: 2524705K->184767K(2764800K), 0.2682475 secs] 2540319K->200381K(7884800K), 0.2683305 secs] [Times: user=1.05 sys=0.00, real=0.27 secs]
优化后的结果:
- JVM内存大小为10000M,约9.7G
- Edem的内存大小为2.6G,扩到原来的10倍
- 新生代:老生代的比值为1:2
- Edem:From:To的比值为8:1:1
目前这个方式应该还不是最优,因为JVM内存大小应该还可以继续扩大,目前需要在线上观察一段时间,然后再研究一下,如何进一步优化。