JVM笔记之垃圾回收
极客时间 JVM
JVM常见垃圾回收算法
引用计数法
古老,基础
- 原理:统计每个对象被引用次数,如果为0,就释放对象,回收无用内存
问题:
- 并发场景下,引用计数的修稿和对象指针的修改必须是同步的,所以需要加锁or无锁算法
- 有时会引发连锁式回收(等待时间无法预测)
- 循环引用的问题(致命问题)
思考:
- 如何解决循环引用
- 是否可以先减去旧obj的引用计数,再增加新obj引用计数?
我觉得不可以,因为减去为0了然后就直接回收了,然后再给他加就来不及了
基于拷贝的算法
原理:将堆一分为二,一半为from,一半为to,用from进行分配,空间不足的时候,触发gc,gc把存活对象复制到to,然后把from和to互换

优点
- 采用bump the pointer(碰撞指针javascript:void(0);法),要分配多少空间就移多少次指针,高效;而回收是否高效需要取决于存活对象的比例
缺点
- 浪费一半内存空间
- 需要停顿(业务线程)
Mark-Sweep
使用链表管理所有的区域,存活的就表示,不存活的就把内存free

基于拷贝的垃圾算法(重点)
问题:对象位置发生变化时,指向它的引用如何维护?(说人话:ab都引用c,ac搬到新空间了,b怎么知道c的新地址)
-
引入中间层:一个间接指针,但虽然复制简单了,但分配和回收不容易做
![]()
-
forwarding 指针:它从c指向c’,b找到c后就可以通过forwarding指针来找到c’
![]()
-
改进:提高空间的利用率:Eden,Survivor0,Survivor1;这样浪费的空间只有Survivor0/1一个。
图算法(其实没搞清楚JVM和它的关系)
- 广度优先
前一层没有遍历完,就不会访问到后一层的节点,所以BFS适合求解最优化的问题
JDK6.0之前用BFS - 深度优先
JDK6.0之后DFS,主要是因为用了DFS,有利于cpu的缓存命中 - DFS应用:八皇后,走迷宫
总结:copy-base适合存活对象少的情况,mark-sweep适合存活对象多的情况。
对于生命周期不同的对象,会希望使用不同的算法。因此,将堆分为了新生代和老年代,新生代使用scavenge(就是s0,s1,eden);老年代使用mark-sweep
并发垃圾回收
分代垃圾回收算法

- 新生代:对象的创建在新生代的Eden空间
- 晋升:新生代进入老年代,此时它可能会成为年轻代GC的root GC,为了避免每次找root都需要对老年代遍历一遍,就引入card table来标记老年代对象中包含的年轻代的指针
![]()
- 根引用:不在堆中,但指向堆的引用。它包括栈上的引用,全局变量,JVM的Handler等
- 维护跨代引用:card Table,解决对象增多时记录集维护的复杂性
下图是记录集
![]()
下图是card table,使用的是位图,是用来维护跨代引用的,所以不分代就不会有card table;将512bytes映射为一个byte,当byte置位,则表示这512bytes的堆空间包含指向年轻代的引用;如果一个对象大于512bytes,就会让两个标志位的card全部置位
![]()
GC停顿和并发标记算法
- GC停顿:拷贝算法中对象的地址会发生移动,
- Mark-Sweep因为不涉及地址的转移,因此可以做到没有GC停顿,这就是并发的GC算法
三色标记算法
不是真实存在的?是个抽象的算法?它只是一个抽象的概念,用于代表不同的状态
白色:未完成搜索
灰色:正在搜索
黑色:已经搜索完成
问题:DFS的时候灰色的状态是什么
- 并发标记的问题:
发生在黑色对向白色对象的引用(漏标),因为b是正在扩展的引用,如果它不能到达c,c就会漏标
![]()
解法:
- 直接把白色标灰,不会影响效率(往前进
![]()
- 把黑色对象标灰,减少浮动垃圾(往后退
![]()
- B对C的引用删除的时候,将C标记为灰色,这时候即使不再被引用,也会被判定为存活。这样就会产生一些存活着的垃圾
![]()
- cms 本质上也是往后退,但充分利用了card table;这时它就有两个功能:维护跨代引用,标记灰色节点
![]()
链式思考:
- 并发标记为什么会产生漏标:因为并发的时候会有多个线程对对象的引用关系进行更改,如果更改的时候恰好出现黑色对象直接引用白色对象,就会发生漏标
- 为了防止漏标,引入了两种barrier(前进后退
- cms为了实现防止漏标方法,就使用card table来重新标记
- 在真正使用的时候,会担心young gc时候会破坏掉card table的数据结构,所以将card table转移到 mod union table
CMS参数调优
- metaSpace和maxMataSpace最好设置成一样的,因为mateSpace满了就一定会fullGC
分块GC,从分代进化而来的
所有对象都集中在一个区域的话,gc停顿事件时不可控的,所以分块gc产生了
新的挑战:
- 维护跨区引用
- 合理选择回收区域
- 考虑分代的影响
write barrier
更改对象之间引用关系的时候,可以通过一些额外的事情来维护相关的信息。引用技术和card table都是一种写屏障
G1 GC
分为Minor GC和Mixed GC;前者只回收年轻代,后者回收年轻代和部分老年代;
G1 的full GC是把系统听下来,整个GC做回收











浙公网安备 33010602011771号