GC 策略解决了哪些问题?
既然是要进行自动 GC, 那必然会有相应的策略, 而这些策略解决了哪些问题呢, 粗略的来说, 主要有以下几点.
1, 哪些对象可以被回收.
2, 何时回收这些对象.
3, 采用什么样的方式回收.
GC 策略采用何种算法?
有关上面所提到的三个问题, 其实最主要的一个问题就是第一个, 也就是哪些对象才是可以回收的.
有一种比较简单直观的办法, 它的效率较高, 被称作引用计数算法. 但是这个算法有一个致命的缺陷, 那就是对于循环引用的对象无法进行回收. 想象一下, 假设 JVM 采用这种 GC 策略, 那么程序猿在编写的程序的时候, 下面这样的代码就不要指望再出现了.
- public class Object {
- Object field = null;
- public static void main(String[] args) {
- Thread thread = new Thread(new Runnable() {
- public void run() {
- Object objectA = new Object();
- Object objectB = new Object();//1
- objectA.field = objectB;
- objectB.field = objectA;//2
- //to do something
- objectA = null;
- objectB = null;//3
- }
- });
- thread.start();
- while (true);
- }
- }
这段代码看起来有点刻意为之, 但其实在实际编程过程当中, 是经常出现的, 比如两个一对一关系的数据库对象, 各自保持着对方的引用. 最后一个无限循环只是为了保持 JVM 不退出, 没什么实际意义.
对于我们现在使用的 GC 来说, 当 thread 线程运行结束后, 会将 objectA 和 objectB 全部作为待回收的对象. 而如果我们的 GC 采用上面所说的引用计数算法, 则这两个对象永远不会被回收, 即便我们在使用后显示的将对象归为空值也毫无作用.
这里 LZ 大致解释一下, 在代码中 LZ 标注了 1,2,3 三个数字, 当第 1 个地方的语句执行完以后, 两个对象的引用计数全部为 1. 当第 2 个地方的语句执行完以后, 两个对象的引用计数就全部变成了 2. 当第 3 个地方的语句执行完以后, 也就是将二者全部归为空值以后, 二者的引用计数仍然为 1. 根据引用计数算法的回收规则, 引用计数没有归 0 的时候是不会被回收的.
根搜索算法
由于引用计数算法的缺陷, 所以 JVM 一般会采用一种新的算法, 叫做根搜索算法. 它的处理方式就是, 设立若干种根对象, 当任何一个根对象到某一个对象均不可达时, 则认为这个对象是可以被回收的.
就拿上图来说, ObjectD 和 ObjectE 是互相关联的, 但是由于 GC roots 到这两个对象不可达, 所以最终 D 和 E 还是会被当做 GC 的对象, 上图若是采用引用计数法, 则 A-E 五个对象都不会被回收.
说到 GC roots(GC 根), 在 JAVA 语言中, 可以当做 GC roots 的对象有以下几种:
1, 虚拟机栈中的引用的对象.
2, 方法区中的类静态属性引用的对象.
3, 方法区中的常量引用的对象.
4, 本地方法栈中 JNI 的引用的对象.
第一和第四种都是指的方法的本地变量表, 第二种表达的意思比较清晰, 第三种主要指的是声明为 final 的常量值.
垃圾搜集算法
根搜索算法解决的是垃圾搜集的基本问题, 也就是上面提到的第一个问题, 也是最关键的问题, 就是哪些对象可以被回收.
不过垃圾收集显然还需要解决后两个问题, 什么时候回收以及如何回收. 在根搜索算法的基础上, 现代虚拟机的实现当中, 垃圾搜集的算法主要有三种, 分别是标记 - 清除算法, 复制算法, 标记 - 整理算法. 这三种算法都扩充了根搜索算法, 不过它们理解起来还是非常好理解的.
来源: http://www.bubuko.com/infodetail-3015817.html