原理
一个简单的JVM的对内存的情况如下所示。
可以看见,原来在堆中分配的永久代被移动到了meta区。而meta是单独的空间,使用JVM的直接内存。
然而用户其实也可以使用直接内存。
直接内存:direct/off-heap/native memory ,大家怎么叫都可以。用户可以使用如下方式使用直接内存:
使用未公开的Unsafe
NIO包下ByteBuffer。`
- 如果使用unsafe,那就只能自己负责内存的申请和清除了
public native long allocateMemory(long bytes);
public native long reallocateMemory(long address, long bytes);
public native void freeMemory(long address);
- 如果使用ByteBuffer。那么可以GC可以在完成队内存垃圾收集的同时帮助你清除。
使用ByteBuffer类分配直接内存
/**
* Allocates a new direct byte buffer.
*
* <p> The new buffer's position will be zero, its limit will be its
* capacity, its mark will be undefined, each of its elements will be
* initialized to zero, and its byte order will be
* {@link ByteOrder#BIG_ENDIAN BIG_ENDIAN}. Whether or not it has a
* {@link #hasArray backing array} is unspecified.
*
* @param capacity
* The new buffer's capacity, in bytes
*
* @return The new byte buffer
*
* @throws IllegalArgumentException
* If the {@code capacity} is a negative integer
*/
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
在DirectByteBuffer中的实现其实还是依靠了UNSAFE的代码。
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);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
在引用被消除后,会调用回掉方法,使用Unsafe代码将内存释放掉。这个是直接内存的释放的原理。
private static class Deallocator
implements Runnable
{
...
public void run() {
if (address == 0) {
// Paranoia
return;
}
UNSAFE.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
}
}
应用
大名鼎鼎的零拷贝技术
比如kafka。(注意,kafka没有用netty)
Netty
netty是对java nio的包装。