【Java】 DirectByteBuffer堆外内存回收

[复制链接]
发表于 2022-10-8 22:00:09 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
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中并不会获取到;
  1.    public static void main(String[] args) {
  2.         // 创建引用队列
  3.         ReferenceQueue<Object> queue = new ReferenceQueue<Object>();
  4.         // 创建obj对象
  5.         Object obj = new Object();
  6.         // 创建虚引用,虚引用引用了obj对象,并与queue进行关联
  7.         PhantomReference<Object> phantomReference = new PhantomReference<Object>(obj, queue);
  8.         // 创建obj1对象
  9.         Object obj1 = new Object();
  10.         PhantomReference<Object> phantomReference1 = new PhantomReference<Object>(obj1, queue);
  11.         // 将obj置为NULL,强引用关系失效
  12.         obj = null;
  13.         // 垃圾回收
  14.         System.gc();
  15.         // 从引用队列获取对象
  16.         Object o = queue.poll();
  17.         if (null != o) {
  18.             System.out.println(o.toString());
  19.         }
  20.     }
复制代码
输出结果:
  1. 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类型的对象:
  1. class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {
  2.     private final Cleaner cleaner;
  3.         
  4.     DirectByteBuffer(int cap) {                   // package-private
  5.         super(-1, 0, cap, cap);
  6.         boolean pa = VM.isDirectMemoryPageAligned();
  7.         int ps = Bits.pageSize();
  8.         long size = Math.max(1L, (long)cap + (pa ? ps : 0));
  9.         // 清理内存
  10.         Bits.reserveMemory(size, cap);
  11.         long base = 0;
  12.         try {
  13.             // 分配内存
  14.             base = unsafe.allocateMemory(size);
  15.         } catch (OutOfMemoryError x) {
  16.             Bits.unreserveMemory(size, cap);
  17.             throw x;
  18.         }
  19.         unsafe.setMemory(base, size, (byte) 0);
  20.         if (pa && (base % ps != 0)) {
  21.             // Round up to page boundary
  22.             address = base + ps - (base & (ps - 1));
  23.         } else {
  24.             address = base;
  25.         }
  26.         // 创建Cleader,传入了当前对象和Deallocator
  27.         cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
  28.         att = null;
  29.     }
  30. }
复制代码
Cleaner从名字上可以看出与清理有关,Bits的reserveMemory方法底层也是通过Cleaner来进行清理,所以Cleaner是重点关注的类。
Deallocator是DirectByteBuffer的一个内部类,并且实现了Runnable接口,在run方法中可以看到对内存进行了释放,接下来就去看下在哪里触发Deallocator任务的执行:
  1. class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer {
  2.     private static class Deallocator implements Runnable {
  3.         // ...
  4.         
  5.         private Deallocator(long address, long size, int capacity) {
  6.             assert (address != 0);
  7.             this.address = address; // 设置内存地址
  8.             this.size = size;
  9.             this.capacity = capacity;
  10.         }
  11.         public void run() {
  12.             if (address == 0) {
  13.                 // Paranoia
  14.                 return;
  15.             }
  16.             // 释放内存
  17.             unsafe.freeMemory(address);
  18.             address = 0;
  19.             Bits.unreserveMemory(size, capacity);
  20.         }
  21.     }
  22. }
复制代码
Cleaner

Cleaner继承了PhantomReference,PhantomReference是Reference的子类,所以Cleaner是一个虚引用对象。

创建Cleaner

虚引用需要与引用队列结合使用,所以在Cleaner中可以看到有一个ReferenceQueue,它是一个静态的变量,所以创建的所有Cleaner对象都会共同使用这个引用队列。
在创建Cleaner的create方法中,处理逻辑如下:

  • 通过构造函数创建了一个Cleaner对象,构造函数中的referent参数为DirectByteBuffer,thunk参数为Deallocator对象,在构造函数中又调用了父类的构造函数完成实例化;
  • 调用add方法将创建的Cleaner对象加入到链表中,添加到链表的时候使用的是头插法,新加入的节点放在链表的头部,first成员变量是一个静态变量,它指向链表的头结点,创建的Cleaner都会加入到这个链表中;
创建后的Cleaner对象处于Active状态。
  1. public class Cleaner extends PhantomReference<Object>{
  2.     // ReferenceQueue队列
  3.     private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
  4.     // 静态变量,链表的头结点,创建的Cleaner都会加入到这个链表中
  5.     static private Cleaner first = null;
  6.      
  7.     // thunk
  8.     private final Runnable thunk;
  9.      
  10.     public static Cleaner create(Object ob, Runnable thunk) {
  11.         if (thunk == null)
  12.             return null;
  13.         // 创建一个Cleaner并加入链表
  14.         return add(new Cleaner(ob, thunk));
  15.     }
  16.    
  17.     private Cleaner(Object referent, Runnable thunk) {
  18.         super(referent, dummyQueue); // 调用父类构造函数,传入引用对象和引用队列
  19.         this.thunk = thunk; // thunk指向传入的Deallocator
  20.     }
  21.      
  22.     private static synchronized Cleaner add(Cleaner cl) {
  23.         // 如果头结点不为空
  24.         if (first != null) {
  25.             // 将新加入的节点作为头结点
  26.             cl.next = first;
  27.             first.prev = cl;
  28.         }
  29.         first = cl;
  30.         return cl;
  31.     }
  32. }
复制代码
Cleaner调用父类构造函数时,最终会进入到父类Reference中的构造函数中:
referent:指向实际的引用对象,上面创建的是DirectByteBuffer,所以这里指向的是DirectByteBuffer。
queue:引用队列,指向Cleaner中的引用队列dummyQueue。
[code]public class PhantomReference extends Reference {    // ...        public PhantomReference(T referent, ReferenceQueue
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表