简单解释
jvm有四种引用: strong soft weak phantom(其实还有一种FinalReference,这个由jvm自己使用,外部无法调用到),主要的区别体现在gc上的处理,如下:
- Strong类型,也就是正常使用的类型,不需要显示定义,只要没有任何引用就可以回收
-
SoftReference
类型,如果一个对象只剩下一个soft引用,在jvm内存不足的时候会将这个对象进行回收 -
WeakReference
类型,如果对象只剩下一个weak引用,那gc的时候就会回收。和SoftReference
都可以用来实现cache -
PhantomReference
类型,这个比较特殊,可以用来实现类似Object.finalize功能,下面单独解释。
示例
SoftReference<Object> soft = new SoftReference<>(new Object());
WeakReference<Object> weak = new WeakReference<>(new Object());
WeakReference<String> weakString = new WeakReference<>("abc");
PhantomReference<Object> phantom = new PhantomReference<>(new Object(), new ReferenceQueue<>());
System.gc();
System.out.println(soft.get());
System.out.println(weak.get());
System.out.println(weakString.get());
System.out.println(phantom.get());
//
java.lang.Object@73ae9565
null
abc
null
soft类型由于内存还充足,不会被回收;weak类型在gc的时候就回收;phantom总是返回null。weakString没被回收是引用常量池持有对"abc"的引用。
finalize方法
这里实际使用了FinalReference
,真正的实现是Finalizer
。大体的过程是jvm将实现了Object.finalize
方法的实例标识为finalizer类型,注册为一个Finalizer
对象,并加到到对象链中,有一个FinalizerThread
线程负责从对象链获取Finalizer
对象,执行runFinalizer
函数,最终调用Object.finalize
。详细过程可以参考JVM源码分析之FinalReference完全解读。
但是finalization方式存在一定的缺陷,比如无法保证一定执行,gc压力比较大等。
PhantomReference
PhantomReference
的get方法总是返回null,因此无法访问对应的引用对象;其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
PhantomReference
的应用并不多,其中一个应用是sun.misc.Cleaner
类,这个应用可以参考JVM源码分析之堆外内存完全解读
为什么要主动调用System.gc
既然要调用System.gc,那肯定是想通过触发一次gc操作来回收堆外内存,不过我想先说的是堆外内存不会对gc造成什么影响(这里的System.gc除外),但是堆外内存的回收其实依赖于我们的gc机制,首先我们要知道在java层面和我们在堆外分配的这块内存关联的只有与之关联的DirectByteBuffer对象了,它记录了这块内存的基地址以及大小,那么既然和gc也有关,那就是gc能通过操作DirectByteBuffer对象来间接操作对应的堆外内存了。DirectByteBuffer对象在创建的时候关联了一个PhantomReference,说到PhantomReference它其实主要是用来跟踪对象何时被回收的,它不能影响gc决策,但是gc过程中如果发现某个对象除了只有PhantomReference引用它之外,并没有其他的地方引用它了,那将会把这个引用放到java.lang.ref.Reference.pending队列里,在gc完毕的时候通知ReferenceHandler这个守护线程去执行一些后置处理,而DirectByteBuffer关联的PhantomReference是PhantomReference的一个子类,在最终的处理里会通过Unsafe的free接口来释放DirectByteBuffer对应的堆外内存块
另一个例子在 Finalizers and References in Java,貌似没翻墙无法访问,可以直接看代码https://github.com/aragozin/example-finalization。这里使用PhantomReference
来实现半自动垃圾回收。主要的区别在ReferenceUsageCheck
这个测试类。leaking_references_finalizer
和leaking_references_phantom
两个方法依赖jvm的gc机制;low_leaking_references_finalizer
和low_leaking_references_phantom
两个方法有99%的对象显式回收,只有1%的对象依赖gc。对比可以看出使用finalizer的没有变化,但是使用PhantomReference
的前后有一个数量级的下降。使用PhantomReference
的时候一般是配合一个ReferenceQueue
,在PhantomReference
引用的对象要释放的时候加入到这个队列中,如果要回收需要调用clear方法,一些housekeeping工作也是在这个时候做,这个逻辑在ResourceDisposalThread
实现:每次从队列中拿一个phantom对象,做一些记录工作,然后clear释放。
public void run() {
while(true) {
try {
PhantomResourceRef ref = (PhantomResourceRef) queue.remove();
ref.dispose();
ref.clear();
} catch (InterruptedException e) {
// ignore
}
}
}
上面这个是依赖jvm的gc机制,如果对象比较多,gc压力会比较大,这个时候可以使用显示释放,这里通过调用PhantomResourceRef.dispose
->PhantomHandle.dispose
->ReferedResource.dispose
,调用PhantomReference
的clear方法,然后将其置为空:
// 这里的handle是PhantomResourceRef,即一个PhantomReference
public synchronized void dispose() {
handle.clear();
handle = null;
super.dispose();
GLOBAL_RESOURCES.remove(this);
}
这样对jvm来说就不需要对这个PhantomReference
对象做特别处理了(不会进入ReferenceQueue
),性能上可以提升不少。
从第二个例子可以看到,PhantomReference
的进入队列之后jvm不会自动回收,需要人为调用clear方法后才最终释放,这是使用PhantomReference
的关键。
Reference类
Reference
类是上面除了Strong类型引用外的其他引用的父类。存在四种不同的状态:
- Active,这是
Reference
对象初创的时候的状态 - Pending,对象在pending列表里,等待进入队列。没有注册队列的引用对象不存在这个状态
- Enqueued,引用对象进入队列,没有注册队列的引用对象不存在这个状态
- Inactive,没有注册队列的引用对象或者从队列中被移除之后所处的状态,这个状态下的对象不再变化,等待gc回收。
上面所说的pending列表是由一个静态字段和一个next对象组成,由gc负责维护:
Reference next;
private static Reference pending = null;
最关键的是Reference
中有一个ReferenceHandler
线程对引用对象进行处理:
public void run() {
for (;;) {
Reference r;
synchronized (lock) {
if (pending != null) {
r = pending;
Reference rn = r.next;
pending = (rn == r) ? null : rn;
r.next = r;
} else {
try {
lock.wait();
} catch (InterruptedException x) { }
continue;
}
}
// Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
}
ReferenceQueue q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
主要逻辑是从pending列表中取出一个引用对象(最后这个r.next=r
不是很理解),如果是Cleaner
对象(上面介绍PhantomReference
的第一个例子里用到的)则调用其clean方法;否则加入到对应的队列中,这个用法在PhantomReference
的第二个例子中用到。上面ReferedResource.dispose
有个小细节handle = null;
,即把一个PhantomReference
对象置为null,这时候gc就不会将其加到pending列表交给ReferenceHandler
处理,而是直接回收(跟普通对象是一样的),这里是性能提升的关键
网友评论