目录
JVM的垃圾回收机制:GC,是Java提供的对于内存自动回收的机制。
在 Java 中,所有的对象都是要存在内存中的(也可以说内存中存储的是一个个对象),因此将内存回收,也可以叫做死亡对象的回收。GC回收的是“堆上的内存”。
一、死亡对象的判断算法
1.1 引用计数算法
思想:
给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1; 任何时刻计数器为0的对象就是不能再被使用的,即对象已"死"。
引用计数法实现简单,判定效率也比较高,在大部分情况下都是一个不错的算法。比如Python语言就采用引用计数法进行内存管理。
1.2 可达性分析算法
思想:
通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到 GC Roots 没有任何的引用链相连时 (从GC Roots到这个对象不可达)时,证明此对象是不可用的。如下:
对象 Object5 - Object7 之间虽然彼此还有关联,但是它们到 GC Roots 是不可达的,因此他们会被判定为可回收对象。
在Java语言中,可作为 GC Roots 的对象包含下面几种:
在 JDK1.2 时,Java 对引用的概念做了扩充,分为以下四种,这四种引用的强度依次递减:
二、垃圾回收算法
2.1 标记-清除算法
"标记-清除"算法是最基础的收集算法。算法分为"标记"和"清除"两个阶段 : 首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。后续的收集算法都是基于这种思路并对其不足加以改进而已。
"标记-清除"算法的不足主要有两个 :
- 效率问题 : 标记和清除这两个过程的效率都不高。
- 空间问题 : 标记清除后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行中需要分配较大对象时,无法找到足够连续内存而不得不提前触发另一次垃圾收集。
2.2 复制算法
复制"算法是为了解决"标记-清理"的效率问题。
此算法实现简单,运行高效。算法的执行流程如下图 :
2.3 标记-整理算法
复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法。 针对老年代的特点,提出了一种称之为"标记-整理算法"。标记过程仍与"标记-清除"过程⼀致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界以外的内存。
流程图如下:
2.4 分代算法
分代算法和上面 3 种算法不同,分代算法是通过区域划分,实现不同区域和不同的垃圾回收策略,从而实现更好的垃圾回收。
当前 JVM 垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。
一般是把Java堆分为新生代和老年代。在新生代中,每次垃圾回收都有大批对象死去,只有少量存活,因此我们采用复制算法;而老年代中对象存活率高、没有额外空间对它进行分配担保,就必须采用"标记-清理"或者"标记-整理"算法。
三、垃圾收集器
收集算法是内存回收的方法论,垃圾收集器是内存回收的具体实现。
垃圾收集器的作用:垃圾收集器是为了保证程序能够正常、持久运行的一种技术,它是将程序中不用的死亡对象也就是垃圾对象进行清除,从而保证了新对象能够正常申请到内存空间。
以下这些收集器是 HotSpot 虚拟机随着不同版本推出的重要的垃圾收集器:
上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,就说明他们之间可以搭配使用。所处的区域,表示它是属于新生代收集器还是老年代收集器。
3.1 CMS收集器(老年代收集器,并发GC)
特性:
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用集中在互联网站或者B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。CMS收集器就非常符合这类应用的需求。
CMS收集器是基于“标记—清除”算法实现的,它的整个过程分为4个步骤:
1. 初始标记(CMS initial mark):初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要“Stop The World”。
2. 并发标记(CMS concurrent mark):并发标记阶段就是进行GC Roots Tracing的过程。
3. 重新标记(CMS remark):重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,仍然需要“Stop The World”。
4. 并发清除(CMS concurrent sweep):并发清除阶段会清除对象。 由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
3.2 G1收集器(唯一一款全区域的垃圾回收器)
G1(Garbage First)垃圾回收器是用在heap memory很大的情况下,把heap划分为很多很多的 region块,然后并行的对其进行垃圾回收。
G1垃圾回收器在清除实例所占用的内存空间后,还会做内存压缩。
年轻代垃圾收集 :
在G1垃圾收集器中,年轻代的垃圾回收过程使用复制算法。把Eden区和Survivor区的对象复制到新的Survivor区域。 如下图:
老年代垃收集:
对于老年代上的垃圾收集,G1垃圾收集器也分为4个阶段,基本跟CMS垃圾收集器一样,但略有不同:
• 初始标记(Initial Mark)阶段:同CMS垃圾收集器的Initial Mark阶段一样,G1也需要暂停应用程序的执行,它会标记从根对象出发,在根对象的第一层孩子节点中标记所有可达的对象。但是G1的垃圾收集器的Initial Mark阶段是跟minor gc一同发生的。也就是说,在G1中,你不用像在CMS那样,单独暂停应用程序的执行来运行Initial Mark阶段,而是在G1触发minor gc的时候一并将年老代上的Initial Mark给做了。
• 并发标记(Concurrent Mark)阶段:在这个阶段G1做的事情跟CMS一样。但G1同时还多做了一件事情,就是如果在Concurrent Mark阶段中,发现哪些Tenured region中对象的存活率很小或者基本没有对象存活,那么G1就会在这个阶段将其回收掉,而不用等到后面的clean up阶段。这也是Garbage First名字的由来。同时,在该阶段,G1会计算每个 region的对象存活率,方便后面的clean up阶段使用 。
• 最终标记(CMS中的Remark阶段):在这个阶段G1做的事情跟CMS一样, 但是采用的算法不同,G1采用一种叫做SATB(snapshot-at-the-begining)的算法能够在Remark阶段更快的标记可达对象。
• 筛选回收(Clean up/Copy)阶段:在G1中,没有CMS中对应的Sweep阶段。相反,它有一个Clean up/Copy阶段,在这个阶段中,G1会挑选出那些对象存活率低的region进行回收,这个阶段也是和minor gc一同发生的,如下图所示: