马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
PhantomReference虚引用
在分析堆外内存回收之前,先了解下PhantomReference虚引用。
PhantomReference需要与ReferenceQueue引用队列结合使用,在GC进行垃圾回收的时候,如果发现一个对象只有虚引用在引用它,则认为该对象需要被回收,会将引用该对象的虚引用加入到与其关联的ReferenceQueue队列中,开发者可以通过ReferenceQueue获取需要被回收的对象,然后做一些清理操作,从队列中获取过的元素会从队列中清除,之后GC就可以对该对象进行回收。
虚引用提供了一种追踪对象垃圾回收状态的机制,让开发者知道哪些对象准备进行回收,在回收之前开发者可以进行一些清理工作,之后GC就可以将对象进行真正的回收。
来看一个虚引用的使用例子:
- 创建一个ReferenceQueue队列queue,用于跟踪对象的回收;
- 创建一个obj对象,通过new创建的是强引用,只要强引用存在,对象就不会被回收;
- 创建一个虚引用PhantomReference,将obj对象和ReferenceQueue队列传入,此时phantomReference里面引用了obj对象,并关联着引用队列queue;
- 同样的方式创建另一个obj1对象和虚引用对象phantomReference1;
- 将obj置为NULL,此时强引用关系失效;
- 调用 System.gc()进行垃圾回收;
- 由于obj的强引用关系失效,所以GC认为该对象需要被回收,会将引用该对象的虚引用phantomReference对象放入到与其关联的引用队列queue中;
- 通过poll从引用队列queue中获取对象,可以发现会获取到phantomReference对象,poll获取之后会将对象从引用队列中删除,之后会被垃圾回收器回收;
- obj1的强引用关系还在,所以从queue中并不会获取到;
- public static void main(String[] args) {
- // 创建引用队列
- ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
- // 创建obj对象
- Object obj = new Object();
- // 创建虚引用,虚引用引用了obj对象,并与queue进行关联
- PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj, queue);
- // 创建obj1对象
- Object obj1 = new Object();
- PhantomReference<Object> phantomReference1 = new PhantomReference<Object>(obj1, queue);
- // 将obj置为NULL,强引用关系失效
- obj = null;
- // 垃圾回收
- System.gc();
- // 从引用队列获取对象
- Object o = queue.poll();
- if (null != o) {
- System.out.println(o.toString());
- }
- }
复制代码 输出结果:- java.lang.ref.PhantomReference@277c0f21
复制代码
Reference实例的几种状态
Active:初始状态,创建一个Reference类型的实例之后处于Active状态,以上面虚引用为例,通过new创建一个PhantomReference虚引用对象之后,虚引用对象就处于Active状态。
Pending:当GC检测到对象的可达性发生变化时,会根据是否关联了引用队列来决定是否将状态更改为Pending或者Inactive,虚引用必须与引用队列结合使用,所以对于虚引用来说,如果它实际引用的对象需要被回收,垃圾回收器会将这个虚引用对象加入到一个Pending列表中,此时处于Pending状态。
同样以上面的的虚引用为例,因为obj的强引用关系失效,GC就会把引用它的虚引用对象放入到pending列表中。
Enqueued:表示引用对象被加入到了引用队列,Reference有一个后台线程去检测是否有处于Pending状态的引用对象,如果有会将引用对象加入到与其关联的引用队列中,此时由Pending转为Enqueued状态表示对象已加入到引用队列中。
Inactive:通过引用队列的poll方法可以从引用队列中获取引用对象,同时引用对象会从队列中移除,此时引用对象处于Inactive状态,之后会被GC回收。
DirectByteBuffer堆外内存回收
在DirectByteBuffer的构造函数中,在申请内存之前,先调用了Bits的reserveMemory方法回收内存,申请内存之后,调用Cleaner的create方法创建了一个Cleaner对象,并传入了当前对象(DirectByteBuffer)和一个Deallocator类型的对象:- class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {
- private final Cleaner cleaner;
-
- 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;
- }
- // 创建Cleader,传入了当前对象和Deallocator
- cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
- att = null;
- }
- }
复制代码 Cleaner从名字上可以看出与清理有关,Bits的reserveMemory方法底层也是通过Cleaner来进行清理,所以Cleaner是重点关注的类。
Deallocator是DirectByteBuffer的一个内部类,并且实现了Runnable接口,在run方法中可以看到对内存进行了释放,接下来就去看下在哪里触发Deallocator任务的执行:- class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {
- private static class Deallocator implements Runnable {
- // ...
-
- private Deallocator(long address, long size, int capacity) {
- assert (address != 0);
- this.address = address; // 设置内存地址
- this.size = size;
- this.capacity = capacity;
- }
- public void run() {
- if (address == 0) {
- // Paranoia
- return;
- }
- // 释放内存
- unsafe.freeMemory(address);
- address = 0;
- Bits.unreserveMemory(size, capacity);
- }
- }
- }
复制代码 Cleaner
Cleaner继承了PhantomReference,PhantomReference是Reference的子类,所以Cleaner是一个虚引用对象。

创建Cleaner
虚引用需要与引用队列结合使用,所以在Cleaner中可以看到有一个ReferenceQueue,它是一个静态的变量,所以创建的所有Cleaner对象都会共同使用这个引用队列。
在创建Cleaner的create方法中,处理逻辑如下:
- 通过构造函数创建了一个Cleaner对象,构造函数中的referent参数为DirectByteBuffer,thunk参数为Deallocator对象,在构造函数中又调用了父类的构造函数完成实例化;
- 调用add方法将创建的Cleaner对象加入到链表中,添加到链表的时候使用的是头插法,新加入的节点放在链表的头部,first成员变量是一个静态变量,它指向链表的头结点,创建的Cleaner都会加入到这个链表中;
创建后的Cleaner对象处于Active状态。- public class Cleaner extends PhantomReference<Object>{
- // ReferenceQueue队列
- private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
- // 静态变量,链表的头结点,创建的Cleaner都会加入到这个链表中
- static private Cleaner first = null;
-
- // thunk
- private final Runnable thunk;
-
- public static Cleaner create(Object ob, Runnable thunk) {
- if (thunk == null)
- return null;
- // 创建一个Cleaner并加入链表
- return add(new Cleaner(ob, thunk));
- }
-
- private Cleaner(Object referent, Runnable thunk) {
- super(referent, dummyQueue); // 调用父类构造函数,传入引用对象和引用队列
- this.thunk = thunk; // thunk指向传入的Deallocator
- }
-
- private static synchronized Cleaner add(Cleaner cl) {
- // 如果头结点不为空
- if (first != null) {
- // 将新加入的节点作为头结点
- cl.next = first;
- first.prev = cl;
- }
- first = cl;
- return cl;
- }
- }
复制代码 Cleaner调用父类构造函数时,最终会进入到父类Reference中的构造函数中:
referent:指向实际的引用对象,上面创建的是DirectByteBuffer,所以这里指向的是DirectByteBuffer。
queue:引用队列,指向Cleaner中的引用队列dummyQueue。
[code]public class PhantomReference extends Reference { // ... public PhantomReference(T referent, ReferenceQueue |