在文章JDK源码阅读-ByteBuffer中,我们进修了ByteBuffer的设计。可是他是一个抽象类,真正的实现分为两类:HeapByteBuffer与DirectByteBuffer。HeapByteBuffer是堆内ByteBuffer,昆山软件开发,利用byte[]存储数据,是对数组的封装,较量简朴。DirectByteBuffer是堆外ByteBuffer,直接利用堆外内存空间存储数据,是NIO高机能的焦点设计之一。本文来阐明一下DirectByteBuffer的实现。
如何利用DirectByteBuffer
假如需要实例化一个DirectByteBuffer,可以利用java.nio.ByteBuffer#allocateDirect这个要领:
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
DirectByteBuffer实例化流程
我们来看一下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要领分派内存
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)) {
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
// 利用Cleaner机制注册内存接纳处理惩罚函数
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
申请内存前会挪用java.nio.Bits#reserveMemory判定是否有足够的空间可供申请:
// 该要领主要用于判定申请的堆外内存是否高出了用例指定的最大值
// 假如尚有足够空间可以申请,则更新对应的变量
// 假如已经没有空间可以申请,则抛出OOME
// 参数表明:
// size:按照是否按页对齐,获得的真实需要申请的内存巨细
// cap:用户指定需要的内存巨细(<=size)
static void reserveMemory(long size, int cap) {
// 因为涉及到更新多个静态统计变量,这里需要Bits类锁
synchronized (Bits.class) {
// 获取最大可以申请的对外内存巨细,默认值是64MB
// 可以通过参数-XX:MaxDirectMemorySize=<size>配置这个巨细
if (!memoryLimitSet && VM.isBooted()) {
maxMemory = VM.maxDirectMemory();
memoryLimitSet = true;
}
// -XX:MaxDirectMemorySize限制的是用户申请的巨细,而不思量对齐环境
// 所以利用两个变量来统计:
// reservedMemory:真实的今朝保存的空间
// totalCapacity:今朝用户申请的空间
if (cap <= maxMemory - totalCapacity) {
reservedMemory += size;
totalCapacity += cap;
count++;
return; // 假如空间足够,更新统计变量后直接返回
}
}
// 假如已经没有足够空间,则实验GC
System.gc();
try {
Thread.sleep(100);
} catch (InterruptedException x) {
// Restore interrupt status
Thread.currentThread().interrupt();
}
synchronized (Bits.class) {
// GC后再次判定,假如照旧没有足够空间,则抛出OOME
if (totalCapacity + cap > maxMemory)
throw new OutOfMemoryError("Direct buffer memory");
reservedMemory += size;
totalCapacity += cap;
count++;
}
}
在java.nio.Bits#reserveMemory要领中,假如空间不敷,会挪用System.gc()实验释放内存,然后再举办判定,假如照旧没有足够的空间,昆山软件开发,抛出OOME。
假如分派失败,则需要把预留的统计变量更新归去:
static synchronized void unreserveMemory(long size, int cap) {
if (reservedMemory > 0) {
reservedMemory -= size;
totalCapacity -= cap;
count--;
assert (reservedMemory > -1);
}
}
从上面几个函数中我们可以获得信息:
-XX:+PageAlignDirectMemor参数节制堆外内存分派是否需要按页对齐,默认差池齐。reserveMemory或unreserveMemory要领,这两个要领按照内部维护的统计变量判定当前是否尚有足够的空间可供申请,假如有足够的空间,更新统计变量,假如没有足够的空间,挪用System.gc()实验举办垃圾接纳,接纳后再次举办判定,假如照旧没有足够的空间,抛出OOME。reserveMemory要领判定是否有足够内存不是判定物理机是否有足够内存,而是判定JVM启动时,指定的堆外内存空间巨细是否有剩余的空间。这个巨细由参数-XX:MaxDirectMemorySize=<size>配置。sun.misc.Unsafe#allocateMemory申请内存