Java GC 快速回顾
如何判断对象是否死亡?
有2种方法:
- 引用计数
- 可达性分析
标记对象真正死亡的过程?
有3个步骤
- 通过可达性分析,标记并筛选对象(是否有必要执行
finalize()
) - 将筛选过后的对象加入
F-Queue
- JVM开启finalizer线程处理
F-Queue
GC Roots
大概有哪些?
- 局部变量、静态变量、常量的引用
Class
类型对象JNI
对象(Native
方法引用的对象)- 被
synchronized
关键字持有的对象
方法区怎么回收?
方法区要回收的主要是:不用的class
对象,废弃的常量
class
对象如何回收?
满足以下的3个条件可以回收
- 没有该类型的实例
- 该类型的类加载器已经卸载
- 没有对class的引用
常量如何回收?
与Java堆对象回收相似
为什么没有静态变量?
静态变量在class
对象里
四种引用类型?
- 强引用
- 软引用:没内存了就收集
- 弱引用:活不过一轮
- 虚引用:只能用来通知一个对象被回收了,无其他用处
分代收集理论?
- 新生代朝生夕灭
- 老年代不容易灭
- 跨代引用占极少数
如何处理跨代引用?
有2步
- 在新生代建立全局数据结构,记忆集
(Rememebered Set)
,记录哪些老年代有跨代引用 - 在
Minor GC
扫描时,将这些内存中的对象加入GC Roots
垃圾回收算法?
有3种算法
标记清除 :生碎片
标记整理 :耗时间
标记复制:耗内存
垃圾回收之前具体要做的事?
具体如何枚举GC Roots
?
根节点枚举必须暂停用户线程。不是去挨个内存的找,通过 OopMap
记录有对象引用的地方,直接查找OopMap
OopMap
是什么?
在类加载时和即时编译时在特定的位置,记录下对象的位置
什么时候暂停运行的用户线程?
在线程到达安全点时,也就是OopMap
记录的位置就是安全点的位置
如何暂停运行的用户线程?
2种方法:
- 抢先式中断:先全部中断,然后一个个看谁没到安全点,谁就重新开始跑,直到安全点
- 主动式中断:设置一个
flag
,执行中一直轮询flag
,如果flag && safe point
那么可以暂停了
什么时候暂停Sleep
或者Blocked
的用户线程?
安全区域
具体如何处理跨代引用?
抽象上是通过一个记忆集Remembered Set
来记录跨代引用,具体实现上使用卡表
具体卡表?
卡表记录的是老年代哪些内存区域存在跨代引用指针,而不用知道具体哪个地方有
其中内存位置记录有3种精度:
- 字长精度:这个内存地址就是跨代引用
- 对象精度:这个对象里面有跨代引用
- 卡精度:这一个内存区域里面有跨代引用
Hotspot
默认的卡表标记逻辑,:
CARD_TABLE [this address >> 9] = 0
具体如何维护卡表?
通过写后屏障,即在进行对象赋值时,(AOP)在虚拟机层面在该语句后面加上post_write_barrier()
卡表共享写如何解决?
在写入之前,进行判断。判断这个区域是否已经有跨代引用了,有就不写了
具体如何进行并发可达性分析?
GC线程和用户线程并发,要保证在一次标记过程中保持一致性,有2种方法
- 增量更新
- 原始快照
垃圾收集器
CMS (Concurrent Mark Swap)
面向对象
低停顿,适用于B/S
架构
回收过程
- 初始标记
- 并发标记
- 重新标记
- 并发清除
初始标记
Stop World 标记通过GC Roots
能直接关联的对象
并发标记
遍历GC Roots
的图
重新标记
Stop World 通过增量更新标记在并发标记过程中用户线程产生的垃圾
并发清除
标记-清楚算法
缺点
- 标记清除算法带来的内存碎片
- 并发清除过程中带来的浮动垃圾提前了
CMS
启动的时机,要预留内存给用户线程以免出现失败进行FULL GC
- 多并发,资源敏感
G1
面向对象
低停顿,吞吐量
回收过程
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
初始标记
Stop World 标记通过GC Roots
能直接关联的对象,并且修改TAMS指针(Top at Mark Start)
并发标记
遍历GC Roots
的图
最终标记
Stop World 通过原始快照去修正并发标记中产生的垃圾
筛选回收
整体标记-整理,局部标记-复制通过对每个Region
估值排序然后回收,更新Region
的统计信息
缺点
-
为每个
Region
维护记忆集并且是双向的(CMS
是单向的),内存占用大 -
因为记忆集维护复杂,所以写屏障也复杂,异步计算(
CMS
同步计算)计算量增大