自动垃圾回收是Java相较于C++的一个重要的特点,想了解JVM的垃圾回收机制,首先我们要知道垃圾回收是回收什么地方的垃圾,我在我的上一篇博客《JVM内存区域划分》里面有写到JVM里面的内存是怎么分布的,这里的回收主要是指对上文中提到的Java堆和方法区的内存的回收。
什么样的对象可以被回收
知道了回收哪里的内存之后,我们还需要知道什么样的对象是可以被回收,或者说是需要被回收的,这些对象我们称之为死掉的对象。那么哪些对象是死掉了的呢?我们说当一个对象不存在任何引用的时候就可以说这个对象是死掉了。
那么什么时候这个对象不存在任何引用了呢?
有一些地方说,可以使用引用计数算法来判断对象是否还存活,引用计数算法是说给对象添加一个引用计数器,每当一个地方引用它时就加1,引用失效时就减1,计数器的值变为0就说这个对象不存在任何引用了,但是这样会存在一个很严重的问题,就是循环引用的问题。比如如下的例子:
1 | public class Example{ |
这样的话,objA和objB的引用计数都不为0,但是他们的确是不会再被使用的了。
那么Java里面是用什么样的算法来实现对引用的判断的呢?
Java里是使用可达性分析的方法来实现的。
如图所示,就是通过一系列的称为”GC Roots”的对象作为起始点,从这些点向下搜索,搜索的路径称为引用链,当一个对象到达GC Roots没有任何引用链时,就可以证明这个对象是不可用的。那么这个对象就可以被回收了。像图中的Obj1-5都属于存活的对象,但是Obj6-8虽然还存在相互引用,但是已经是可以认为是死掉的对象了。
引用
目前来说,Java中的引用可以分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,他们的引用顺序依次逐渐减弱。
- 强引用是用来描述必须存在和引用的对象,比如Object a=new Object(),只要强引用还存在,被引用的对象就永远不会被回收。
- 软引用是用来描述一些还有用但是不是必需的对象。这类引用的对象会在内存溢出之前被列入回收范围进行第二次回收,如若回收完之后还没有获得足够的内存才会抛出内存溢出异常。可以使用SoftReference类来实现。
- 弱引用是用来描述非必需的对象,但是强度比软引用更弱,被引用的对象只能存活到下一次GC之前。当进行GC的时候不论内存是否足够都会对该引用的对象进行回收。可以使用WeakReference类来实现。
- 虚引用是最弱的引用关系。虚引用的存在不会对对象的存活造成任何影响,也不能通过虚引用来获得任何对象实例。设置虚引用的唯一目的就是在关联对象被回收时会获得一个系统通知。可以使用PhantomReference类来实现。
死亡对象的自我救赎
当一个对象在进行可达性分析的时候发现已经是没有任何引用的了,这时候垃圾收集器并没有立即判处该对象死刑,而是给了它一次自我救赎的机会,这时它会被标记一次,同时判断是不是有必要执行finalize()方法,当对象没有重写finalize()方法或者finalize()方法已经被执行过的时候,就不执行finalize()方法。下面就会有两种情况发生了:
- 执行finalize()方法:这时这个对象会被放在F-Queue队列之中,然后由虚拟机自动创建一个低优先级的Finalizer线程去执行它,但是为了保证F-Queue不被无限阻塞并不保证一定会等待它执行结束。此时在finalize()里如果对象又重新获得与GC Roots的联系就可以完成自我救赎,否则的话就只能被第二次标记,然后听天由命了。
- 不执行finalize()方法:这时候要么是垃圾收集器已经给过一次机会了,要么是对象自己没有重写finalize()去获得机会,这个时候就只能被第二次标记,立即被执行死刑了。
方法区的回收
方法区的回收分为废弃常量的回收和无用类的卸载。
- 废弃常量的回收:回收废弃常量与上面所说的堆中对象的回收差不多,没有任何引用的时候回收。
- 无用类的卸载:类的卸载需要满足三个条件:
- 该类的所有对象都被回收。
- 加载该类的ClassLoader已经被回收。
- 该类对应的java.lang.Class没有在任何地方被引用,无法在任何地方通过反射访问该类。
但是满足这三个条件只是说可以被回收,但是不代表一定被回收,具体是否被回收还要由虚拟机里的一些参数来具体确定。