深入理解JVM第3章笔记
3.1 概述
垃圾收集器关注的是运行期间动态分配的内存如何管理
3.2 对象已死?
引用计数法
- 有地方引用对象,计数器加一;当引用失效时,计数器减一;计数器为零时该对象不再使用
- 缺陷是,不会回收互相引用的对象(循环引用)
可达性分析法
- 某个对象与GC Roots为起始节点集间没有任何引用链相连,证明该对象不再使用
- GC Roots包括:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI引用的对象
- JVM内部引用
- 所有被同步锁(synchronized关键字)持有的对象
- 反映JVM内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等
- 某些 “临时性” 加入的对象
- 为了避免GC Roots包含过多对象,很多收集器都具备局部回收特征
再谈引用
- Java中引用分为 ‘强引用’、’软引用’、’弱引用’ 和 ‘虚引用’ 四种:
- 强引用是指引用赋值,GC永远不会回收被强引用
- 软引用指有用但非必须得对象,软引用的对象一般在系统要发生OOM之前会被列入GC回收范围内
- 弱引用用来描述非必须对象,只生存到下一次GC发生为止
- 虚引用唯一目的是对象在GC回收时会收到一个系统通知
生存还是死亡?
- 对象宣告死亡之前至少经历两次标记过程:
- 可达性分析法判定该对象不可达时
- finalize() 方法执行前,收集器会对F-Queue对象进行第二次小规模标记
- 不建议人为调用finalize()方法
回收方法区
- 《Java虚拟机规范》 不要求方法区实现垃圾收集
- 方法区垃圾回收性价比低,主要手机废弃常量和不再使用的类
- 不再使用的类 需要同时满足以下三个条件:
- 类所有的实例都需要被回收
- 加载该类的类加载器被回收
- 该类的Class对象没有任何对象引用(无法通过反射访问该类)
3.3 垃圾收集算法
垃圾收集算法大致分为’引用计数式垃圾收集’(直接垃圾收集)和追踪式垃圾收集(间接垃圾收集)两类。本节主要介绍追踪式垃圾收集。
分代收集理论
- 分代收集的理论依据 —— 分代假说:
- 弱分代假说:大多数对象朝生夕灭
- 强分代假说:熬过越多次的GC过程的对象越难以消亡
- 跨代引用假说:跨代引用对于同代引用来说仅占极少数
- 堆内存通常划分为新生代和老年代
- GC根据分代情况分为以下几种:
- Minor GC/ Yound GC:回收新生代
- Major GC/ Old GC:单独收集老年代,目前仅CMS收集器有
- Mixed GC:回收整个新生代和部分老年代,目前仅G1收集器有
- Full GC: 回收整个Java堆和方法区,有时Major GC指的就是Full GC
标记-清除算法
内存等分为两块,每次用一块,内存占满后把存活的对象复制到另一块,然后对当前块整体清理。
- 后基于多数对象“朝生夕灭”的特点,认为内存不必等分。
- 演变为Appel式回收,即新生代分为Eden和Survivor区域,Survivor一分为二。Eden和Survivor默认大小比例为8:1。
- Appel式回收中,每次使用一块Survivor,Eden加其中一块Survivor的存活对象复制到另一块Survivor。
- 缺点是对象存活率高时复制多、效率低,因此适合对象存活率不高的新生代。
标记-整理算法
针对老年代存亡特征提出的算法,标记后将存活对象移动到内存空间一端,直接清理掉边界外内存。
- 移动对象并更新引用属于负重操作,需要“Stop the world”(全程暂停用户程序)。
- 解决了内存碎片化的问题
- HotSpot中关注吞吐量的Paraller Scavenge是基于“标记-整理”算法的,而关注延迟的CMS收集器是基于“标记-清除”算法的。
3.4 HotSpot算法细节实现
根节点枚举
- 目前所有的垃圾收集器在根节点枚举过程中都需要停顿
- HotSpot基于OopMap的数据结构来获取执行上下文和全局引用存放的位置
安全点
- 为了降低负荷,HotSpot仅在安全点生成OopMap。因此GC只能在安全点开始。
安全区域
- 指能够确保某段代码中,引用关系不发生变化的区域。在区域中任意地方开始GC都是安全的。
记忆集和卡表
- 记忆集是用于记录非收集区域指向收集区域指针集合的抽象数据结构。
- 记忆集的记录精度可以选择为字长精度、对象精度、卡精度等。
- 卡精度指用卡表来实现记忆集,它通过一个字节数组来标识“卡页”的位置。
- 如果卡页中对象存在跨代指针,整个卡页标识为1(称为变脏)。GC发生时,只要筛选中变脏的卡页,并将其中的跨代指针加入GC Roots中。
写屏障
- 在引用类型字段赋值前后产生环形通知,供程序执行额外动作,即写屏障。通过写屏障维护卡表状态。