目录
1.2.2 判断是否有足够的空间可供申请java.nio.Bits#reserveMemory
1.2.6 内存分配失败回收内存计数:java.nio.Bits#unreserveMemory
2.1 DirectByteBuffer及对应的堆外内存回收机制Cleaner
2.2.1 守护线程处理对象引用Reference以及ReferenceQueue
2.2.2 如何触发Cleaner中的Deallocator线程释放内存
作为Java程序开发者我们都知道编译好的程序需要在Java虚拟机中运行,需要分配内存空间指定堆的大小,通过-Xms2G -Xmx2G参数指定JVM启动堆内存的大小,这是堆内内存的分配。而Java程序往往在开发中需要通过JNI调用lib库,以及使用第三方开发框架,诸如mina,netty Nio框架,这些框架为了提高通信效率,减少内存拷贝减轻Jvm GC压力使用了堆外内存,我们从下图了解一下Java运行时所需内存的分配:

根据上图很直观的发现Java程序运行需要的内存分堆内内存(Heap)和堆外内存(No Heap),本次介绍的DirectByteBuffer只是堆外内存的其中一部分,除此之外还有字节码、本地native方法调用、JVM运行本身所需内存等。
一、DirectByteBuffer直接缓冲区介绍
DirectByteBuffer是ByteBuffer的一个实现,ByteBuffer有两个类实现,一类是HeapBuffer(堆内内存),另一类是DirectBuffer(堆外内存)

1.1 如何使用DirectByteBuffer
如果需要实例化一个DirectByteBuffer,可以使用java.nio.ByteBuffer#allocateDirect这个方法
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
1.2 DirectByteBuffer对象实例化过程
1.2.1 构造器
我们来看一下DirectByteBuffer是如何构造,如何申请与释放内存的,先看看DirectByteBuffer的构造器
DirectByteBuffer(int cap) { // package-private
//初始化Buffer的四个核心属性
super(-1, 0, cap, cap);
//判断是否需要页面对齐,通过参数-XX:+PageAlignDirectMemory控制,默认为false
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 {
// 通过unsafe.allocateMemory分配堆外内存,并返回堆外内存的基地址
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
// 分配失败,释放内存
Bits.unreserveMemory(size, cap);
throw x;
}
// 初始化内存空间为0
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;
}
1.2.2 判断是否有足够的空间可供申请java.nio.Bits#reserveMemory
// 该方法主要用于判断申请的堆外内存是否超过了用例指定的最大值
// 如果还有足够空间可以申请,则更新对应的变量
// 如果已经没有空间可以申请,则抛出OutOfMemoryError
// 参数解释:
// size:根据是否按页对齐,得到的真实需要申请的内存大小
// cap:用户指定需要的内存大小(<=size)
static void reserveMemory(long size, int cap) {
// 获取最大可以申请的对外内存大小,默认值是64MB
// 可以通过参数-XX:MaxDirectMemorySize=<size>设置这个大小
// -XX:MaxDirectMemorySize限制的是用户申请的大小,而不考虑对齐情况
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
// optimist!
if (tryReserveMemory(size, cap)) {
return;
}
final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
// retry while helping enqueue pending Reference objects
// which includes executing pending Cleaner(s) which includes
// Cleaner(s) that free direct buffer memory
while (jlra.tryHandlePendingReference()) {
if (tryReserveMemory(size, cap)) {
return;
}
}
// trigger VM's Reference processing
// 如果已经没有足够空间,则尝试手动触发一次Full GC,触发释放堆外内存
System.gc();
// a retry loop with exponential back-off delays
// (this gives VM some time to do it's job)
boolean interrupted = false;
try {
long sleepTime = 1;
int sleeps = 0;
while (true) {
if (tryReserveMemory(size, cap)) {
return;
}
if (sleeps >= MAX_SLEEPS) {
break;
}
if (!jlra.tryHandlePendingReference()) {
try {
Thread.sleep(sleepTime);
sleepTime <<= 1;
sleeps++;
} catch (InterruptedException e) {
interrupted = true;
}
}
}
// no luck
throw new OutOfMemoryError("Direct buffer memory");
} finally {
if (interrupted) {
// don't swallow interrupts
Thread.currentThread().interrupt();
}
}
}
其中,如果系统中内存( 即,堆外内存 )不够的话:
final JavaLangRefAccess jlra = SharedSecrets.getJavaLangRefAccess();
// retry while helping enqueue pending Reference objects
// which includes executing pending Cleaner(s) which includes
// Cleaner(s) that free direct buffer memory
while (jlra.tryHandlePendingReference()) {
if (tryReserveMemory(size, cap)) {
return;
}
}
jlra.tryHandlePendingReference()会触发一次非堵塞的Reference#tryHandlePending(false)。该方法会将已经被JVM垃圾回收的DirectBuffer对象的堆外内存释放。
1.2.3 尝试申请内存
java.nio.Bits#tryReserveMemory方法尝试计算可以分配的真实内存大小,如何可以申请,则更新以下参数:
toalCapacity=toalCapacity+cap,目前用户已经申请的总空间大小;reservedMemory=reservedMemory+size,目前保留堆外内存总空间的大小;count:申请次数加1;
// 参数解释:
// size:根据是否按页对齐,得到的真实需要申请的内存大小
// cap:用户指定需要的内存大小(<=size)
private static boolean tryReserveMemory(long size, int cap) {
// -XX:MaxDirectMemorySize limits the total capacity rather than the
// actual memory usage, which will differ when buffers are page aligned.
// totalCapacity:目前用户已经申请的总空间
// maxMemory:堆外内存设置最大值
// reservedMemory:真实的目前保留的堆外内存空间
long totalCap;
while (cap <= maxMemory - (totalCap = totalCapacity.get())) {
if (totalCapacity.compareAndSet(totalCap, totalCap + cap)) {
reservedMemory.addAndGet(size);
count.incrementAndGet();
return true;
}
}
return false;
}
调用AtmoticLong#compareAndSet(long expect, long update),最终实现通过Unsafe更新用户申请的总内存空间大小totalCapacity+cap,即unsafe.compareAndSwapLong(valueOffset,expect,update)
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value

最低0.47元/天 解锁文章
414

被折叠的 条评论
为什么被折叠?



