Java引用类型
- 应该说,引用对于对象来说是至关重要的,对象生来是要被用的,没有引用的对象自然就用不到了,也就面临被回收,判断对象存活的关键在于引用
引用的分类
JDK1.2 之前,Java 中引用的定义很传统:如果 reference 类型的数据存储的数值代表的是另一块内存的起始地址,就称这块内存代表一个引用;JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用,终结器引用五种(引用强度逐渐减弱)。垃圾回收器对于不同引用强度的引用有不同的回收策略
- 对象当然可以对应多种类型的引用,每个引用都会发挥其对应的作用(比如与各种队列的关联),但是在回收策略上由强度最高的引用决定
强引用
- 强引用是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,JVM宁愿抛出 OOM 错误,使程序终止,也不会靠随意回收具有强引用的对象来解决内存不足问题
对于强引用来说,唯一使得其实例主动被回收的方法就是向强引用赋值null
为强引用赋值null以促成JVM垃圾回收的最典型的案例就是ArrayList的clear方法
public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
- 释放数组成员的内存,但是不释放数组的内存,因为后续可能还会使用,只是清除存储的对象数据,但是不回收已经分配的数组空间
- 与其他几种引用类型相比,强引用没有对应的类型定义,也没有引用队列使用,毕竟是古老的默认的引用,同时作为最强的引用也没必要有这些
软引用
- 如果一个对象只具有软引用,则类似于非必须生活用品。如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存,可加速 JVM 对垃圾内存的回收速度,可以维护系统的运行安全,防止内存溢出(OutOfMemory)等问题的产生
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中
public class SoftReferenceDemo { public static void main(String[] args) { Obj obj = new Obj(); SoftReference<Obj> sr = new SoftReference<Obj>(obj); obj = null; System.out.println(sr.get()); } } class Obj { int[] obj; public Obj() { obj = new int[1000]; } }
- 使用
SoftReference
描述软引用
- 使用
- 使用软引用的一个例子:假如有一个应用需要读取大量的本地图片,如果每次读取图片都从硬盘读取,则会严重影响性能,但是如果全部加载到内存当中,又有可能造成内存溢出,此时使用软引用可以解决这个问题:使用软引用维系内存中的图片数据,如果空间够用就正常使用,如果内存不够了,垃圾回收器会回收内存中图片数据占用的空间
弱引用
- 如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,JVM就会把这个弱引用加入到与之关联的引用队列中
public class WeakReferenceDemo { public static void main(String[] args) { WeakReference<String> sr = new WeakReference<>(new String("hello")); System.out.println(sr.get()); // hello System.gc(); //通知JVM的gc进行垃圾回收 System.out.println(sr.get()); // null 对饮的实例已经被回收 } }
- 使用
WeakReference
描述弱引用
- 使用
ThreadLocal
- 关于经典的ThreadLocal + 弱引用导致的内存泄漏在ThreadLocal原理的文章中介绍
虚引用
- 虚引用主要用来跟踪对象被垃圾回收的活动,给程序以指示以在对象的内存被回收前采取必要的行动。或者说一个对象持有虚引用,实际上就是等待被回收的对象
- 虚引用与软引用和弱引用的一个区别在于: 虚引用必须和引用队列(ReferenceQueue)联合使用,并且无法通过该引用来获取对象
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
public class PhantomReferenceDemo { public static void main(String[] args) { ReferenceQueue<String> queue = new ReferenceQueue<String>(); // 虚引用必须指定一个ReferenceQueue PhantomReference<String> pr = new PhantomReference<String>(new String("hello"), queue); System.out.println(pr.get()); // null } }
- 使用
PhantomReference
描述虚引用
- 使用
- 关于虚引用的一个经典的使用案例是
DirectByteBuffer
类中定义的Cleaner
类(继承自PhantomReference),Cleaner
类可以借用虚引用的特性保证DirectByteBuffer
实例被回收后,也可以同步回收由此类分配的堆外内存
DirectByteBuffer
DirectByteBuffer
是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现protected static final Unsafe unsafe = Bits.unsafe(); DirectByteBuffer(int cap) { // package-private super(-1, 0, cap, cap); boolean pa = VM.isDirectMemoryPageAligned(); int ps = Bits.pageSize(); long size = Math.max(1L, (long)cap + (pa ? ps : 0)); Bits.reserveMemory(size, cap); long base = 0; try { // 分配内存,返回基地址 base = unsafe.allocateMemory(size); } catch (OutOfMemoryError x) { Bits.unreserveMemory(size, cap); throw x; } // 内存初始化 unsafe.setMemory(base, size, (byte) 0); if (pa && (base % ps != 0)) { // Round up to page boundary address = base + ps - (base & (ps - 1)); } else { address = base; } // 构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起同步被释放 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
如何通过构建垃圾回收追踪对象Cleaner实现堆外内存释放的?----虚引用的典型应用
// Cleaner继承虚引用 public class Cleaner extends PhantomReference<Object> { // 虚引用必须结合虚引用队列使用 private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue(); // .... // DirectByteBuffer类初始化Cleaner类的语句 cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); // Cleaner类提供的初始化方法 public static Cleaner create(Object var0, Runnable var1) { return var1 == null ? null : add(new Cleaner(var0, var1)); } // var1是DirectByteBuffer的引用,var2是Deallocator实例 private Cleaner(Object var1, Runnable var2) { // 传递给虚引用的构造函数,至此,DirectByteBuffer拥有两个类型的引用,一个是调用DirectByteBuffer类的上层类中持有的强引用,一个是Cleaner中持有(本质上是被其父类的父类Reference持有)的虚引用 super(var1, dummyQueue); this.thunk = var2; } // Cleaner内部维护了一个简单的链表 private static synchronized Cleaner add(Cleaner var0) { if (first != null) { var0.next = first; first.prev = var0; } first = var0; return var0; } public void clean() { if (remove(this)) { try { this.thunk.run(); } catch (final Throwable var2) { AccessController.doPrivileged(new PrivilegedAction<Void>() { public Void run() { if (System.err != null) { (new Error("Cleaner terminated abnormally", var2)).printStackTrace(); } System.exit(1); return null; } }); } } } // ... } // 执行堆外内存的回收的线程任务 private static class Deallocator implements Runnable { private static Unsafe unsafe = Unsafe.getUnsafe(); private long address; private long size; private int capacity; private Deallocator(long address, long size, int capacity) { assert (address != 0); this.address = address; this.size = size; this.capacity = capacity; } // 在clean方法中被调用 public void run() { if (address == 0) { // Paranoia return; } // 释放内存 unsafe.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); } }
- Cleaner继承PhantomReference,PhantomReference必须与引用队列ReferenceQueue结合使用,可以实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能
当某个被Cleaner引用的对象(DirectByteBuffer)将被回收时(DirectByteBuffer的强引用已经无效,只剩下虚引用,这个虚引用的类型其实就是Cleaner(继承自PhantomReference)),JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表(不是
dummyQueue
,pending列表是GC或者说是JVM维护的,pending列表中的非Cleaner的引用才会放入到对应的引用队列中)中,等待Reference Handler
进行相关处理。其中,Reference Handler为一个拥有最高优先级的守护线程,会循环不断的处理pending链表中的对象引用,执行Cleaner的clean方法进行相关清理工作Reference Handler
的处理过程参考下边的Reference类的解析
终结器引用
终结器引用
FinalReference
,与前边几种引用一样都是属于Reference
这个抽象类的子类,其作用在于其唯一子类Finalizer
在类加载时创建了执行finalize
方法的线程,用于在GC回收前执行回收方法final class Finalizer extends FinalReference<Object> { private static class FinalizerThread extends Thread { private volatile boolean running; FinalizerThread(ThreadGroup g) { super(g, null, "Finalizer", 0, false); } public void run() { // in case of recursive call to run() if (running) return; // Finalizer thread starts before System.initializeSystemClass // is called. Wait until JavaLangAccess is available while (VM.initLevel() == 0) { // delay until VM completes initialization try { VM.awaitInitLevel(1); } catch (InterruptedException x) { // ignore and continue } } final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); running = true; for (;;) { try { // 从队列中取出引用执行finalize方法 Finalizer f = (Finalizer)queue.remove(); f.runFinalizer(jla); } catch (InterruptedException x) { // ignore and continue } } } } // ... 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(); } // ... }
- 在可达性分析算法中,不可达对象默认的被JVM赋予FinalReference引用(实际上是Finalizer类型)并投喂到对应的一个ReferenceQueue中,等待Finalizer线程去执行此队列中的对象的finalize方法(在finalizer线程中,判断是否需要执行finalize方法)
Reference类
- 除了强引用之外的其他四种引用实际上都以
Reference
类作为父类,而该类中定义了引用分类的核心功能
// Reference类
static {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
// 创建高优先级的Reference Handler线程
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();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}
/* High-priority thread to enqueue pending References 高优先级的守护线程用来处理GC向pending列表中投入的已经不可达的引用
*/
private static class ReferenceHandler extends Thread {
private static void ensureClassInitialized(Class<?> clazz) {
try {
Class.forName(clazz.getName(), true, clazz.getClassLoader());
} catch (ClassNotFoundException e) {
throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
}
}
static {
// pre-load and initialize InterruptedException and Cleaner classes
// so that we don't get into trouble later in the run loop if there's
// memory shortage while loading/initializing them lazily.
ensureClassInitialized(InterruptedException.class);
ensureClassInitialized(Cleaner.class);
}
ReferenceHandler(ThreadGroup g, String name) {
super(g, name);
}
public void run() {
while (true) {
tryHandlePending(true);
}
}
}
// 此属性承载着pending list中的下一个pending的reference,由JVM维护
private static Reference<Object> pending = null;
// 高优先级的引用处理线程会死循环的执行此方法
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
// 'instanceof' might throw OutOfMemoryError sometimes
// so do this before un-linking 'r' from the 'pending' chain...
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
// 等待pending list有reference后被JVM唤醒
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
// Give other threads CPU time so they hopefully drop some live references
// and GC reclaims some space.
// Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
// persistently throws OOME for some time...
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// Fast path for cleaners
if (c != null) {
// 如果当前处理的reference是Cleaner类型的则执行clean方法
c.clean();
return true;
}
// 如果当前处理的reference不是Cleaner类型则加入到queue中
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
// 下边补充软引用与弱引用的实现
public class SoftReference<T> extends Reference<T> {
/**
* Timestamp clock, updated by the garbage collector
由GC去更新时钟
*/
static private long clock;
/**
* Timestamp updated by each invocation of the get method. The VM may use
* this field when selecting soft references to be cleared, but it is not
* required to do so.
维护一个时间戳,类似于lru,每次调用get方法都会更新此时间戳,JVM可以以这个时间戳为凭证,判断内存不够时选择性的回收那些不常使用的弱引用
*/
private long timestamp;
/**
* Creates a new soft reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new soft reference will refer to
*/
public SoftReference(T referent) {
super(referent);
this.timestamp = clock;
}
/**
* Creates a new soft reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new soft reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*
*/
public SoftReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
this.timestamp = clock;
}
/**
* Returns this reference object's referent. If this reference object has
* been cleared, either by the program or by the garbage collector, then
* this method returns <code>null</code>.
*
* @return The object to which this reference refers, or
* <code>null</code> if this reference object has been cleared
*/
public T get() {
T o = super.get();
if (o != null && this.timestamp != clock)
this.timestamp = clock;
return o;
}
}
// 弱引用
public class WeakReference<T> extends Reference<T> {
/**
* Creates a new weak reference that refers to the given object. The new
* reference is not registered with any queue.
*
* @param referent object the new weak reference will refer to
*/
public WeakReference(T referent) {
super(referent);
}
/**
* Creates a new weak reference that refers to the given object and is
* registered with the given queue.
*
* @param referent object the new weak reference will refer to
* @param q the queue with which the reference is to be registered,
* or <tt>null</tt> if registration is not required
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
Reference
代码中包含的逻辑并非是四种引用的特性生效的全部逻辑,是需要JVM与GC配合使用的(逻辑不可见,只能从注释中瞥见一二),包括四种引用类型的子类的代码中也有对应的逻辑,大概的逻辑应该可以用下图去理解<img src="http://images.demoli.xyz/image-20210706201301955.png" style="zoom:200%;" />
引用分类的作用
类似于Java并发编程中性能提升手段组要是锁的类型细化,引用的分类也是为了便于对引用实例进行管理以提升性能
- 程序员自己可以手动的设置一个引用的引用类型,这使得程序员也可以使用引用介入对象生命周期的管理
- 更有利于JVM进行垃圾回收
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。