JVM垃圾回收器
这篇帖子记录一下学习到的 JVM 垃圾回收 方面相关的知识,当然这里的知识并没有多深,因为自己对 JVM 方面的知识一致都了解不多。在这之前我是看过一点 JVM 虚拟机内存模型 方面的知识,只是一直没有时间(就是懒),所以也就没有整理写出来,时间久了就忘了好多,后面也会复习一下哪方面的知识并写出来的,当然也不会很深,因为很深的话我也看不懂,哈哈哈哈。家里买了一本 《深入理解 Java 虚拟机》 的书,到现在书签还是夹在第 44 页上面。后面不懒的时候必须要强迫自己读一下了。下面正文开始。
一、三种垃圾清除算法
在这之前其实最好先简单了解一下 JVM虚拟机 的内存模型,堆内存逻辑分区,流转机制方面的知识,比如怎么判断一个对象是否是垃圾(Root Searching 根可达性算法)等。
从始至今垃圾回收只有一下三种方式:
Mark-Sweep(标记清除):
这种清理方式是指在你使用内存的时候会将已经使用的地方打一个标记,在使用之后会将标记的地方清理掉。这种清理方式存在的最明显的问题就是:他只是将使用的地方清理掉了,并没对内存空间进行整理。这也就到导致了 内存碎片化 的问题,当后续你有个大的对象要放入内存中,而内存中没有足够的空间去存放,这就导致类内存溢出的问题。

Copying(拷贝):
这种清理方式是指你在使用内存的时候,将内存一分为二,你只使用其中一半。在使用后要清理的内存的时候,就是还有用的东西(对象),复制到没有使用的另一半中,并且是经过整理的。之后将使用的那一半整体性的全部回收。这种清理方式最明显的问题就是:内存浪费高。总有一半是没有使用的。

Mark-Compact(标记压缩):
这种清理方式在我看来就是第一种方式的增强,第一种方式的问题就是内存碎片化,那在这种方式清理的时候就是将标记的内存清楚掉,并进行整理。这种清理方式的问题就是:效率比较低,是三种方式中最低的

二、内存分代模型下的堆内存逻辑分区及对象的流转
首先,我们要知道堆内存分为年轻代、老年代这两块区域,而年轻代又分为伊甸园区(Eden)、Survivor1(S1)、Survivor2(S2)这三块,他们的比例是8:1:1。年轻代采用 Copying 算法,老年代采用 Mark-Sweep + Mark-Compact 这两种算法
对象在堆内存中的流转如下:
- 对象新生的时候是在Eden区。
- 在进行第一次垃圾回收的时候会清理Eden区并将存所的对象放到S1区。
- 在进行第二次垃圾回收的时候会清理Eden+S1这两块,将存活的对象放到S2区。
- 在进行第三次垃圾祸首的时候会清理Eden+S2这两块,将存活的对象放到S1区。
- 垃圾回收重复上面2、3、4这三步,存活对象一直在S1、S2这两块空间来回折腾。在上面几步中可以看出来使用的就是 Copying 算法。但是并不是只使用一半空间,而是在Eden、S1、S2这三块区域之间进行拷贝。
- 上面对象每经历过一次垃圾会后也就是YoungGC后年龄+1,当对象达到指定年龄后(默认15岁,或者通过
-XX:MaxTenuringThreshold指定),会进入到老年代。 - 当老年代满了之后会触发 FullGC。
三、各种垃圾回收器
1、Serial 和 Serial Old
这两种都是单线程的回收器,并且在进行垃圾回收的时候都是遵循 STW 的。
- Serial:处理的是年轻代的垃圾,在清理垃圾的时候遵循 STW,采用 Copying 算法。
- Serial Old:处理的是老年代的垃圾,在清理垃圾的时候遵循 STW,在清理的时候采用的是 Mark-Sweep + Mark-Compact 算法。
2、 Paralel Scavenge 和 Paralel Old
这两种回收器是多线程的,可以理解为上面两种的增强型。
3、ParNew 和 CMS
ParNew:这个垃圾回收器和Paralel Scavenge差不多,只不过他是适配了CMS来使用的。
CMS:这种垃圾回收器是 Concurrent 的。业务线程和垃圾回收线程是同时进行的,并没有 STW

在上图我们可以看到四个标记:
- 初始标记:开始的时候标记出 根,可以理解为 起始对象。遵循 STW。
- 并发标记:字面意思就是业务程序进行的时候标记出产生的垃圾。
- 重新标记:这里因为有可能在上一步标记之后还会产生垃圾,但是并没有被扫描到,所以需要重新标记一次。遵循 STW。
- 标记清理:清理标记出来的垃圾。
这里的标记使用到了一种标记方式,叫做三色标记。这个还是有必要去了解一下的。
还有这个回收器是存在一个天生的bug:漏标问题,这里B站上面看这个视频来了解:CMS天生的BUG