如何判断对象是否应该回收?

Java垃圾收集器主要工作在堆中,回收对象之前,首先需要判断该对象是否需要回收,那么如何判断一个对象是否应该回收?

引用计数法

给对象添加一个计数器,每当一个地方引用对象时,计数器就进行加一操作,每当一个引用失效,计数器就进行减一操作。这个方法简单,判定效率高,但是它存在对象之间相互引用的问题。比如objA中的属性引用objB,objB中的属性引用objA,那么这两个对象相互引用,就导致GC不能回收这两个对象。不过HotSpot虚拟机不是采用这种算法。

根搜索算法

通过一系列的名为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索过的路径成为引用链,当一个对象到任何一个“GC Roots”都没有引用链相连,就说明这个对象不可用,需要回收了。

可作为GC Roots的对象可包括下列:

1.栈帧中的的本地变量表中的引用的对象

2.方法区中类静态属性引用的对象

3.方法区中常量所引用的对象

4.本地方法栈中JNI的引用的对象

finalize()

如果对象到“GC Roots”不可达,也并不会马上回收,会进行一次标记并且进行一次筛选,筛选的条件是是否有必要执行finalize方法,如果finalize被覆盖并且没有执行过,那么这个对象会放置在一个名为F-Queue的队列中,有一个低优先级的Finalizer线程去执行,并且只是执行,并不确保能执行结束。如果在此时将该对象与引用链上的任何对象相连,便可让他重新存活,但是记住finalze方法至多只能执行一次。不过这个方法不推荐使用。

回收方法区

一般方法区执行垃圾回收是非常少的,回收的效率比较低,这也是为什么方法区也被成为永久代。永久代的垃圾收集主要分为两部分——废弃的常量和无用的类。

只要任何地方都没有引用该常量,那么这个常量就需要被回收了。

判断一个无用的类需要满足以下三个条件

1.内存中不存在任何该类的实例

2.该类的ClassLoader已经被回收

3.无法在任何地方通过反射访问该类的方法

垃圾收集算法

标记——清除算法

首先标记出需要回收的对象,在标记完成后同一回收所有被标记的对象,标记过程如同根搜索算法。标记清除是最基本的算法,后续的算法都是根据该算法的缺点进行改进的。

该算法的缺点,一个是效率问题,效率不高;另一个是空间问题,因为该算法会导致大量不连续的空间碎片,这样当程序需要分配较大内存的空间时,就找不到足够的空间。

复制算法

他将内存划分为大小相等的两块,每次使用其中的一块,当这一块内存用满时,就将还存活的对象复制到另一块空的内存中去,然后将这一块空间全部清除,这样就不会产生不连续的内存碎片了,但是这样比较浪费空间,每次都要预留一块空间。

考虑到新生代中大部分的内存是需要回收的,于是Java虚拟机就按8:1:1的比例分配给Eden和两块较小的Survivor,当回收时将Eden和Survivor中还存在的对象一次性拷贝到另一块Survivor中,最后清理Eden和一块Survivor空间,HotSpot默认分配比例是8:1:1。但是我们不能保证存活的对象每次都小于10%,那么当Survivor空间不足时,就需要老年代进行分配担保,也就是将其复制到老年代的内存中。

标记——整理算法

复制算法中存在的缺陷是,我们不想浪费50%的内存空间,那么就需要额外的内存担保,所以老年代一般不推荐使用这种算法。

根据老年代的特点,提出了一种“标记——整理”的算法,标记整理算法几乎与“标记——清除”算法一样,但是后续还会将存活的对象都往一端移动,然后直接清理掉段边界以外的内存。

分代收集算法

新生代中对象存活率低,一般使用复制算法,只需要付出少量存活对象的复制成本。

老年代中对象存活率高,一般使用标记——清除或标记整理——算法。

YGC和FGC触发的时机

垃圾收集器

HotSpot中的垃圾收集器
HotSpot中提供了其中作用于不同分代的收集器。不同场景使用不同或不同组合的收集器。

Serial收集器

ParNew收集器

Parallel Scavenge收集器

Serial Old收集器

Parallel Old收集器

CMS收集器

G1收集器