这是「死磕P7」系列第 003 篇文章,欢迎大家来跟我一起 死磕 100 天,争取在 2025 年来临之际,给自己一个交代。
上两篇介绍了 JVM 内存区域划分,简单记忆一下就可以了,后面再不断深入吧。
死磕P7: JVM内存划分必知必会(一)-CSDN博客
死磕P7: JVM内存划分必知必会(二)-CSDN博客
今天我们开始来了解 JVM 垃圾回收相关的内容。
垃圾回收,最主要的位置就是 堆,JVM 为了垃圾回收的方便,将堆划分为了 2 大区域,分别是 年轻代 和 老年代,另外 1.8 之前还有 永久代,1.8 已经变成了元空间,知道一下就好了。
年轻代
有得文章也把它叫做新生代,年轻代是用来存放新生的对象,一般占据堆的 1/3 空间,由于频繁创建对象,所以新生代会频繁触发 MinorGC 进行垃圾回收。
年轻代又分为 Eden 区、SurvivorFrom、SurvivorTo 三个区,From, To 其实是交替使用的,所以我更喜欢直接叫 s0, s1, 反正你现在只需要记着 年轻代中又分为 3 个区就行,eden, s0, s1,并且他们的默认占比是 8:1:1.
Eden 区
Java 新对象的出生地(如果新创建的对象占用内存很大,则直接分配到老年代,这个大怎么划分的,先别管它)。
当Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。
除了 MinorGC 还有 MajorGC, FullGC:
-
MinorGC,发生在年轻代中的GC,Eden 区内存不够时发生
-
MajorGC,发生在老年代的 GC,出现 MajorGC 前要先进行 MinorGC,所以 MajorGC 一般经常伴有 MinorGC
-
FullGC,字面意思,Full,全部区域的 GC
FullGC 补充(先看 2 遍就好,知道一下,后面会再介绍到),什么时候发生呢?
-
当老年代无法再分配内存的时候;
-
元空间不足的时候;
-
显示调用 System.gc 的时候;
-
在 MinorGC 出现空间分配担保(见最下面的介绍)不足的时候也会发生 FullGC
Eden 区进行 MinorGC 时,将还存活的对象转移到 Survivor 其中一个区,如下图
SurvivorFrom
上面也说了,S0, S1 是交替进行转移的,上一次 GC 的幸存者 obj1 ,作为这一次 GC 的被扫描者, 所以 S0 将作为 From,S1 将作为 To,如下图
SurvivorTo
上面也说过了,S0, S1 交替作为 From 和 To,每次转移都会保证空出一个 S 区域,比如再 MinorGC 一次, S1 将作为 From, S0 将作为 To
不知道上面的 S0, S1, From, To 介绍清楚了没有,其实也介绍了 MinorGC 的基本流程。
MinorGC 采用的是 复制算法,Eden, S0, S1 面临的就是 复制->清空->互换 的过程。
关于垃圾清理算法,本篇先略过,后面会专门用一篇文章介绍,知道年轻代有 Eden,S0,S1 区域,并且 S0,S1 会交替被清空,来回复制即可。
年轻代中对象每经过一次 MinorGC,如果还存活的话,就会年龄+1,也就是说每次复制,如果不被删除,年龄就会增长一岁,如果达到阈值(默认15),就会转移到老年代。
老年代
老年代(年老代)主要存放应用程序中生命周期长的实例对象。老年代的对象比较稳定,所以 MajorGC 不会频繁执行。
在进行 MajorGC 前一般都先进行了一次 MinorGC,使达到年龄的新生代的对象晋身入老年代。
当无法找到足够大的连续空间分配给较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。
老年代 GC 算法使用的比较混乱,下次专门介绍,先了解这么多。
重点回顾
对象优先在 Eden 区分配
大多数情况下(注意是大多数),对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor GC。
大对象直接进入老年代
大对象是指需要连续内存空间的对象,比如很长的字符串以及数组。
老年代直接分配的目的是避免在 Eden 区和 Survivor 区之间出现大量内存复制。
长期存活的对象进入老年代
虚拟机给每个对象定义了年龄计数器,对象在 Eden 区出生之后,如果经过一次 Minor GC 之后,将进入 Survivor 区,同时对象年龄变为 1 (上面介绍有图示),增加到一定阈值时则进入老年代(阈值默认为 15, 有特例,见下方)
动态对象年龄判定
为了能更好地适应不同程序的内存状况,虚拟机并不总是要求对象的年龄必须达到阈值才能进入老年代。
如果在 Survivor 区中相同年龄的所有对象的空间总和大于 Survivor 区空间的一半,则年龄大于或等于该年龄的对象直接进入老年代。
空间分配担保
在发生 Minor GC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的空间总和,如果这个条件成立,那么 Minor GC 可以确保是安全的,如果不成立则进行 Full GC。
总结
本文主要介绍了 堆 空间的更细的划分方式,主要是从垃圾收集的角度进行划分。
堆是 JVM 面试最重要的部分之一,堆划分为 年轻代 和 老年代,为了方便垃圾回收,又把年轻代划分为了 Eden,S0, S1 区域,S0, S1 没有先后之分,它们 2 个的地位相同,就是来回替换用来存储未被清理,尚存活的对象。
老年代用来存放大对象及长期存活的对象,在老年代发生的 GC 是 MajorGC ,发生MajorGC 之前一般要先进行 MinorGC .
简单了解几点 FullGC 发生的场景即可,比如老年代无法分配内存,元空间内存不足,显示调用 GC 指令(一般也没人傻了吧唧的自己调吧),空间分配担保不成立时。
好了,今天的分享就到这里,关注公&号:新质程序猿,和我一起死磕 P7, 一起学习成长。
感谢大家的阅读,如果有任何异议的地方,欢迎指正,也欢迎大家+v: hyx2011 与我深入交流。
小福利
文末小福利,作为资深囤货达人,购置或转存了上千 T 的各种资源,反正我也学不完,如有需要,可以 hyx2011 找到我,直接送您,能帮助到大家也算是有所福报吧!