为了实现垃圾收集,首先要决定如何定义对象是否已经“死去”,即是否已经不被引用。
最简单的计数方法,维护一个对象的计数器,当对其引用时就将计数器值加1,引用失效时减1,计数器值为0时即未被任何地方引用,对象死亡。
然而若存在循环引用时,当两个对象都不再被引用,但互相引用对方,此时计数器值不为0,即无法回收。
为了防止抱团取暖的情况发生,即去除循环引用的无用对象,采取可达性分析算法标记存活的对象。
以一系列称为“GC Roots"的对象作为起始点,按照引用链进行搜索,能够通过引用链到达“GC Roots"的对象则是存活的,而无法到达的对象将被放逐抛弃(可回收对象)。类似森林,每个“GC Roots"作为根节点生成树,无法追溯到根节点的对象无法从树根获取营养死亡。
可作为GC Roots的对象:
JDK1.2后,Java对引用的概念进行扩充,分为强引用、软引用、弱引用、虚引用四种:
按顺序总结:不回收 -> 空间不足时回收 -> 只要发生GC必定回收 -> 回收通知一声
当对象被标记为死亡时,并非一定会被回收,仍有一次(且仅有一次)自救的机会。
GC会进行两次标记,第一次标记出没有引用链通向GC Roots的对象并进行一次筛选,筛选条件为是否有必要执行finalize()方法。当对象没有覆盖finalize()方法或该方法已经被虚拟机调用过了,则视为没必要执行。
若筛选为有必要执行,则对象会被放入叫F-Query的队列中,稍后由虚拟机建立的一个低优先级的Finalizer线程执行。(不保证等待方法执行结束,因为当方法执行较慢或陷入死循环后,会导致队列永久等待甚至GC崩溃)
若对象要拯救自己,只需在finalize()方法中与引用链上任意一个对象建立关联即可(如将自己(this关键字)赋值给某个类变量或对象成员变量),拯救成功的对象会在第二次标记时被移出回收集合。
总结:确认枪毙名单(第一次标记) -> 判断有没有上诉资格(是否覆盖finalize()方法或方法是否已经被调用)-> 二次确认枪毙名单
方法区(永久代)对回收并不作要求,因为回收的效率远远不如新生代。
与堆中对象相似,当常量池中的接口、方法、字段的符号引用等没有被其他任何地方引用即可被清除出常量池。
当满足下列三个条件,才可以说这个类是无用的:
总结:
标记-清除算法:将一块田里坏了的苗都原地拔掉,留下好的
复制算法:准备两块一样大的田,只种一块,把这块好的苗移到另一块按顺序种好,然后犁平,两块田交替使用。
标记-整理算法:把好的苗都按顺序种到一边,然后犁平剩下的田
( 新生代每次都有大批对象死去,少量存活,因此使用复制算法;老年代对象存活率高,使用标记-整理算法或标记-清除算法)
HotSpot虚拟机包含7种垃圾收集器,分布如下
新生代:Serial收集器、ParNew收集器、Parallel Scavenge收集器
老年代:CMS收集器、Serial Old收集器、Parallel Old收集器
(G1收集器在两代中均可用)
特点:
过程:
用户线程到达SafePoint(暂停) -> GC线程(单线程)(新生代采用复制算法) -> 用户线程运行到达SafePoint(暂停) -> GC线程(单线程)(老年代采用标记-整理算法) ->用户线程继续运行
多线程的Serial收集器,除使用多线程进行垃圾收集,其余与Serial收集器一样。
过程:
用户线程到达SafePoint(暂停) -> GC线程(多线程)(新生代采用复制算法) -> 用户线程运行到达SafePoint(暂停) -> GC线程(单线程)(老年代采用标记-整理算法) ->用户线程继续运行
使用复制算法的并行多线程收集器,与ParNew相同。
不同点:其他收集器更关注尽可能缩短垃圾收集时用户进程的停顿时间,而Parallel Scavenge收集器更注重达到一个可控制的吞吐量,即cpu用于运行用户代码的占比,因此也被称为“吞吐量优先”收集器。
特点:
是Serial收集器的老年代版本
特点:
Parallel Scavenge收集器的老年代版本
特点:
特点:
过程:
用户线程到达SafePoint(暂停)-> 初始标记(仅标记GC Roots,速度很快)-> 并发标记(与用户线程并行,进行对象的追踪标记)-> 重新标记(与初始标记相同,为了标记并发期间变化的对象)->并发清理(与用户线程并行,进行对象的清理)
特点:
过程:
用户线程到达SafePoint(暂停)-> 初始标记(仅标记GC Roots,速度很快)-> 并发标记(与用户线程并行,进行对象的追踪标记)->最终标记(多线程,修复并发标记阶段产生的标记变动)->筛选回收(多线程)
java的堆分区为:
永久代(方法区)不属于堆
(Java 8 移除了永久代,创建了Metaspace(元空间)来替代,现大多数的类元数据分配在本地化内存中,元空间被并入堆中,堆的空间变大)
注:大对象(需要大量连续内存空间的,如长字符串以及数组)直接进入老年代。
对象进入Survivor区后年龄设为1,每在Survivor区熬过一次Minor GC,年龄就增加1岁,当达到一定程度(默认15)转入老年代。
并不是一定要达到要求值才能转入老年代,如果Survivor区中相同年龄所有对象大小总和大于Survivor空间的一半,则年龄大于等于该数值的对象都可以直接进入老年代。
发生Minor GC前,虚拟机会先检查老年代的最大连续可用空间是否大于新生代所有对象总空间,若成立则可确保是安全的。若是不成立,则需检查设置是否允许担保失败,如果允许,则会检查老年代的最大连续可用空间是否大于历次晋升对象的平均大小,若大于,则会尝试本次Minor GC(尽管存在风险),若小于或不允许冒险,则改为进行Full GC。