垃圾收集
判断对象已死?
1、引用计数法
给对象添加一个引用计数器。但是很难解决循环引用问题。
2、可达性分析法
通过一系列的 ‘GC Roots’ 的对象作为起始点,从这些节点出发所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连的时候说明对象不可用。
可作为GC Roots的对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中 JNI(即一般说的 Native 方法) 引用的对象
- 虚拟机中内部的引用
- 同步锁持有的对象
- 反映Java虚拟机内部情况的IMXBean等
再谈引用
四种强度的引用类型(由强到弱):
- 强引用
类似于 Object obj = new Object();
创建的,只要强引用在就不回收。
- 软引用
SoftReference 类实现软引用。在系统要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收
- 弱引用
WeakReference 类实现弱引用。对象只能生存到下一次垃圾收集之前。在垃圾收集器工作时,无论内存是否足够都会回收掉只被弱引用关联的对象
- 虚引用
PhantomReference 类实现虚引用。无法通过虚引用获取一个对象的实例,为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
生存或死亡
即使在可达性分析算法中不可达的对象,也并非是“facebook”的,这时候它们暂时出于“缓刑”阶段,一个对象的真正死亡至少要经历两次标记过程:如果对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。
如果这个对象被判定为有必要执行 finalize() 方法,那么这个对象竟会放置在一个叫做 F-Queue 的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚拟机会出发这个方法,并不承诺或等待他运行结束。finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象简历关联即可。
finalize() 方法只会被系统自动调用一次。
垃圾回收算法
分代收集理论上:
(1) 弱分代假说:绝大多数对象都是朝生夕灭的。
(2) 强分代 假说:熬过越多次垃圾收集过程的对象就越 难以消亡。
(3) 跨代引用假说:跨代引用相对于同代引用来说仅占 极少数。
跨代解决:在新生代中建立全局数据结构,记忆集,来标记老年代中某块存在跨代引用
- 标记清除算法:标记出所有需要回收的对象,最后将这些标记的对象清除掉。优点:简单。缺点:效率低,产生大量的内存碎片
- 标记整理算法:标记出所有需要回收的对象,把存活的移到一端,然后会清理掉存活区以外的内存。优点:得到规整的内存。缺点:移动对象比较消耗时间
- 复制算法:将内存回收分为大小相等的两个部分,在其中一部分进行存储,当存储满了或者空间不够时,就将空间进行一次垃圾回收,将存活下来的对象放到另一个内存上,把用过的那一部分清楚掉。优点:解决了内存碎片问题。缺点:由于很多对象都是朝生夕死的,所以会浪费50%的空间代价。
- Eden和2个survivor区 8:1
- 分代回收算法:根据对象的存活周期将对象划分为新生代和老年代,新生代主要采用复制算法,老年代主要选择标记整理或者标记清除算法。
HotSpot的算法实现细节(简单了解)
- 根节点枚举
-
解决方案-OopMap
一旦类加载动作完成的时候,HotSpot就会把对象内什么偏移量上是什么类型的数据计算出来,在即时编译(见第11章)过程中,也 会在特定的位置记录下栈里和寄存器里哪些位置是引用。这样收集器在扫描时就可以直接得知这些信 息了,并不需要真正一个不漏地从方法区等GC Roots开始查找。
-
- 安全点
- 只是在“特定的位置”记录OopMap,这就是安全点
- 安全区域
- 在这个区域内的线程只能等待遍历OopMap结束才能出来 ,同时,遍历OopMap不需要考虑在安全区域的线程
- 记忆集与卡表
- 用来解决跨表引用
- 表中元素位老年代的内存地址
- 写屏障
- 并发的可达性分析
- 三色标记
- 原始快照
- G1 Shenandoah收集器
- 增量更新
- CMS收集器
- 原始快照
- 三色标记