写点什么

DirectByteBuffer 内存释放

  • 2022 年 5 月 03 日
  • 本文字数:1885 字

    阅读完需:约 6 分钟

  • 处置从 allocateMemory 或 reallocateMemory 获得的本地内存块。 传递给此方法的地址可以为 null,在这种情况下,不采取任何措施。


![](https://img-blog.csdnimg.cn/20210215150030454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGV 《一线大厂 Java 面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 pdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzMzNTg5NTEw,size_16,color_FFFFFF,t_70)


分配给定大小的新本地内存块(以字节为单位)。 存储器的内容未初始化; 它们通常是垃圾。 结果本机指针永远不会为零,并且将针对所有值类型进行对齐。 通过调用 freeMemory 处理此内存,或使用 reallocateMemory 调整其大小。



  • 直接内存的释放,必须手工调用 freeMemory 方法,因为 JVM 只能帮我们管理堆内存,直接内存不在其管理范围之内。


DirectByteBuffer 帮我们简化了直接内存的使用,我们不需要直接操作 Unsafe 类来进行直接内存的申请与释放,那么其是如何实现的呢?


直接内存的申请:


在 DirectByteBuffer 实例通过构造方法创建的时候,会通过 Unsafe 类的 allocateMemory 方法 帮我们申请直接内存资源。


直接内存的释放:


DirectByteBuffer 本身是一个 Java 对象,其是位于堆内存中的,JDK 的 GC 机制可以自动帮我们回收,但是其申请的直接内存,不再 GC 范围之内,无法自动回收。好在 JDK 提供了一种机制,可以为堆内存对象注册一个钩子函数(其实就是实现 Runnable 接口的子类),当堆内存对象被 GC 回收的时候,会回调 run 方法,我们可以在这个方法中执行释放 DirectByteBuffer 引用的直接内存,即在 run 方法中调用 Unsafe 的 freeMemory 方法。注册是通过 sun.misc.Cleaner 类来实现的。


class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer


{


....


//构造方法


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); //分配直接内存,base 表示的是直接内存的开始地址


} 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 = Cleaner.create(this, new Deallocator(base, size, cap));//注册钩子函数,释放直接内存


att = null;


}


....


}


可以看到构造方法中的确是用了 unsafe.allocateMemory 方法帮我们分配了直接内存,另外,在构造方法的最后,通过 Cleaner.create 方法注册了一个钩子函数,用于清除直接内存的引用。


Cleaner.create 方法声明如下所示:


public static Cleaner create(Object heapObj, Runnable task)


其中第一个参数是一个堆内存对象,第二个参数是一个 Runnable 任务,表示这个堆内存对象被回收的时候,需要执行的回调方法。我们可以看到在 DirectByteBuffer 的最后一行中,传入的这两个参数分别是 this,和一个 Deallocator(实现了 Runnable 接口),其中 this 表示就是当前 DirectByteBuffer 实例,也就是当前 DirectByteBuffer 被回收的时候,回调 Deallocator 的 run 方法


Deallocator 就是用于清除 DirectByteBuffer 引用的直接内存,代码如下所示:


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;


}


public void run() {


if (address == 0) {


// Paranoia


return;


}


unsafe.freeMemory(address);//清除直接内存


address = 0;


Bits.unreserveMemory(size, capacity);


}


}


可以看到 run 方法中调用了 unsafe.freeMemory 方法释放了直接内存的引用。


[](()System.gc


========================================================================


在 DirectByteBuffer 实例创建时,分配内存之前调用了 Bits.reserveMemory,如果分配失败调用了 Bits.unreserveMemory,同时在 Deallocator 释放完直接内存的时候,也调用了 Bits.unreserveMemory 方法。


这两个方法,主要是记录 jdk 已经使用的直接内存的数量,当分配直接内存时,需要进行增加,当释放时,需要减少,源码如下:

用户头像

还未添加个人签名 2022.04.13 加入

还未添加个人简介

评论

发布
暂无评论
DirectByteBuffer内存释放_Java_爱好编程进阶_InfoQ写作社区