在之前的文章中我们谈到过Java内存区域的概念,我们知道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
类来实现。虚引用的作用是在关联的对象被回收时能收到一个系统通知。
参考文献
深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)
学堂在线-JAVA程序设计进阶