如何确定哪些对象“存活”及“死亡”。
定义:在对象中添加一个引用计数器,每当有一个地方引用他,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
问题:无法解决循环引用
定义:通过一系列称为“ GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots 间没有任何引用链相连,或者用图论的话来说就是从 GC Roots 到这个对象不可达时,则证明此对象是不可能再被使用的。
GC Roots包括以下几种:
在虚拟机栈(栈帧中的本地变量表)中可用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
在方法区中类静态属性引用的对象,譬如 Java 类的引用类型静态变量。
在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
在本地方法栈中 JNI(即通常所说的 Native 方法)引用的对象。
Java 虚拟机内部的引用,如基本数据类型对应的 Class 对象,一些常驻的异常对象
(比如 NullPointExcepiton、OutOfMemory Brror) 等,还有系统类加载器
所有被同步锁(synchronized关键字)持有的对象
反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
目前,最新的几款垃圾收集器无一例外都具备了局部回收的特征。
JDK1.2之后把引用细分为:强引用(日常使用的引用)、软引用、弱引用和虚引用。
在可达性分析中:
1.如果对象在进行分析后发现没有与GC Roots相连接的引用链,那么它将会被第一次标记
2.finalize()方法是对象逃离死亡命运的最后一次机会,稍后收集器将对F-Queue中的对象进行第二次小规模的标记,重新建立则能活过来。
从如何判定对象消亡的角度出发,垃圾收集算法可以划分为“引用计数式垃圾收集”(Reference Counting GC)和“追踪式垃圾收集”(Tracing GC)两大类,这两类也常被称作“直接垃圾收集”和“间接垃圾收集”。
本书主要是追踪式垃圾收集
1.弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
2.强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
3.跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。
简单
缺点:
1.执行效率不稳定
2.内存空间的碎片化问题
该算法在对象存活率较高时就要进行较多的复制操作,效率将降低。
具体做法:标记过程和标记-清除一样,但是不是直接对内存进行回收,而是让所有的存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。这样就会引起Stop-The-World
**寻找GCRoots节点,这一步都是必须暂停用户线程的,因此毫无疑问根节点枚举是有STW的。**但是查找引用链的过程可以与用户并发进行。
查找节点也无需遍历所有的引用,使用了一组称为OopMap的数据结构达到了这个目的。
**1.作用:**用户程序到达安全点才能够进行垃圾回收。
实际上 HotSpot 也的确没有为每条指令都生成 OopMap,前面已经提到,只是在“特定的位置”记录了这些信息,这些位置被称为安全点(Safepoint)。
2.如何选择
“是否具有让程序长时间执行的特征”为标准进行选定的
例如:方法调用、循环跳转、异常跳转等都属于指令序列复用,
3.如何在垃圾收集发生时让所有的线程停下来
1)抢占式中断
先中断,如果不能中断的线程再恢复这条线程,跑到安全点再中断
2)主动式中断
线程主动区轮询是否要休息
轮询点:
轮询操作已经优化至一条汇编指令的程度了
安全区域是指能够确保在某一段代码片段之中,引用关系不会发生变化,因此,在这个区域中任意地方开始垃圾收集都是安全的。我们也可以把安全区域看作被扩展拉伸了的安全点。
为了解决跨代引用产生的问题,引入记忆集。
记忆集是一种抽象的数据结构:
字长精度: 每个记录精确到一个机器字长(就是处理器的寻址位数,如常见的 32位或 64 位,这个精度决定了机器访问物理内存地址的指针长度),该字包含跨代指针。
对象精度: 每个记录精确到一个对象,该对象里有字段含有跨代指针。
卡精度: 每个记录精确到一块内存区域,该区域内有对象含有跨代指针
卡表
是记忆集的一个具体实现,定义了记忆集的记录精度与对内存的映射关系。
如何工作?
一个卡页的内存中通常包含不止一个对象,只要卡页内有一个(或更多)对象的字段存在着跨代指针,那就将对应卡表的数组元素的值标识为 1,称为这个元素变脏(Dirty),没有则标识为 0。在垃圾收集发生时,只要筛选出卡表中变脏的元素,就能轻易得出哪些卡页内存块中包含跨代指针,把它们加入 GC Roots 中一并扫描。
卡表元素何时变脏的答案是很明确的——有其他分代区域中对象引用了本区域对象时,其对应的卡表元素就应该变脏。
写屏障可以看作在虚拟机层面对“引用类型字段赋值”这个动作的 AOP 切面,在引用对象赋值时会产生一个环形(Around)通知,供程序执行额外的动作,也就是说赋值的前后都在写屏障的覆盖范畴内。
**开销:**每次只要对引用进行更新,就会产生额外的开销,不过这个开销与 Minor GC 时扫描整个老年代的代价相比还是低得多的。
三色标记法:
白色: 表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。
黑色: 表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
灰色: 表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没
有被扫描过。
当且仅大满足以下条件,会产生对象消失的问题,即原本是黑色的对象被误标为白色:
赋值器插入了一条或多条从黑色对象到白色对象的新引用;
赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。
解决这个问题的两种方案:
1.增量更新(Incremental Update)
黑色对象一旦新插入了指向白色对象的引用之后,它就变回灰色对象了。
2.原始快照(Snapshot At TheBeginning, SATB)
无论引用关系删除与否,都会按照刚刚开始扫描那一刻的对象图快照来进行搜索。
以上两种方案都是通过写屏障实现的。