回收分为两大块, 一为搜索, 二为回收.
一, 搜索
GC(Garbage Collector)在回收对象前首先必须发现那些无用的对象, 如何去发现定位这些无用的对象?
1, 引用计数算法(基本弃用)
引用计数器算法是给每个对象设置一个计数器, 当有地方引用这个对象的时候, 计数器 + 1, 当引用失效的时候,
计数器 - 1, 当计数器为 0 的时候, JVM 就认为对象不再被使用, 是 "垃圾" 了.
引用计数器实现简单, 效率高; 但是不能解决循环引用问问题(A 对象引用 B 对象, B 对象又引用 A 对象, 但是
A,B 对象已不被任何其他对象引用), 同时每次计数器的增加和减少都带来了很多额外的开销, 所以在 JDK1.1 之后,
这个算法已经不再使用了.
2, 根达搜索算法(目前使用中)
根搜索算法是通过一些 "GC Roots" 对象作为起点, 从这些节点开始往下搜索, 搜索通过的路径成为引用链
(Reference Chain), 当一个对象没有被 GC Roots 的引用链连接的时候, 说明这个对象是不可用的.
gcroot 对象:
a) 虚拟机栈 (栈帧中的本地变量表) 中的引用的对象.
b) 方法区域中的类静态属性引用的对象.
c) 方法区域中常量引用的对象.
d) 本地方法栈中 JNI(Native 方法)的引用的对象.
二, 回收
1, 标记清除算法
标记 - 清除算法包括两个阶段:"标记" 和 "清除". 在标记阶段, 确定所有要回收的对象, 并做标记. 清除阶
段紧随标记阶段, 将标记阶段确定不可用的对象清除. 标记 - 清除算法是基础的收集算法, 标记和清除阶段的效率不
高, 而且清除后回产生大量的不连续空间, 这样当程序需要分配大内存对象时, 可能无法找到足够的连续空间.
2, 复制算法
复制算法是把内存分成大小相等的两块, 每次使用其中一块, 当垃圾回收的时候, 把存活的对象复制到另一块上,
然后把这块内存整个清理掉. 复制算法实现简单, 运行效率高, 但是由于每次只能使用其中的一半, 造成内存的利用
率不高. 现在的 JVM 用复制方法收集新生代, 由于新生代中大部分对象 (98%) 都是朝生夕死的, 所以两块内存的比
例不是 1:1(大概是 8:1).
3, 标记整理算法
标记 - 整理算法和标记 - 清除算法一样, 但是标记 - 整理算法不是把存活对象复制到另一块内存, 而是把存活对
象往内存的一端移动, 然后直接回收边界以外的内存. 标记 - 整理算法提高了内存的利用率, 并且它适合在收集对象
存活时间较长的老年代.
4, 分代收集(当今最常用的方法)
将对象按生命周期不同划分:
年轻代(Young Generation)(Eden,Survivor-s0,Survivor-s1)
年老代(Old Generation)
持久代(Permanent Generation).(包含应用的类 / 方法信息, 以及 JRE 库的类和方法信息. 和垃圾回收基本无关)
1)创建新对象, 一般将直接放入新生代 Eden 区域, 大对象将直接放入年老代.
2)当 Eden 区域内存分配完毕, 小 Gc 触发, 根达性分析的可达对象将进入 Survivor 区域 - s0, 并清空 Eden 区域. 不可达对象将直接删除.
3)当 Eden 区域再次内存分配完毕时候, 小 gc 触发, 根达性分析的可达对象将进入 Survivor-s1 区域, 同时, Survivor-s0 区域触发小 gc, 其中可达对象移动到 Survivor-s1 区域, 企鹅年龄 + 1, 并清空 Survivor-s0,.
4)Eden 又填满之后, Survivor-s0 与 Survivor-s1, 互换标签, Eden 区域可达对象进入 Survivor-s0,Survivor-s1 触发小 gc, 可达对象进入 Survivor-s0, 并且年龄 + 1.
5)重复上述过程, 达到一定时候, 进入年老代.
来源: https://juejin.im/post/5b17a5475188257d6225a7a2