在堆中的每一个对象都有一个引用计数器,在任意位置引用了该对象,则该对象的引用计数器加一。这种垃圾回收机制比较传统,其缺点是无法判断两个对象循环依赖的情况,即A调用B,且B又调用了A。因此大多数主流的JVM都不会使用这种方法。
可达性分析算法是将所有的对象转化为一个链式结构,若A调用了B,则A指向B。该链式结构的根节点为GCRoots,即从GCRoots作为起点向下索引。若发现一个对现象不在以GCRoots为根节点的调用链中,则该对象被JVM判定为不可达,被JVM标记为可回收对象,如下图:
图中,Object1-Object4均在以GCRoots为根节点的调用链中,则为不可回收对象;Object5-Object7虽然也存在调用,但不在GCRoots的调用链中,因此为可回收对象。
可作为GCRoots的对象一般都有一个特征,它只会引用其他变量,而不会被其他变量引用。一般可作为GCRoots的对象包含下面几种:
若一个对象重写了finalize()方法,则在JVM回收该对象时会调用该对象重写的finalize()方法,但只会执行一次。被回收的对象可在finalize()方法中实现一次自救。因此被标记为可回收的对象也不是非死不可,最多会经历两次被判定的过程。
JVM在可达性检测中发现了不可达的对象,首先会检查该对象是否重写了finalize()方法,若重写了该方法再判断是否调用过该方法,若没有重写finalize()方法或已被调用过finalize()方法则会被回收;若重写了finalize()方法且没有调用过的对象被判定为有必要执行finalize()方法,则会被JVM将其放置在一个名为F-Queue的队列中,JVM会自动生成一个优先级低的Finalizer的线程去执行F-Queue队列中对象的finalize()方法。若该对象的finalize()方法中引用了任意GCRoots调用链中的任意一个对象,则该对象会被判定为可达,不会被JVM回收,从而完成了一次自救。但自救只有一次,一个对象的finalize()方法只会被执行一次,下一次被判定为不可达时会直接被回收,第二次回收也被成为二次回收。
JVM的方法区主要存放的是编译class文件后生成的Class类以及一些静态资源与常量等资源。常量的回收机制与对象的回收机制类似,即没有在任何一个地方引用该常量,则该常量会从常量池中销毁。类的回收机制相对复杂一些,需要同时满足以下三个条件:
在jdk1.2之前,一个对象只有两种状态,要么可回收,要么不可回收,这中非黑即白的机制不利于程序的灵活性,因此在jdk1.2之后,jdk引入了四种对象引用状态级别,从高到低分别为:强引用>软引用>弱引用>虚引用
强引用就是我们普遍使用一个对象时的方法,即类似Object obj=new Object()
这种的引用方法。在强引用时,JVM即使内存溢出,抛出OutOfMemoryError
错误,使程序运行中断,也不会去回收强引用对象解决内存溢出问题。因此在强引用对象用完时需要将强引用弱化,可将对象置为空obj=null
软引用是指使用 SoftReference
或继承SoftReferenct
类
public class Test extends SoftReference<User> {
public Test(User referent) {
super(referent);
}
}
继承SoftReference对象时必须要实现有参构造器,且不能存在无参构造
如果一个对象只有软引用,内存充足时是不会对其进行回收的,只有当内存溢出时才会对其进行回收。JVM会尽可能优先回收闲置时间长的软引用,尽量保留新创建的软引用。
软引用也可以与引用队列ReferenceQueue联合使用,这样这个对象被回收时会被加入这个队列中,可以通过poll()方法来判断你关心的对象是否在队列中,如果没在队列中则返回null
ReferenceQueue<User> rq=new ReferenceQueue<>();
User user=new User();
SoftReference sf<User>=new SoftReference(user,rq);
Reference<? extends User> poll=rq.poll();
软引用可以通过get()方法,获取到这个对象的强引用对象,可以直接使用
ReferenceQueue<User> rq=new ReferenceQueue<>();
User user=new User();
SoftReference sf<User>=new SoftReference(user,rq);
User user=sf.get();
软引用一般用户内存敏感的高速缓存
比如在页面缓存中,进入到下一个页面中可以回退到上一个页面。如果上一个页面以强引用缓存在内存中,会造成内存泄露,随着页面访问量的增加,容易造成内存溢出。而如果将上一个页面回收掉,在回退到上一个页面时再重新加载页面会造成资源的紧张。因此如果将上一个页面以软引用缓存再内存中可以解决如上问题,不仅不需要重新加载页面,随着页面访问量的增加,当内存满溢时会回收掉最早访问的页面保留最近访问的页面。
弱引用是指使用 WeakReference
或继承WeakReference
类
public class Test extends WeakReference<User> {
public Test(User referent) {
super(referent);
}
}
继承WeakReference对象时必须要实现有参构造器,且不能存在无参构造
如果一个对象只有弱引用,无论内存是否充足,只要垃圾回收线程扫描到了弱引用,则会立即对其进行回收;因此相较于软引用来说,只拥有弱引用的对象具有更短暂的生命周期;但是垃圾回收线程的优先级很低,因此不一定会很快发现那些只具有弱引用的对象。
若引用也可以与引用队列ReferenceQueue联合使用,这样这个对象被回收时会被加入这个队列中,可以通过poll()方法来判断你关心的对象是否在队列中,如果没在队列中则返回null
ReferenceQueue<User> rq=new ReferenceQueue<>();
User user=new User();
WeakReference sf<User>=new WeakReference(user,rq);
Reference<? extends User> poll=rq.poll();
且WeakReference
并没有实现get
方法,因此弱引用无法获得强引用对象。
弱引用也可用于内存敏感的高速缓存区
例如ThreadLocal中,有一个静态内部类ThreadLocalMap,该哈希表是以开放地址法解决哈希冲突的。其key为ThreadLocal对象,value为ThreadLcoal所对应的值。该类内还有一个静态内部类Entry,其依赖链为ThreadLocal->ThreakLocalMap->Entry->key
,当ThreadLocal用完时,将其置为null,由于ThreadLocalMap为静态内部类,其中还存在ThreadLocalMap->Entry->key
的引用,则ThreadLocal并不会被回收,会造成内存泄露。JDK将Entry设置为WeakReference
,当取消了ThreadLocal的强引用时,就只剩下一个弱引用,当垃圾回收线程扫描时会将其进行回收
虚引用是指使用PhantomReference
或继承PhantomReference
类
public class Test extends PhantomReference<User> {
/**
* Creates a new phantom reference that refers to the given object and
* is registered with the given queue.
*
* It is possible to create a phantom reference with a null
* queue, but such a reference is completely useless: Its get
* method will always return null and, since it does not have a queue, it
* will never be enqueued.
*
* @param referent the object the new phantom reference will refer to
* @param q the queue with which the reference is to be registered,
*/
public Test(User referent, ReferenceQueue<? super User> q) {
super(referent, q);
}
}
继承PhantomReference对象时必须要实现有参构造器,且不能存在无参构造
虚引用必须要与引用队列ReferenceQueue
联合使用,在垃圾回收线程准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。可以通过poll()方法来判断你关心的对象是否在队列中,如果没在队列中则返回null
虚引用也叫虚幻引用,顾名思义如同幽灵一样,引用没引用都一样,并不会决定对象的生命周期。若一个对象只有一个虚引用,那么这个对象就像没引用一样,在任何时候都有可能被JVM回收。且PhantomReferenct
实现了get
方法,但只返回null,因此也无法获取到强引用对象;且从定义上来说,虚引用不可访问
虚引用一般用于检测所引用的对象是否被回收
一个对象有一个强引用和一个虚引用,由于其有一个强引用,不会被回收。当强引用用完时将其置为null,此时这个对象只有一个虚引用,随时都有可能被回收;当这个对象被回收时,会被加入到引用队列中,通过查看引用队列可知道这个对象是否被回收。