1.引用计数算法
在对象中添加一个引用计数器,如果该对象被引用一次,计数器就加一,如果引用失效一个,计数器就减一;如果计数器为零,则该对象就可以被回收。
在Java中一般不采用引用计数算法对对象进行管理,因为引用计数算法需要考虑一些特殊情况,必须要配合大量额外工作才能保证正确地工作,例如典型的对象之间互相循环引用的问题。如果两个对象之间互相引用,会导致这两个对象的计数器始终不为零,最终导致两个对象永远无法被清理的情况(类似于死锁)。
2.可达性分析算法
主流的商用程序语言(Java、C#等)都采用可达性分析算法来判断对象是否要被回收。
该算法通过一系列的 “GC Roots” 的根对象作为起始的节点集,然后从这些节点开始,根据引用关系向下索引,通过对象之间的引用关系(“引用链”)遍历所有跟 “GC Roots” 相关的对象。而与 “GC Roots” 没有用任何引用链相连的对象,用图论的说法就是从GC Roots到该对象不可达,即此对象为可以回收的对象。
可作为GC Roots的对象一般包括:
- 虚拟机栈中局部变量表中引用的对象
- 本地方法栈中JNI(即本地方法)所引用的对象
- 方法区中静态属性引用的对象(如静态变量的引用)
- 方法区中常量引用的对象(如字符串常量池中的引用)
3.引用类型
在JDK1.2版以前,Java对引用的定义很简单,只有 “被引用” 和 “未被引用” 两种状态。在JDK1.2之后,Java对引用的概念进行了扩充,将引用分为了强引用、软引用、弱引用、虚引用。
-
强引用:类似于 new 一个新对象的引用。使用强引用相关联的对象永远不会被回收。
-
软引用:用来描述一些还有用但非必须的对象。使用软引用相关联的对象,只有在内存不够的时候,才会进行清理。
-
弱引用:描述非必须对象,只能存活到下一次垃圾回收。
-
虚引用:虚引用不会对对象的生存时间造成影响,也无法通过虚引用获得一个对象实例。设置虚引用的唯一目的就是为了在这个对象被回收的时候收到一个系统通知。
4.finalize( )方法
即使对象被可达性分析算法判定为可以回收的对象,该对象仍然有自救的方法。如果该对象覆盖了finalize()方法,且该方法没有被虚拟机调用过,在该对象被判定为可回收对象时,会被放置在一个回收队列中,如果对象在该队列中,被某个引用链上的对象重新关联,便可以逃脱被回收的命运。但是这种自救只能进行一次,如果该对象再次被作为清理的对象,将不再调用finalize()方法。
在Java中已经不再推荐使用该方法,因为回收队列还是位于堆中,如果大量的对象进入回收队列中,势必会对堆内存有很大的占用,严重的会导致内存溢出。
注:本文参考自《深入理解Java虚拟机》第三版