主要讲述JVM内存结构,以及线上简单的调优场景。
JVM系列应该属于Java高阶的内容,本文主要是对学习的知识做个记录,然后再记录分析问题的过程。
刚转到小米人事部门,发现线上的2台机器一直在GC,而且频率非常高,下面是GC的日志:
如果需要看懂这个GC日志,下面的内容就一定需要掌握。
通常,判断一个对象是否被销毁有两种方法:
就像上图的那样,绿色部分的对象都在GC Roots的引用链上,就不会被垃圾回收器回收,灰色部分的对象没有在引用链上,自然就被判定为可回收对象。
那么,问题来了,这个GC Roots又是什么?下面列举可以作为GC Roots的对象:
见名知义,标记--清除算法就是对无效的对象进行标记,然后清除。
对于标记--清除算法,你一定会清楚看到,在进行垃圾回收之后,堆空间有大量的碎片,出现了不规整的情况。在给大对象分配内存的时候,由于无法找到足够的连续的内存空间,就不得不再一次触发垃圾收集。另外,如果Java堆中存在大量的垃圾对象,那么垃圾回收的就必然进行大量的标记和清除动作,这个势必造成回收效率的降低。
标记--复制算法就是把Java堆分成两块,每次垃圾回收时只使用其中一块,然后把存活的对象全部移动到另一块区域。
标记--复制算法有一个很明显的缺点,那就是每次只使用堆空间的一半,造成了Java堆空间使用率的的下降。
标记--整理算法算是一种折中的垃圾收集算法,在对象标记的过程,和前面两个执行的是一样步骤。但是,进行标记之后,存活的对象会移动到堆的一端,然后直接清理存活对象以外的区域就可以了。这样,既避免了内存碎片,也不存在堆空间浪费的说法了。但是,每次进行垃圾回收的时候,都要暂停所有的用户线程,特别是对老年代的对象回收,则需要更长的回收时间,这对用户体验是非常不好的。
HotSpot VM中的垃圾回收器,以及适用场景:
内存模型几个重要点:
通常情况下,初次被创建的对象存放在新生代的Eden区,当第一次触发Minor GC,Eden区存活的对象被转移到Survivor区的某一块区域。以后再次触发Minor GC的时候,Eden区的对象连同一块Survivor区的对象一起,被转移到了另一块Survivor区。可以看到,这两块Survivor区我们每一次只使用其中的一块,这样也仅仅是浪费了一块Survivor区。
需要注意的2点:
老年代是存储长期存活的对象的,占满时就会触发我们最常听说的Full GC,期间会停止所有线程等待GC的完成。所以对于响应要求高的应用应该尽量去减少发生Full GC从而避免响应超时的问题。
需要注意的几点:
无论是Minor GC,还是Full GC,都会产生停顿现象,即Stop-The-World。Minor GC停顿时间较短,而Full GC耗时较长将导致长时间停顿、系统无响应,极大影响系统的性能。因此,Full GC日志的监控和性能分析在性能优化中极为重要。
偷个懒,直接贴网上的内容:
Minor GC日志:
Full GC日志:
其实还有一些打印及CMS方面的参数,这里就不以一一列举了。
线上机器配置:
内存是16G cpu 4核
再回到我们刚开始的截图:
通过分析和计算,可以得到如下数据:
得出如下结论:
我们再看一下线上的配置:
通过该配置再验证刚才的计算结果:
SurvivorRatio计算公式可见:https://blog.csdn.net/flyfhj/article/details/86630105
需要优化的点:
优化后的配置:
优化后的线上日志:
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内存大小应该还可以继续扩大,目前需要在线上观察一段时间,然后再研究一下,如何进一步优化。
“Java 面试题指南”经历接近一年的迭代打磨,目前已经提供了小程序刷题、PC 端访问(https://java.ecool.fun/)。截至 2022 年 2 月 28 日,已经录入 Java 常见面试 800+ 题,想刷 Java 面试题的小伙伴千万不要错过。我们在近期推出了简历指导、模拟面试等付费功能,有想了解的小伙伴们可以添加小助手微信(interview-java)进行咨询哦~