我们来看底层实现:对于所有DirectByteBuffer的读写,都用到了Unsafe类的public native void putByte(Object o, long offset, byte x);
方法,底层实现是:
UNSAFE_ENTRY(void, Unsafe_SetNative##Type(JNIEnv *env, jobject unsafe, jlong addr, java_type x)) \
UnsafeWrapper("Unsafe_SetNative"#Type); \
JavaThread* t = JavaThread::current(); \
t->set_doing_unsafe_access(true); \
//获取地址
void* p = addr_from_java(addr); \
//设置值
*(volatile native_type*)p = x; \
t->set_doing_unsafe_access(false); \
UNSAFE_END \
那么这个获取地址的方法是啥样子呢?
inline void* addr_from_java(jlong addr) {
// This assert fails in a variety of ways on 32-bit systems.
// It is impossible to predict whether native code that converts
// pointers to longs will sign-extend or zero-extend the addresses.
//assert(addr == (uintptr_t)addr, "must not be odd high bits");
//转换为int
return (void*)(uintptr_t)addr;
}
这里我们看到,转换地址会被强制转换为int类型,所以只能映射 2GB - 1B 。
但是为何-XX:MaxDirectMemory
可以指定比2G
大的值呢?因为对于分配的直接内存中的 buffer,有对一个 BitMap 管理他们的基址,可以保证映射出对的地址,类似于堆内存的基址映射。但是对于文件映射内存,JVM 没有维护这么一个基址,或者说觉得没必要(一般不会有直接操作这么大文件的这么大内容的需求,大于2GB-1B
我们多映射两次自己维护就行了)。