Java中调用sun.misc.Unsafe函数的内存开销分析
Java中使用Unsafe进行堆外内存分配的开销分析
创建Unsafe实例的代码
// 创建Unsafe实例的方式 Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null);
待分析的堆外内存操作代码
long address = unsafe.allocateMemory(Integer.BYTES); int myInteger = 3; unsafe.putInt(address, myInteger); int myReadInt = unsafe.getInt(address);
内存分配细节拆解
unsafe.allocateMemory返回的long地址:这个long变量如果是局部变量,会分配在JVM栈上,占用8字节(64位JVM环境下);如果是类成员变量则会分配在堆中。这部分是JVM用来追踪堆外内存地址的必要开销。int myInteger = 3:作为局部变量时,该int值会存在栈上,占用4字节;如果是编译期常量,JVM可能会直接优化为常量池引用,甚至在指令中嵌入值,不会额外分配栈空间。unsafe.putInt写入堆外:确实会在堆外分配4字节内存并写入值,这部分是真正的堆外存储开销。unsafe.getInt读取操作:读取时会把堆外的4字节数据复制到JVM的栈内存中(对应myReadInt变量),这个myReadInt作为局部变量占用4字节栈空间,但这不属于堆分配,栈内存会在方法执行完毕后自动释放,不需要垃圾回收(GC)处理。
总开销计算与C/C++对比
你提到的"写入4字节堆外内存总共分配16字节",是基于局部变量都分配栈空间的场景:栈上8字节地址 + 4字节myInteger + 堆外4字节 = 16字节。但要注意,栈内存是方法执行时的临时空间,方法结束后会自动回收,和堆内存的GC机制完全不同。
和C/C对比:在64位系统中,C/C常规写法int* addr = malloc(4); *addr = 3;里,指针变量addr同样占用栈上8字节,字面量3通常直接嵌入指令,不会额外分配栈空间,总开销为栈8字节 + 堆外4字节 = 12字节,比Java的情况少4字节(对应Java中myInteger的栈空间)。但如果数据量很大(比如分配1MB堆外内存),JVM中记录地址的8字节开销几乎可以忽略,这时候堆外内存的优势就会凸显。
读取时的内存回收问题
从堆外读取的myReadInt如果是局部变量,存储在栈上,方法执行完毕后栈帧弹出,这部分内存自动释放,不需要GC;如果将其赋值给堆中的对象成员,那这部分内存属于堆对象的一部分,会由GC统一管理。
直接传入字面量的优化
使用unsafe.putInt(address, 3);时,JVM会把字面量3直接作为方法参数传递,不需要在栈上额外分配myInteger变量的4字节空间,确实能减少这部分栈内存开销。此时写入操作的总开销为栈上8字节地址 + 堆外4字节 = 12字节,和C/C++常规写法的开销一致。
内容的提问来源于stack exchange,提问作者user438454534
相关产品推荐
相关产品推荐

