什么是 Java 中的直接内存(堆外内存)?

Sherwin.Wei Lv8

什么是 Java 中的直接内存(堆外内存)?

回答重点

Java 中的直接内存(Direct Memory)是由操作系统分配的内存区域,它不受 JVM 堆内存管理限制(是堆外内存)。直接内存是通过 java.nio 包中的 ByteBuffer.allocateDirect() 方法分配的,它可以绕过 JVM 垃圾回收机制,直接与本地系统内存交互。

扩展知识

直接内存的优势

由于直接内存不需要在堆上进行分配和复制数据,因此与操作系统的 I/O 操作(如文件读写、网络传输)时可以减少一次复制,提升性能,在文件读写和网络传输场景直接内存有很大的优势。

性能优化策略

  • 使用缓存机制:可以缓存 ByteBuffer.allocateDirect() 分配的缓冲区,减少频繁的直接内存分配。
  • 合理设置 JVM 参数:堆外内存不归 JVM 设置的堆大小限,但是可以通过设置 -XX:MaxDirectMemorySize 来设置直接内存的最大使用量,避免不必要的内存消耗。

直接内存和堆内存的区别

  • 分配位置:堆内存由 JVM 管理,受 GC 控制;直接内存由操作系统分配,使用本地内存。
  • 访问速度:直接内存的访问速度在特定场景下更快,因为减少了堆内存到本地内存的复制过程。
  • 回收机制:堆内存由垃圾回收器自动回收,而直接内存的回收需要通过调用 ByteBuffercleaner 方法进行清理。

直接内存使用示例

在 Java 中可以利用 Unsafe 类和 NIO 类库使用直接内存。

例如利用 NIO 的 ByteBuffer.allocateDirect(1024) 即可分配得到一个直接内存。

简单示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

public class DirectMemoryExample {
public static void main(String[] args) {
// 分配直接内存
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);

// 写入数据
directBuffer.put("Hello, 面试鸭 Direct Memory!".getBytes());

// 切换为读模式
directBuffer.flip();

// 读取数据
byte[] bytes = new byte[directBuffer.remaining()];
directBuffer.get(bytes);

// 打印结果
String retrievedData = new String(bytes, StandardCharsets.UTF_8);
System.out.println(retrievedData);

// 手动释放直接内存
((sun.nio.ch.DirectBuffer)directBuffer).cleaner().clean();
}
}

注意最后一行释放内存的 cleaner 。因为垃圾回收器无法直接管理堆外内存,所以 JVM 在创建 ByteBuffer 的时候,在堆内存储了这个对象的指针,然后注册了一个关联的 cleaner(清理器)。

可以看到,源码里面绑定了一个 cleaner:

企业微信截图_48f9c9da-1927-4c6f-a96a-889bb7dcee91.png

而这个 cleaner 是个虚引用(详细看面试鸭关于虚引用的面试题):

企业微信截图_b44d9298-401e-468f-9dd2-8d6ca9f90c62.png

如果 JVM 检测到没有对象关联 ByteBuffer,说明这个堆外内存已经成为了垃圾,此时 ByteBuffer 会被回收,然后 cleaner 会被加入到引用队列中,之后会就会被触发其 clean 接口,然后清理堆外内存。

Comments