Java 直接内存ByteBuffer.allocateDirect原理与源码解析
Java直接内存通过ByteBuffer.allocateDirect()
分配,绕开Java堆直接操作本地内存,适用于高频IO场景。以下结合源码详解其全流程:
一、Java层入口:DirectByteBuffer初始化
代码入口:ByteBuffer.allocateDirect(int capacity)
public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity); }
创建DirectByteBuffer
对象,构造函数核心逻辑如下:
-
内存对齐与容量计算
boolean pa = VM.isDirectMemoryPageAligned(); // 是否页对齐 int ps = Bits.pageSize(); // 系统页面大小 long size = Math.max(1L, (long)cap + (pa ? ps : 0)); // 总分配大小
若开启页对齐,分配大小=容量+页面大小,确保后续调整后用户内存对齐。
-
配额预留与分配检查
Bits.reserveMemory(size, cap); // 检查直接内存配额
Bits.reserveMemory
内部通过-XX:MaxDirectMemorySize
判断是否超限,可能触发GC或抛出OOM。 -
本地内存分配
long base = UNSAFE.allocateMemory(size); // 调用native方法
调用
Unsafe.allocateMemory
,通过JNI进入JVM层。
二、JVM层:本地内存分配
JNI映射:allocateMemory0
对应Unsafe_AllocateMemory0
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory0(...)) {size_t sz = (size_t)size;void* x = os::malloc(sz, mtOther); // 调用os::mallocreturn addr_to_java(x); } UNSAFE_END
-
操作系统内存分配
void* os::malloc(size_t size, MEMFLAGS flags) {if (size == 0) size = 1; // 保证至少分配1字节return ::malloc(alloc_size); // 调用系统malloc }
通过系统调用(如glibc的
malloc()
)分配内存,支持NMT(Native Memory Tracking)追踪。 -
内存追踪与保护
ptr = (u_char*)::malloc(alloc_size); // 实际分配内存 MemTracker::record_malloc(...); // 记录内存分配
NMT添加头信息跟踪内存使用,调试模式下通过
GuardedMemory
检测越界访问。
三、Java层后续处理
-
内存初始化
UNSAFE.setMemory(base, size, (byte) 0); // 清零内存
确保分配的内存初始为0,避免脏数据。
-
地址对齐调整
if (pa && (base % ps != 0)) {address = base + ps - (base & (ps - 1)); // 页面对齐调整 }
若开启页对齐,调整返回地址为页面起始位置,提升IO效率。
-
注册Cleaner释放资源
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
Cleaner
通过虚引用(PhantomReference)关联DirectByteBuffer
,当对象GC回收时触发Deallocator.run()
释放内存:UNSAFE.freeMemory(base); // 释放本地内存 Bits.unreserveMemory(size, cap); // 释放配额
四、异常处理
-
分配失败:若
os::malloc
返回NULL,抛出OutOfMemoryError
,并回滚配额。 -
页对齐失败:地址计算错误可能导致内存访问异常,但JVM通过保护页机制拦截非法访问。
五、关键设计
-
配额管理:
Bits
类全局记录已分配内存,避免超出MaxDirectMemorySize
限制。 -
页对齐优化:对齐的内存提升NIO操作性能(如FileChannel读写)。
-
安全释放:Cleaner机制确保即使开发者未手动释放,GC时也能自动回收内存,防止泄漏。
总结:Java直接内存通过JNI桥接,由JVM调用系统API分配本地内存,结合配额管理、页对齐优化及Cleaner自动释放机制,实现了高效、安全的内存管理。
##源码
java代码
public long allocateMemory(long bytes) {allocateMemoryChecks(bytes);if (bytes == 0) {return 0;}long p = allocateMemory0(bytes);if (p == 0) {throw new OutOfMemoryError();}return p;}private native long allocateMemory0(long bytes);public static ByteBuffer allocateDirect(int capacity) {return new DirectByteBuffer(capacity);}DirectByteBuffer(int cap) { // package-privatesuper(-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 boundaryaddress = base + ps - (base & (ps - 1));} else {address = base;}cleaner = Cleaner.create(this, new Deallocator(base, size, cap));att = null;}openjdk jvm C++代码{CC "allocateMemory0", CC "(J)" ADR, FN_PTR(Unsafe_AllocateMemory0)},UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory0(JNIEnv *env, jobject unsafe, jlong size)) {size_t sz = (size_t)size;assert(is_aligned(sz, HeapWordSize), "sz not aligned");void* x = os::malloc(sz, mtOther);return addr_to_java(x);
} UNSAFE_ENDvoid* os::malloc(size_t size, MEMFLAGS flags) {return os::malloc(size, flags, CALLER_PC);
}void* os::malloc(size_t size, MEMFLAGS memflags, const NativeCallStack& stack) {NOT_PRODUCT(inc_stat_counter(&num_mallocs, 1));NOT_PRODUCT(inc_stat_counter(&alloc_bytes, size));// Since os::malloc can be called when the libjvm.{dll,so} is// first loaded and we don't have a thread yet we must accept NULL also here.assert(!os::ThreadCrashProtection::is_crash_protected(Thread::current_or_null()),"malloc() not allowed when crash protection is set");if (size == 0) {// return a valid pointer if size is zero// if NULL is returned the calling functions assume out of memory.size = 1;}// NMT supportNMT_TrackingLevel level = MemTracker::tracking_level();size_t nmt_header_size = MemTracker::malloc_header_size(level);#ifndef ASSERTconst size_t alloc_size = size + nmt_header_size;
#elseconst size_t alloc_size = GuardedMemory::get_total_size(size + nmt_header_size);if (size + nmt_header_size > alloc_size) { // Check for rollover.return NULL;}
#endif// For the test flag -XX:MallocMaxTestWordsif (has_reached_max_malloc_test_peak(size)) {return NULL;}u_char* ptr;ptr = (u_char*)::malloc(alloc_size);#ifdef ASSERTif (ptr == NULL) {return NULL;}// Wrap memory with guardGuardedMemory guarded(ptr, size + nmt_header_size);ptr = guarded.get_user_ptr();if ((intptr_t)ptr == (intptr_t)MallocCatchPtr) {log_warning(malloc, free)("os::malloc caught, " SIZE_FORMAT " bytes --> " PTR_FORMAT, size, p2i(ptr));breakpoint();}if (paranoid) {verify_memory(ptr);}
#endif// we do not track guard memoryreturn MemTracker::record_malloc((address)ptr, size, memflags, stack, level);
}