JVM中的垃圾收集器
如何判断对象是否应该回收?
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中提供了其中作用于不同分代的收集器。不同场景使用不同或不同组合的收集器。
Serial收集器
ParNew收集器
Parallel Scavenge收集器
Serial Old收集器
Parallel Old收集器
CMS收集器
G1收集器