首先什么样的能称为"垃圾呢":
在jvm的运行时内存区域中,一般来说垃圾回收就是针对堆以及方法区这两块区域而言的。
那怎么又判断里面的内容是否就是没用的"垃圾"了呢
- 引用计数算法:顾名思义就是在对象中添加一个引用计数器,如果有一个地方对它引用了那么计数器就加一,引用失效就减一,但是这样的算法呢有一个致命的问题,就是当两个对象相互引用但已经没有其他的对象再引用它们了,这时候实际这两个对象已经没用了,但是引用计数法依然判断他们再被使用所以无法回收,所以在一般情况下以及主流的Java虚拟机中都已经没有再使用这种算法了
- 可达性分析算法:这种算法的基本思路就是通过一系列称为“GC Roots”的根对象作为起始节点集,然后从这些节点的引用关系向下搜索,搜索的这个路径称为“引用链”,如果某个对象到“GC Roots”间没有任何的引用链,就是说不可达的时候,那么这个对象就是可以判断为可收回的“垃圾”。
如下图所示:
其次我们还需要知道一个概念,叫做分代收集
分代收集的出现是因为在jvm中有的对象朝生夕灭存活时间比较短,但是有的对象存活时间又比较长,所以如果为了提高效率和有效地利用内存空间将堆划分出了不同的区域;
也因此才有了“Minor GC”,“Major GC”,“Full GC”这样不同的回收类型。并且发展出了针对不同区域的不同的回收算法——“标记-复制算法”,“标记-清除算法”,“标记整理算法”。
首先我们先介绍一下分代收集分为了新生代和老年代,对象是根据对象年龄来区分是属于哪个区域的,新生代一般是一些存活时间较短的对象,但是当新生代中的对象的年龄达到15的时候(即经历了15此GC并存活下来)就会进入老年代,也有一种比较大的对象会直接进入老年代
然后刚才谈到了几种回收类型,简单的说明一下:
- Minor GC/Young GC:指目标是新生代的垃圾回收
- Major GC/Old GC:指目标只是老年代的垃圾回收(目前只有CMS收集器会有单独的收集老年代的行为)
- Mixed GC:指目标是收集整个新生代和部分老年代的垃圾收集(目前只有G1收集器会有这种行为)
- Full GC:收集整个Java堆和方法区的垃圾收集
那么现在我们就正式的介绍一下三种不同的垃圾回收算法以及它们之间的区别:
-
标记-清除算法:
这是最早出现也是最基础的垃圾收集算法,首先它可以被分为两个阶段——”标记”,“清除”
标记出所有需要回收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回收所有未被标记的对象。
它主要的缺点有两个:一是执行的效率不稳定,如果堆中有大量的对象,且大部分都需要回收的,这时必须进行大量的标记和清除动作,导致标记和清除的这两个过程的执行效率都随着对象数量的增长而降低;二是内存空间的碎片化问题,标记清除后可能会产生大量不连续的内存碎片,空间碎片过多可能会导致在以后的程序过程中需要给较大对象分配内存空间时因找不到足够的连续内存而提前触发另一次的垃圾收集动作。
整个过程图解如下:
-
标记-复制算法
在很早的时候,1969年有人提出了一种叫做“半区复制”的垃圾回收算法,就是将可用的内存按容量划分为大小相等的两块,每次只使用其中的一块,当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把使用过的内存空间一次性的清理掉。
这样设计的优点呢就是实现比较简单,运行也相对高效;缺点呢就是只能使用一半的可用内存,过于浪费了
如下图所示:
由于在新生代中的对象98%其实都不能熬过第一轮的收集,所以衍生出了空间利用率更高的“Apple式回收”
Apple式回收的具体实现方法是把新生代分为较大的Eden空间和两块较小的Survivor空间,每次分配内存只使用Eden和其中的一块Survivor。发生垃圾收集的时候,将Eden和Survivor中任然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已经用过的Survivor空间即可。
在HotSpot虚拟机中默认Eden和Survivor的大小比例是8:1,所以三块空间的比例是Eden:Survivor:Survivor = 8:1:1
当然还有一种情况,就是当将Eden和Survivor的对象复制到另一个Survivor上时,不能保证每一次的存活对象Survivor都可以刚好足以容纳下,所以当这种情况发生时就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保。
-
标记-整理算法
这种算法主要是针对老年代对象的存亡特征,1974年提出了具有针对性的“标记-整理”算法,其中的标记过程和“标记-清除算法”一样,但是后续的不是对可回收对象进行清理,而是让所有存活对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
但是移动存活的对象且更新所有引用这些对象的地方是一种极为负重的操作,而且在对象移动操作中必须全程暂停用户应用程序才能进行。如果不移动对象,又会因为内存空间碎片过多导致大对象分配空间时再次出发GC,所以现在有的解决办法是平时虚拟机多数采用标记-清除,暂且容忍内存碎片,直接空间碎片化的程度已经大到影响对象分配时,再采用标记-整理算法收集一次以获得规整的内存空间
如下图所示: