在Java世界里,几乎所有的对象都在堆中。堆的空间不是无限的,总有满的一天,被垃圾收集器清除掉是对象们的宿命。
那么,对象到了要被清理的时候,垃圾收集器要如何去确定哪些对象是需要清除的呢?
如何确定一个对象是垃圾
1.引用计数法
在对象中添加一个引用计数器,每当此对象被引用时,计数器值就加一;当引用失效时,计数器值就减一;当引用为零时,说明此对象不会再被使用了,到了该清理的时候了。
但是,JVM里面是不用这种方法的,原因就是对象的相互引用问题无法解决。如下所示:相互引用,但其实最后是无用的。
A a = new A();
B b = new B();
a.object = b;
b.object = a;
a = null;
b = null;
2.可达性分析算法
通过名为GC Root的根对象作为起始节点集,根据引用关系向下搜索,搜索过程所走过的路径称为引用链。如果某个对象到GC Roots间没有任何引用链相连(也就是从GC Root无法到达此对象),则说明此对象是不可能再被使用的。能作为GC Root的为:类加载器,Thread,虚拟机栈的本地变量表,static成员,常量引用,本地方法栈的变量等。
如图,对象D,E,F存在关联,到时不存在到达GC Root的路径,所以这三个对象是无用的对象。
无用对象找到了,该清理时就清理,那要如何清理呢?
垃圾收集算法
1.标记-清除(Mark-Sweep)算法
将需要清理的对象进行标记,然后根据标记来讲对象清理掉。
清理完成后:
缺点:
1.效率。对象数量越多,则需要标记和清除的数量也随之增加。效率会随着对象数量的增长而降低。
2.会产生大量不连续的空间碎片。当空间碎片太多,又有大空间的对象进来后就没有足够的连续空间可以分配,不得不提前触发垃圾收集。
2.标记-复制算法(Semispace Copying)算法
为解决标记-清除算法的缺陷,出现了标记-复制的算法,这种算法是将内存空间划分为两块相等的区域,每次只使用其中一块。当已使用的这块内存用完了,就
将当前这块内存里面还存活的对象复制到空的那块内存中去,然后执行清理操作。
清理完成后:
复制过去时,会按顺序来分配,所以的确是解决了空间碎片的问题。
缺点:
因为可以使用的内存容量只有以前的一半,空间浪费太大了。
3.标记-整理(Mark-Compact)算法
标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
清理后:
缺点:
即使解决了前两种算法的缺陷,但终归标记-整理也存在自己的缺陷:移动存活对象时,比如对象数量对象很大的老年代,移动并更新所有对象的引用是
一件负担很大的事情,而且这种操作必须全程暂停用户应用程序。所以并不是有缺点的垃圾回收算法就不能用,而是要在不同场景选择不同的算法。
不同区域使用的收集算法
堆里面分为Young区和Old区,这两个区是如何选择收集算法的。
Young区
使用标记-复制算法,对象分配在Young区后的生命周期会比较短,更新迭代快,使用复制算法效率高。
Old区
使用标记-清除或整理算法,对象在Old区呆的时间是比较长的,在两块内存空不断来回复制显然不合适。
垃圾收集器
用收集算法还不行,还要有东西来进行实现。
1.Serial
Serial收集器是最基础、历史最悠久的收集器,在JDK 1.3.1之前是HotSpot虚拟机新生代收集器的唯一选择。是一个新生代的垃圾收集器,使用算法为标记-复制算法
它是一个单线程的垃圾收集器,但这个单线程不是指用一个处理器或者一条线程来执行,而是垃圾收集的时候会暂停其它的工作线程直到收集结束。
但Serial的简单高效是优于其它垃圾收集器的,而且内存消耗也是最小的。并且Serial由于没有线程交互的开销,只专心做垃圾收集,所以是能获得最高的单线程收集效率。
现在的应用很多微服务化,分配给虚拟机的内存不会太多,收集几十兆或一两百兆的新生代停顿时间是可以控制在毫秒级的。
2.ParNew
Serial收集器的多线程并行版本。同样是是一个新生代的垃圾收集器,使用算法为标记-复制算法。
多CPU环境下效率高于Serial,单CPU情况下效率低于Serial
3.Parallel Scavenge
同样是新生代收集器,基于标记-复制算法实现的收集器,也是能够并行收集的多线程收集器。但是Parallel Scavenge更关注系统的吞吐量。
吞吐量大意味着在相同时间内能收集更多的垃圾,效率自然也高。
提供了两个可以用户自己设置的参数:
4.Serial Old
5.Parallel Old
6.CMS(Concurrent Mark Sweep)
使用标记-清除法,以最短回收停顿时间为目标的一款垃圾收集器。
整个收集过程分为四步:
初始标记(CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
因为并发标记和并发清除,收集器线程可以与用户线程一起工作,所以CMS收集器的内存回收过程是与用户线程一起并发地执行的
CMS的主要优点为并发收集、低停顿,因为算法是使用的标记-清除法,所以缺点显而易见,会产生大量空间碎片和并发时会降低吞吐量。
7.G1(Garbage First)
G1是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。
G1之前的垃圾收集器,要么是新生代收集器要么是老年代收集器。G1跳出了这个束缚,可以面向堆内存的任何部分组成回收集来进行回收,
衡量标准不再是属于哪个分代了,而是哪个内存中垃圾数量最多.。
JDK 9发布之日,G1宣告取代Parallel Scavenge加ParallelOld组合,成为服务端模式下的默认垃圾收集器,而CMS则沦落至被声明为不推荐使用(Deprecate)的收集器。