上一篇博客大概了解了一下GC收集机制的基本概念,所以现在就接着上一篇继续聊GC垃圾收集回收机制(1)
如何标记垃圾?
1>.引用计数法(Python,PHP)
有几个引用指向该对象,引用计数就是几,有新的引用指向该对象,引用计数就+1;有新的引用消亡,引用计数就减1(引用是局部变量,当出了自己的作用域);当引用计数为0时,此时该对象就可以认为是垃圾。
引用计数法虽然简单好用,但时有一个致命的缺陷,循环引用 问题,就会引起误判。
这就是一个简单的循环引用代码
这是一个简单的循环引用代码。当a1 和a2出了作用域范围之后,问题就出现了。a1,a2消亡了,对应的引用计数就 -1,这个时候两个对象的引用计数都是1,无法释放,但是这两对象无发被找到,应该被回收,但是现在无法回收。
引用计数法本身无法应对循环引用的问题,需要引入额外的开销来解决
2>.可达性分析(java)
以二叉树为例
class Node{
public int val;
public Node left;
public Node right;
public Node( int val){
this.val=val;
}
}
public class Test1 {
public static void main(String[] args) {
Node root=bulid();
}
public static Node bulid(){
Node a=new Node(1);
Node b=new Node(2);
Node c=new Node(3);
Node d=new Node(4);
Node e=new Node(5);
Node f=new Node(6);
Node g=new Node(7);
a.left=b;
a.right=c;
b.left=d;
b.right=e;
e.left=g;
c.right=f;
return a;
}
}
在此处我们手动创建一个二叉树,具体结构如下图
从代码或者结构图可以看的出来,此时 root 是 a的引用,而其他结点看起来好像都没引用指向那么奇特结点是否可以被视为垃圾?答案肯定是不可以,因为通过root这个入口,就可以间接访问到其他结点,只要这个结点能被访问到,就不能视为垃圾,这就是“可达”。
当 root 和 c 断开连接,那么此时c和f就无法被访问到,就会被标记为垃圾,被回收。
可达性分析的起点(root)称为“GCRoot”,那么哪些可以作为GCRoot呢?
1.栈上的局部变量表中的引用(root就是这个)
注:每个线程都有自己的栈,又有很多栈帧,每层栈帧又有很多局部变量,这些都是GCRoot的一部分。由此可以看出可达性分析,没有那么快
2.常量池中的引用指向的对象
3.方法区中静态引用指向的对象
可达性分析就是从GCRoot开始遍历,那些不能被访问到,就会视为垃圾,这里需要强调的一点就是,实际中对象和对象之间的相互引用关系并不是简单的树形结构,而是“图”结构,例如:
从可达性分析可以看出来,引用能够决定对象的生死,但是引用的初心只是为了能够找到这个对象而不是决定对象的生死。
java为了解决这个问题。引用了多种引用:
1.强引用:既能用于找到对象,还能决定生死,平时的引用都是强引用。
2.软引用:能找到对象,一定程度上决定对象的生死(若内存充裕,就不会回收软引用指向的对象,反之,就会回收。)
3.弱引用:只能找到对象,不能决定生死。
4.虚引用:不能找到对象,也不能决定生死,只能在对象消亡前收到通知做一些善后工作
前边的可达性分析和引用标记都考虑的是堆上的垃圾回收,现在我们来看看方法区的垃圾回收。
方法区回收的是类对象(类的卸载)
类什么时候可以卸载呢?
1>.该类的所有实例都已经报备回收。
2>.加载该类的类加载器已经被回收了
3>.该类的类对象没有在任何地方使用(也没有被反射使用这个类和对象)
同时满足这三条,该类就可以被卸载了。
关于GC标记已经总结的差不多了,下一篇将继续总结回收算法GC垃圾收集回收机制(3)