[TOC]
实现思想
在对象中添加引用计数器,引用它时,计数器加 1,对它的引用失效,计数器减 1。计数器为 0 的对象就是不可能再被引用的
问题
无法解决对象之间循环引用的问题
实现思想
从 GC Roots 对象作为起始点,向下搜索,当一个对象到 GC Roots 没有任何引用链,则此对象是不可用的。
GCRoots
引用链搜索的起始点,包括
首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺点:
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
缺点:浪费一半内存
实际使用:
将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor[1]。当回收时,将Eden和Survivor中还存活着的对象一次性地复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。当然,98%的对象可回收只是一般场景下的数据,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
目前大多数jvm采用的算法
算法核心思想:根据对象的存活周期将内存划分为若干个区域。
一般情况下将堆内存分为:新生代和老年代。
目前大多数垃圾收集器:
对于新生代都采用Copying算法。
因为新生代每次都有大量对象被回收,也就是说复制的次数较少,但是实际中并不是按1:1来划分新生代的内存空间。 一般将新生代划分为一块较大的Eden区和两块较小的Survivor区(一般为8:1:1),每次使用Eden区和一块Survivor区。当进行回收时,一般都将Eden区和Survivor中还存活的对象复制到另一块Survivor中,然后清除Eden区和刚才使用过的Survivor区。
对于老年代都采用Mark-Compact算法。
老年代每次回收都只有少量对象回收。
Serial收集器是最基本、历史最悠久的收集器,在JDK1.3.1之前是虚拟机新生代收集的唯一选择。
特点:单线程,简单高效(因为没有线程交互所带来的系统开销),进行垃圾收集时需要暂停其他所有线程(stop the world),所以就会有一定的卡顿现象。
应用:虚拟机在Clinet模式下的默认新生代收集器。
ParNew收集器是Serial收集器的多线程版本。
特点:多线程,速度相对较慢(因为有线程交互所带来的系统开销),进行垃圾收集时需要暂停其他所有进程
应用:虚拟机在Server模式下首选的新生代收集器,目前除了Serial收集器外,目前只有它能与CMS收集器配合工作。
老年代串行收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多线程和"标记-整理"算法。
特点:适用于老年代,多线程
应用:在注重吞吐量及CPU资源敏感的场合,优先考虑Parallel Scavenge收集器和Parallel Old收集器
ParallelOldGC
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,基于"标记-清除"算法。
收集过程:
初始标记(CMS initial mark)
特点:单线程,stop the world
作用:仅仅是标记一下GC Roots能直接关联到的对象,速度很快
并发标记(CMS concurrent mark)
特点:单线程,与其他线程并发运行
重新标记(CMS remark)
特点:多线程,stop the world
作用:修正并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。
并发清除(CMS concurrent sweep)
特点:单线程,与其他线程并发运行
应用:服务端
很明显的缺点:
ConcMarkSweepGC
面向服务端,设计用来取代CMS
特点:
G1分代收集的说明:G1仍采用分代收集的策略,但是不同于上面的收集器,G1的分代不是固定的区域,而是如下图一样把堆分成一个个Region,每个region具有不同的角色。
已记忆集合 Remember Set (RSet) : 避免GC时全堆扫描
在串行和并行收集器中,GC通过整堆扫描,来确定对象是否处于可达路径中。然而G1为了避免STW式的整堆扫描,在每个分区记录了一个已记忆集合(RSet),内部类似一个反向指针,记录引用分区内对象的卡片索引。当要回收该分区时,通过扫描分区的RSet,来确定引用本分区内的对象是否存活,进而确定本分区内的对象存活情况。
事实上,并非所有的引用都需要记录在RSet中,如果一个分区确定需要扫描,那么无需RSet也可以无遗漏的得到引用关系。那么引用源自本分区的对象,当然不用落入RSet中;同时,G1 GC每次都会对年轻代进行整体收集,因此引用源自年轻代的对象,也不需要在RSet中记录。最后只有老年代的分区可能会有RSet记录,这些分区称为拥有RSet分区(an RSet’s owning region)。
G1收集器运作步骤:
G1中提供了三种模式垃圾回收模式,young gc、mixed gc 和 full gc,在不同的条件下被触发。
发生在年轻代的GC算法,一般对象(除了巨型对象)都是在eden region中分配内存,当所有eden region被耗尽无法申请内存时,就会触发一次young gc,这种触发机制和之前的young gc差不多,执行完一次young gc,活跃对象会被拷贝到survivor region或者晋升到old region中,空闲的region会被放入空闲列表中,等待下次被使用。
当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即mixed gc,该算法并不是一个old gc,除了回收整个young region,还会回收一部分的old region,这里需要注意:是一部分老年代,而不是全部老年代,可以选择哪些old region进行收集,从而可以对垃圾回收的耗时时间进行控制。
如果对象内存分配速度过快,mixed gc来不及回收,导致老年代被填满,就会触发一次full gc,G1的full gc算法就是单线程执行的serial old gc,会导致异常长时间的暂停时间,需要进行不断的调优,尽可能的避免full gc。