--- layout: post title: Java 引用类型 category: 技术 tags: 源码 keywords: description: ---
引用类型一共分为四种,分别对应四种不同的垃圾回收策略:
- 幽灵引用/虚引用, 和没有引用对象几乎一样, 即通过幽灵引用是无法访问对象的.垃圾回收不考虑幽灵引用, 如果一个对象只被幽灵引用指向,则在垃圾回收时,会直接回收.(提供了一种比finalization机制更加灵活的资源清理机制,文章后面会提到finalizetion机制存在的问题)
- 弱引用, 如果一个对象只被弱引用关联时, 则它也只能够存活到下一次垃圾回收, 在发生垃圾回收之前,是可通过弱引用访问对象的,发生垃圾回收的时候,会将它回收掉.(在文档中提到这中引用可用来实现 canonicalizing mappings, 关于这个映射,请看这个文章:传送门, ThradLocalMap的key即使用的这种引用类型 )
- 软引用, 如果一个对象只被软引用关联, 则只有在所有不可达对象和虚引用指向对象,以及弱引用指向对象都被回收, 内存仍然不足的情况才会被回收掉(常用做内存敏感的缓存).
- 强引用, 任何情况下,只要有强引用存在,就不会回收指向的对象, 如果内存不足,则发生OOM.
Java 对不同引用类型的支持
下面是java.lang.ref包下,引用支持的相关类
SoftReference
, WeakReference
, PhanfomReference
分别对应上面提到的,软引用,弱引用,幽灵引用. 而
对强引用来说,任何正常赋值的引用都是强引用(例如 String strong = new String("")
, strong 就是一个强引用). 所以无需一个新的Reference
子类来表示它.
而FinalReference
这个类,应该不是上面提到的强引用对应的类,这个类感觉就只是用来实现对对象的finalize()
方法调用的.
Final references, used to implement finalization
java的Reference
提供一种对象回收提示机制.Reference
提供两种构造方式:
// 普通构造, 直接接收一个对象,包装成某一种引用类型
Reference(T referent) {this(referent, null);}
// 对象回收构造, 接收待包装对象,以及一个ReferenceQueue队列
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
其中上面的第二种构造方式,会需要传入一个ReferenceQueue
对象, 这个队列对象的作用是,如果本Reference
实例包装的对象被回收掉了,则JVM会将对象引用放入这个队列中,从而Reference
客户端代码,就可以感知到对象被回收事件,从而可做出对应处理.
对象被回收,对应Reference进入ReferenceQueue
Reference包装对象被回收时,对应的Reference对象会被加入ReferenceQeueue队列中.以下为Reference类中,被回收对象对应的Reference入队列操作.ReferenceHandler
为Reference
的内部类,用来处理入列操作.
// 引用处理线程和垃圾回收器共用锁对象.每次垃圾回收开始的时候
// 都会去获取这个锁,所以任何获取这个锁的线程,都要尽快完成并释放这个锁.
static private class Lock { };
private static Lock lock = new Lock();
// 这是一个已被回收对象的链表,里面都是等待被`ReferenceHandler`线程处理的`Reference`对象
// 由垃圾回收线程,将回收对象加入这个链表中.
private static Reference pending = null;
// 引用处理线程,用来将所有已回收对象加入对应的`ReferenceQueue`对象中去.
private static class ReferenceHandler extends Thread {
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
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 {
// 如果没有已回收对象需要添加入`ReferenceQueue`中,则将线程挂起,等待垃圾回收线程唤醒
try {
lock.wait();
} catch (InterruptedException x) { }
continue;
}
}
// Fast path for cleaners
if (r instanceof Cleaner) {
((Cleaner)r).clean();
continue;
}
// 将被回收对象对应的`Reference`对象加入 对应的`ReferenceQueue`中,
// 从而客户端代码,可以感知到,对象被回收
ReferenceQueue q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
}
}
}
// 创建引用处理线程,将优先级设置为最高,并且设置为守护线程
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
/* If there were a special system-only priority greater than
* MAX_PRIORITY, it would be used here
*/
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
}
Finalizer 执行覆盖了Object.finalize()方法的待回收对象的finalize()方法
JVM会将所有覆盖了Object.finalize()
方法的对象,包装为一个Finalizer
并放入一个需执行finalize()
方法的队列中,如果对象需要被回收,则会将它放入另一个ReferenceQueue
队列中,然后,有一个守护线程,在不断的从ReferenceQueue
队列中获取Finalizer
对象,并执行它的finalize()
方法.
至于JVM如何保证一个对象的finalize()
方法,只会被执行一次,而不会被执行多次呢?
前面说到每一个需要执行finalize()
方法的对象都会被包装成一个Finalizer
对象,这个Finalizer
对象会被放入一个双向链表中.如果一个Finalizer
对象的已经执行过finalize()
方法了,则会将它从双向链表中删除.从而JVM在发现一个需执行finalize()
方法的对象已经不在链表中了,就可以判定为该对象的finalize()
已经执行过了,无需再次执行,直接回收.
在下面的remove()
方法中,会将Finalizer
对象的next和pre都指向自己,从而表明它已经执行过finalize()
了
private void remove() {
synchronized (lock) {
if (unfinalized == this) {
if (this.next != null) {
unfinalized = this.next;
} else {
unfinalized = this.prev;
}
}
if (this.next != null) {
this.next.prev = this.prev;
}
if (this.prev != null) {
this.prev.next = this.next;
}
this.next = this; /* Indicates that this has been finalized */
this.prev = this;
}
}
下面是执行所有对象finalize()
方法的线程实现, 就是不断的从等待被回收,但需执行finalize()
方法的队列中获取对象.然后执行其finalize()
方法.(调用queue.remove()
,若队列无元素,则会阻塞).
private static class FinalizerThread extends Thread {
private volatile boolean running;
FinalizerThread(ThreadGroup g) {
super(g, "Finalizer");
}
public void run() {
if (running)
return;
// Finalizer thread starts before System.initializeSystemClass
// is called. Wait until JavaLangAccess is available
while (!VM.isBooted()) {
// delay until VM completes initialization
try {
VM.awaitBooted();
} catch (InterruptedException x) {
// ignore and continue
}
}
final JavaLangAccess jla = SharedSecrets.getJavaLangAccess();
running = true;
for (;;) {
try {
Finalizer f = (Finalizer)queue.remove();
f.runFinalizer(jla);
} catch (InterruptedException x) {
// ignore and continue
}
}
}
}
下面是创建上面说的线程的代码, 可以看见线程的优先级并不是最高的.所以有可能对象已经待回收了,但是一直无法执行该线程执行finalize()
, 从而对象无法被回收,最终导致内存泄漏.其所持有的资源也无法释放
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread finalizer = new FinalizerThread(tg);
finalizer.setPriority(Thread.MAX_PRIORITY - 2);
finalizer.setDaemon(true);
finalizer.start();
}
这篇讲finalizer
的博客 (传送门) 也讲的蛮详细的 .