直接内存(Direct Memory)又叫做堆外内存,和堆内内存相对应,堆外内存就是把内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。
PS:元空间对应的内存也叫作直接内存,它们对应的都是机器的物理内存。
在 java.nio 包中提供了 DirectByteBuffer 可以用来分配一块直接内存(堆外内存)。从名字就可以看出 DirectByteBuffer 是创建的字节类型缓冲区(ByteBuffer),所以,在 ByteBuffer 中提供以下两个分配内存的方法:
- allocate():分配一块堆内存
- allocateDirect():来分配一块直接内存

DirectByteBuffer 里面保存了这块直接内存的起始地址和大小信息;而 HeapByteBuffer 继承了 ByteBuffer,所以,它实际是有一个数组来暂存数据,等读写完成后再刷入(flush)到直接内存中。
注:其余 Buffer,比如 IntBuffer、LongBuffer 都只提供了基于内存的分配方式,并没有提供 allocateDirect() 方法。
1.直接内存性能分析
下面我们来通过代码来看看堆内存和直接内存在分配速度和访问的区别…
1.1 分配速度
public class TestDirectMemory01 {
/**
* 分配10w次100字节大小的堆内存
*/
public static void heapAllocate() {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
// 分配堆内存
ByteBuffer.allocate(100);
}
long endTime = System.currentTimeMillis();
System.out.println("堆内存申请:" + (endTime - startTime));
}
/**
* 分配10w次100字节大小的直接内存
*/
public static void directAllocate() {
long startTime = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
// 分配直接内存
ByteBuffer.allocateDirect(100);
}
long endTime = System.currentTimeMillis();
System.out.println("直接内存申请:" + (endTime - startTime));
}
public static void main(String args[]) {
for (int i = 0; i < 10; i++) {
heapAllocate();
directAllocate();
}
}
}

1.2 访问速度
public class TestDirectMemory02 {
/**
* 访问10w次堆内存
*/
public static void heapAccess() {
long startTime = System.currentTimeMillis();
// 分配堆内存
ByteBuffer buffer = ByteBuffer.allocate(1000);
for (int i = 0; i < 100000; i++) {
// 写
for (int j = 0; j < 200; j++) {
buffer.putInt(j);
}
buffer.flip(); // 锁定
// 读
for (int j = 0; j < 200; j++) {
buffer.getInt();
}
buffer.clear(); // 清空
}
long endTime = System.currentTimeMillis();
System.out.println("堆内存访问:" + (endTime - startTime));
}
/**
* 访问10w次直接内存
*/
public static void directAccess() {
long startTime = System.currentTimeMillis();
// 分配直接内存
ByteBuffer buffer = ByteBuffer.allocateDirect(1000);
for (int i = 0; i < 100000; i++) {
for (int j = 0; j < 200; j++) {
buffer.putInt(j);
}
buffer.flip();
for (int j = 0; j < 200; j++) {
buffer.getInt();
}
buffer.clear();
}
long endTime = System.currentTimeMillis();
System.out.println("直接内存访问:" + (endTime - startTime));
}
public static void main(String args[]) {
for (int i = 0; i < 10; i++) {
heapAccess();
directAccess();
}
}
}
小结
从上面两个程序运行结果看出,直接内存申请较慢,但访问效率高。在 Java 虚拟机实现上,本地IO会直接操作直接内存(直接内存<=系统调用=>硬盘/网卡),而非直接内存的读写则需要二次拷贝(堆内存<=>直接内存<=系统调用=>硬盘/网卡)。
2.直接内存分配源码分析
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
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));
// 判断是否有足够的直接内存空间分配,可通过‐XX:MaxDirectMemorySize=<size>参数指定直接内存最大可分配空间,如果不指定默认为最 大堆内存大小,
// 在分配直接内存时如果发现空间不够会显示调用System.gc()触发一次full gc回收掉一部分无用的直接内存的引用对象,同时直接内存也会 被释放掉
// 如果释放完分配空间还是不够会抛出异常java.lang.OutOfMemoryError
Bits.reserveMemory(size, cap);
long base = 0;
try {
// 调用unsafe本地方法分配直接内存
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 boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
// 使用Cleaner机制注册内存回收处理函数,当直接内存引用对象被GC清理掉时,
// 会提前调用这里注册的释放直接内存的Deallocator线程对象的run方法
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
// 申请一块本地内存。内存空间是未初始化的,其内容是无法预期的。
// 使用freeMemory释放内存,使用reallocateMemory修改内存大小
public native long allocateMemory(long var1);
我们看一下 JVM 底层对 allocateMemory 方法的实现
// openjdk8/hotspot/src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(jlong, Unsafe_AllocateMemory(JNIEnv *env, jobject unsafe, jlong size))
UnsafeWrapper("Unsafe_AllocateMemory");
size_t sz = (size_t)size;
if (sz != (julong)size || size < 0) {
THROW_0(vmSymbols::java_lang_IllegalArgumentException());
}
if (sz == 0) {
return 0;
}
sz = round_to(sz, HeapWordSize);
// 调用os::malloc申请内存,内部使用malloc这个C标准库的函数申请内存
void* x = os::malloc(sz, mtInternal);
if (x == NULL) {
THROW_0(vmSymbols::java_lang_OutOfMemoryError());
}
// opy::fill_to_words((HeapWord*)x, sz / HeapWordSize);
return addr_to_java(x);
UNSAFE_END
3.直接内存优缺点
优点:
- 不占用堆内存空间,减少了发生GC的可能
- Java虚拟机实现上,本地IO会直接操作直接内存(直接内存<=
系统调用=>硬盘/网卡),而非直接内存的读写则需要二次拷贝(堆内存<=>直接内存<=系统调用=>硬盘/网卡)
缺点:
- 初始分配较慢
- 没有JVM直接帮助管理内存,容易发生内存溢出。
为了避免一直没有 FULL GC,最终导致直接内存把物理内存被耗完,我们可以指定直接内存的最大值,通过-XX:MaxDirectMemorySize来指定,当达到阈值的时候,调用system.gc来进行一次FULL GC, 间接把那些没有被使用的直接内存回收掉。
注:-XX:MaxDirectMemorySize 指定的是方法区之外的直接内存大小;如果要指定方法区这块直接内存的大小通过 -XX:MetaspaceSize 来设置。








还没有评论,来说两句吧...