我们知道Java中的对象一般存放在堆中,但是总不能让这些对象一直占着内存空间,这些对象最终都会被回收并释放内存,那么我们如何判断对象已经成为垃圾呢?这篇文章会提出两种算法解决这个问题。另外,本文还要谈一谈对象引用相关的知识,这在面试中也是经常被问到的问题。
判断对象是否存活算法
引用计数算法
引用计数算法的原理比较简单,在对象中添加一个引用计数器,当有一个引用指向一个对象时,这个对象的引用计数器会加1,当引用不再指向对象时,引用计数器便减1。当引用计数器的值为0时,说明对象已经成为垃圾。
引用计数算法的优点是算法非常简单,效率也比较高。但是Java并没有使用这种算法管理内存,这是为什么呢?我们先来看看下面这段代码。
public class Demo {
public static void main(String[] args) {
Obj obj1 = new Obj();
Obj obj2 = new Obj();
obj1.instance = obj2;
obj2.instance = obj1;
obj1 = null;
obj2 = null;
}
}
class Obj {
public Obj instance;
public Obj() {
super();
}
}
我们看一下上面这段代码,两个对象obj1和obj2的instance分别指向对方,造成了循环引用。即便最后obj1和obj2都置为空,但它们的instance还是指向对方,如果我们使用这种算法,这两个对象势必无法回收,最终造成内存泄漏。因此,Java没有选择使用引用计数算法管理内存。
可达性分析算法(根搜索算法)
主流的商用编程语言(Java、C#以及上古语言Lisp)都是用这种算法进行对象是否存活的判断。
根搜索算法利用GC Roots作为起始节点集,从这些节点开始向下搜索,凡是能够到达的节点都是存活节点,不能到达的节点都不再会被使用,这些节点的对象都可以被回收。
Java技术体系中可以作为GC Roots的对象有以下几种:虚拟机栈(栈帧的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(及native方法)引用的对象
虚拟机内部的引用,比如基本数据类型对应的Class对象,一些常驻的异常对象(如NullPointerException、OutOfMemoryError)等,还有类加载器。
被同步锁(synchronized)持有的对象
反映Java虚拟机内部情况的JMXBean、JVMTI注册的回调,本地代码缓存等
另外,需要补充的是,即便被可达性分析算法标记为不可达对象,这些对象也未必一定会被回收。事实上,被标记为不可达对象后,这个对象还需要经历第二次标记才可能会被宣布正式死亡。当第一次标记以后,会判断对象有没有必要执行finalize()方法。当对象没有覆盖finalize()方法或已经被虚拟机调用过的话,就被判定没有必要再继续执行了。当有必要执行finalize()方法时,这些对象会被放在名为F-Queue的队列中,随后虚拟机会创建一个线程去执行队列中的对象的finalize()方法。如果这些对象在收集器对F-Queue中对象标记前在finalize()方法中重新与引用链上的对象建立关系,比如将自己赋值给某个类变量就可以移出回收集合,免除被回收的命运。
对象引用
谈到引用,不得不提到前几天的考研复试,我的一位学弟在复试过程中被问到Java中有哪些引用,这几种引用有什么区别。被问到这道题时,他一时语塞,没有回答上来。不过好在没有因为这道题受到影响。今天我们就来谈一谈Java中的四种引用。强引用。强引用是我们平时使用最多的引用方法。Object obj = new Object()这就是一个典型的强引用实例,只要强引用关系还存在,即使系统内存不足,也不会回收这些对象。
软引用。软引用用来描述一些还有用,但不是必须的对象,使用SoftReference类来实现软引用。当系统内存不足时,会回收软引用关联的对象。
弱引用。弱引用通过WeakReference类来实现,即使内存充足,下一次垃圾收集时,弱引用对象也会被回收。
虚引用。虚引用又称“幽灵引用”或“幻影引用”,是最弱的一种对象引用,它使用PhantomReference类来实现。虚引用的作用是在关联的对象被回收时能收到一个系统通知。
喜欢的给个赞呗
了解更多可以关注我的公众号:SKY技术修炼指南