【JVM原理详解】16-直接内存与NIO
直接内存与NIO
前面几篇讨论的运行时数据区——堆、栈、方法区——都在JVM的内存管理范畴内。但有一块内存游离于JVM堆之外,却对Java的高性能IO至关重要——直接内存(Direct Memory)。它不是JVM运行时数据区的一部分,却通过NIO的DirectByteBuffer与Java应用紧密协作。本篇将剖析直接内存的概念、NIO ByteBuffer的设计、Unsafe.allocateMemory的底层原理、DirectByteBuffer的Cleaner回收机制、零拷贝技术,以及堆外内存的实践要点。
直接内存的概念
什么是直接内存
直接内存(Direct Memory)是指通过Java代码直接向操作系统申请的堆外内存(Off-Heap Memory),不归JVM垃圾回收器管理,也不受-Xmx堆大小限制(但受-XX:MaxDirectMemorySize约束)。
JVM 进程内存布局
┌──────────────────────────────────────────────┐
│ JVM 进程内存 │
│ │
│ ┌──────────────────┐ ┌────────────────┐ │
│ │ Java堆 │ │ 直接内存 │ │
│ │ (-Xmx 控制) │ │ (堆外内存) │ │
│ │ ┌──────────────┐ │ │ ┌────────────┐ │ │
│ │ │ 新生代 老年代 │ │ │ │ DirectByteBuffer│ │
│ │ └──────────────┘ │ │ │ 管理的内存 │ │ │
│ └──────────────────┘ │ └────────────┘ │ │
│ └────────────────┘ │
│ ┌──────────────────┐ ┌────────────────┐ │
│ │ 元空间 │ │ 线程栈 │ │
│ └──────────────────┘ └────────────────┘ │
└──────────────────────────────────────────────┘
直接内存与JVM堆的关键区别:
| 对比维度 | Java堆 | 直接内存 |
|---|---|---|
| 分配方式 | new关键字,JVM管理 |
ByteBuffer.allocateDirect()或Unsafe |
| GC管理 | 由GC自动回收 | 通过Cleaner机制间接回收 |
| 大小限制 | -Xmx |
-XX:MaxDirectMemorySize |
| 数据拷贝 | 与IO操作需经过堆 | 可直接与IO通道交互 |
| 分配速度 | 快(TLAB内) | 慢(系统调用) |
为什么需要直接内存
Java传统的IO(java.io包)是基于流的,读写数据需要经过JVM堆中转:
传统IO读文件:
操作系统内核缓冲区 ──拷贝──→ Java堆中的byte[]
read()系统调用:
磁盘 → 内核页缓存 → 用户态堆内存
↑
一次数据拷贝 (内核→用户态)
这种模式下,数据从内核缓冲区拷贝到Java堆中,多了一次内存拷贝。对于高吞吐的IO场景(如网络中间件、大数据处理),这个开销不可忽视。
直接内存的思路是:在堆外分配一块内存,让操作系统直接将数据读入这块内存,Java代码直接操作这块内存,避免了内核缓冲区到堆的拷贝。
NIO DirectByteBuffer读文件:
操作系统内核缓冲区 ──直接写入──→ 直接内存 (堆外)
read()系统调用:
磁盘 → 内核页缓存 → 直接内存 (堆外)
↑
Java代码直接操作
NIO ByteBuffer
HeapByteBuffer vs DirectByteBuffer
Java NIO的ByteBuffer有两种实现:
// 适用: JDK 8/11/17
import java.nio.ByteBuffer;
public class ByteBufferDemo {
public static void main(String[] args) {
// 1. 堆内缓冲区 (HeapByteBuffer)
ByteBuffer heapBuf = ByteBuffer.allocate(1024);
// 底层是 byte[] array, 在Java堆中分配
// 优点: 分配快, GC管理
// 缺点: IO操作需额外拷贝
// 2. 直接缓冲区 (DirectByteBuffer)
ByteBuffer directBuf = ByteBuffer.allocateDirect(1024);
// 底层是堆外内存, 通过Unsafe.allocateMemory分配
// 优点: IO零拷贝
// 缺点: 分配慢, 回收不确定
System.out.println(heapBuf.isDirect()); // false
System.out.println(directBuf.isDirect()); // true
// 3. 包装已有数组的缓冲区 (非直接)
byte[] data = new byte[1024];
ByteBuffer wrappedBuf = ByteBuffer.wrap(data);
System.out.println(wrappedBuf.isDirect()); // false
}
}
两种Buffer的IO路径差异
理解性能差异需要看IO操作的完整路径:
HeapByteBuffer 的 write() 路径:
┌──────────┐ 拷贝 ┌──────────┐ write() ┌──────────┐
│ Java堆 │ ──────────→ │ 临时直接 │ ──────────→ │ 内核缓冲 │
│ byte[] │ │ 内存 │ │ 区 │
└──────────┘ └──────────┘ └──────────┘
↑
JVM内部临时分配
(每次IO都需拷贝)
DirectByteBuffer 的 write() 路径:
┌──────────┐ write() ┌──────────┐
│ 直接内存 │ ──────────────────────────→ │ 内核缓冲 │
│ (堆外) │ │ 区 │
└──────────┘ └──────────┘
↑
无需额外拷贝, 直接传内存地址给操作系统
当HeapByteBuffer进行IO操作时,JVM为了防止IO过程中GC移动堆内存导致地址失效,会临时分配一块直接内存,将堆中数据拷贝过去,再进行IO。这个临时分配和拷贝在每次IO调用时都发生。
而DirectByteBuffer本身就是堆外内存,地址固定,IO操作直接使用其内存地址,无需额外拷贝。
代码示例:性能对比
// 适用: JDK 8/11/17
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.io.*;
public class BufferPerfCompare {
private static final int BUFFER_SIZE = 64 * 1024; // 64KB
private static final long FILE_SIZE = 500 * 1024 * 1024; // 500MB
// 使用HeapByteBuffer写文件
public static long writeWithHeap(File f) throws Exception {
long start = System.nanoTime();
try (FileChannel ch = new FileOutputStream(f).getChannel()) {
ByteBuffer buf = ByteBuffer.allocate(BUFFER_SIZE);
while (buf.remaining() > 0 || ch.position() < FILE_SIZE) {
if (!buf.hasRemaining()) buf.clear();
buf.put((byte) 1);
if (!buf.hasRemaining() || ch.position() + buf.position() >= FILE_SIZE) {
buf.flip();
ch.write(buf);
buf.clear();
}
}
}
return System.nanoTime() - start;
}
// 使用DirectByteBuffer写文件
public static long writeWithDirect(File f) throws Exception {
long start = System.nanoTime();
try (FileChannel ch = new FileOutputStream(f).getChannel()) {
ByteBuffer buf = ByteBuffer.allocateDirect(BUFFER_SIZE);
while (buf.remaining() > 0 || ch.position() < FILE_SIZE) {
if (!buf.hasRemaining()) buf.clear();
buf.put((byte) 1);
if (!buf.hasRemaining() || ch.position() + buf.position() >= FILE_SIZE) {
buf.flip();
ch.write(buf);
buf.clear();
}
}
}
return System.nanoTime() - start;
}
public static void main(String[] args) throws Exception {
File f1 = new File("test_heap.dat");
File f2 = new File("test_direct.dat");
// 预热
System.out.println("HeapByteBuffer: " + writeWithHeap(f1) / 1_000_000 + " ms");
System.out.println("DirectByteBuffer: " + writeWithDirect(f2) / 1_000_000 + " ms");
f1.delete(); f2.delete();
}
}
典型输出(500MB文件,64KB缓冲区):
HeapByteBuffer: 约 1200-1500 ms
DirectByteBuffer: 约 800-1000 ms
差距会随IO量增大而明显。但注意:对于小数据量、单次IO,DirectByteBuffer的分配开销可能抵消零拷贝的收益。
选择策略
| 场景 | 推荐Buffer | 原因 |
|---|---|---|
| 大文件读写、高吞吐网络IO | DirectByteBuffer | 零拷贝收益大 |
| 小数据量、频繁分配 | HeapByteBuffer | 分配快,GC即回收 |
| 需要频繁随机访问数组内容 | HeapByteBuffer | 堆内访问快,有数组边界优化 |
| 长期复用的IO缓冲区 | DirectByteBuffer | 复用避免重复分配开销 |
| 一次性的短生命周期缓冲区 | HeapByteBuffer | 避免直接内存分配+回收的额外成本 |
Unsafe.allocateMemory
底层分配机制
DirectByteBuffer的底层内存分配依赖sun.misc.Unsafe类:
// HotSpot JDK 8 中 DirectByteBuffer 的核心源码 (简化)
class DirectByteBuffer extends MappedByteBuffer {
// Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 堆外内存地址
private long address;
// Cleaner用于回收
private final Cleaner cleaner;
DirectByteBuffer(int cap) {
super(-1, 0, cap, cap);
// 通过BitMap分配内存, 记录全局直接内存使用量
boolean pa = VM.isDirectBufferMemorySettingAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? pageSize : 0));
// 关键: 调用Unsafe分配堆外内存
base = unsafe.allocateMemory(size);
// 内存清零
unsafe.setMemory(base, size, (byte) 0);
// 记录内存地址
address = unsafe.realAddress(base);
// 注册Cleaner, GC时通过Deallocator释放内存
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
}
}
Unsafe.allocateMemory的native实现最终调用操作系统的malloc(或mmap):
Java层: ByteBuffer.allocateDirect(1024)
↓
DirectByteBuffer构造函数
↓
Bits.reserveMemory() // 检查直接内存配额
↓
Unsafe.allocateMemory(size) // native方法
↓
os::malloc(size) // HotSpot C++层
↓
malloc() / mmap() // 操作系统系统调用
直接使用Unsafe分配内存
可以直接使用Unsafe操作堆外内存(生产不推荐,仅用于理解原理):
// 适用: JDK 8 (JDK 9+ 需要模块化反射访问)
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeMemoryDemo {
public static void main(String[] args) throws Exception {
// 通过反射获取Unsafe实例 (Unsafe.getUnsafe()会检查调用者)
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
// 分配100字节堆外内存
long address = unsafe.allocateMemory(100);
System.out.println("分配地址: 0x" + Long.toHexString(address));
// 写入数据
unsafe.putLong(address, 123456789L); // 写8字节long
unsafe.putByte(address + 8, (byte) 42); // 写1字节byte
unsafe.putInt(address + 9, 1000); // 写4字节int
// 读取数据
long val1 = unsafe.getLong(address);
byte val2 = unsafe.getByte(address + 8);
int val3 = unsafe.getInt(address + 9);
System.out.println(val1 + ", " + val2 + ", " + val3);
// 输出: 123456789, 42, 1000
// 释放内存 (必须手动释放, 否则内存泄漏!)
unsafe.freeMemory(address);
}
}
关键点:
allocateMemory返回的是内存地址(long类型的指针),不是Java对象- 堆外内存不受GC管理,必须手动
freeMemory释放 - 没有 bounds checking,越界访问直接crash JVM
- JDK 9+对
sun.misc.Unsafe做了模块化封装,推荐使用VarHandleAPI
直接内存配额检查
Bits.reserveMemory()在分配前检查直接内存配额:
分配流程:
1. Bits.reserveMemory(size)
├── 检查 总已用 + size <= MaxDirectMemorySize?
├── 如果超限:
│ ├── 触发System.gc() (希望GC回收无用的DirectByteBuffer)
│ ├── 等待一段时间
│ └── 如果还不够 → OutOfMemoryError: Direct buffer memory
└── 如果OK: 增加全局计数器
2. unsafe.allocateMemory(size) → 实际分配
这就是为什么设置了-XX:MaxDirectMemorySize后,直接内存OOM前通常会先看到一次Full GC——JVM在尝试回收闲置的DirectByteBuffer。
-XX:MaxDirectMemorySize
参数说明
# 限制直接内存最大为512MB
java -XX:MaxDirectMemorySize=512m -jar app.jar
| 参数 | 默认值 | 说明 |
|---|---|---|
-XX:MaxDirectMemorySize |
约等于-Xmx |
直接内存上限 |
默认值与-Xmx一致(不严格相等,略有偏差)。这个默认值的设计初衷是:大多数应用不会大量使用直接内存,设为与堆相同比较宽松。但在使用Netty等IO框架时,直接内存使用量可能很大,需要单独设置。
OOM复现
// 适用: JDK 8/11/17
// 运行: java -XX:MaxDirectMemorySize=64m DirectOOM
import java.nio.ByteBuffer;
public class DirectOOM {
public static void main(String[] args) {
java.util.List<ByteBuffer> list = new java.util.ArrayList<>();
try {
while (true) {
// 每次分配1MB直接内存, 保持引用防止回收
ByteBuffer buf = ByteBuffer.allocateDirect(1024 * 1024);
list.add(buf);
System.out.println("已分配: " + list.size() + " MB");
}
} catch (OutOfMemoryError e) {
e.printStackTrace();
// java.lang.OutOfMemoryError: Direct buffer memory
}
}
}
输出:
已分配: 1 MB
已分配: 2 MB
...
已分配: 62 MB
java.lang.OutOfMemoryError: Direct buffer memory
直接内存的监控
# 查看直接内存使用 (JMX方式, 通过jcmd)
jcmd <pid> GC.class_histogram
# 通过jstat无法直接查看直接内存, 需用JMX
# 使用jconsole或JVisualVM的MBean查看:
# java.nio:type=BufferPool,name=direct
# → count, memoryUsed, totalCapacity
# Arthas查看
[arthas@1234]$ memory # 显示各内存区域, 包括direct
# Native内存追踪 (JDK 11+)
jcmd <pid> VM.native_memory summary
# 查看Internal部分 (包含DirectMemory)
JDK 11+的NMT(Native Memory Tracking)可以追踪本地内存使用:
# 启动时开启NMT
java -XX:NativeMemoryTracking=summary -jar app.jar
# 查看直接内存
jcmd <pid> VM.native_memory summary | grep -A5 "Internal"
DirectByteBuffer的Cleaner机制
PhantomReference + Cleaner
DirectByteBuffer本身是一个Java对象(在堆中),它引用了一块堆外内存。当DirectByteBuffer对象被GC回收时,堆外内存也需要被释放——这就是Cleaner机制的作用。
Java堆 堆外内存
┌──────────────────┐ ┌──────────────┐
│ DirectByteBuffer │ ──address───→ │ 1MB内存 │
│ (Java对象) │ │ (malloc分配) │
└──────────────────┘ └──────────────┘
↑
GC可达时: 对象存活, 堆外内存保留
GC不可达时: Cleaner触发回收
Cleaner机制的核心是虚引用(PhantomReference):
// HotSpot JDK 8 中 Cleaner 的核心源码 (简化)
public class Cleaner extends PhantomReference<Object> {
private static final ReferenceQueue<Object> dummyQueue = new ReferenceQueue<>();
private Cleaner next, prev;
private final Runnable thunk; // 回收动作
// 创建Cleaner: obj被GC回收时, 执行thunk
public static Cleaner create(Object obj, Runnable thunk) {
return add(new Cleaner(obj, thunk));
}
// ReferenceHandler线程轮询, 发现Cleaner被回收时执行
@Override
public void clean() {
if (remove(this)) {
try {
thunk.run(); // 执行Deallocator.run()
} catch (Throwable x) {
...
}
}
}
}
DirectByteBuffer的Deallocator:
// Deallocator: 实际释放堆外内存的Runnable
private static class Deallocator implements Runnable {
private long address;
private long size;
private int capacity;
public void run() {
if (address == 0) return;
// 调用Unsafe释放堆外内存
unsafe.freeMemory(address);
// 减少全局直接内存计数
Bits.unreservedMemory(size, capacity);
address = 0;
}
}
回收的完整流程
1. DirectByteBuffer对象变为GC不可达
(应用不再引用该buffer)
2. GC扫描发现DirectByteBuffer可回收
├── 普通对象: 直接回收堆内内存
└── 有Cleaner的对象: 将Cleaner加入ReferenceQueue
3. ReferenceHandler后台线程 (优先级最高)
├── 轮询ReferenceQueue
├── 发现Cleaner
└── 调用Cleaner.clean() → Deallocator.run()
└── unsafe.freeMemory(address) // 释放堆外内存
4. 堆外内存被释放, 返还给操作系统
关键点:
-
堆内对象和堆外内存的回收是异步的——DirectByteBuffer对象在GC时回收,但堆外内存要等ReferenceHandler线程执行
freeMemory才释放。 -
ReferenceHandler是JVM的内部线程(
java.lang.ref.Reference$ReferenceHandler),优先级最高(MAX_PRIORITY),确保及时处理引用。 -
如果DirectByteBuffer一直被引用,堆外内存不会被释放。常见场景:Netty的ByteBuf未release导致内存泄漏。
-
System.gc()的配合:直接内存分配失败时,JVM会调用
System.gc()触发Full GC,期望回收不可达的DirectByteBuffer。如果应用禁用了System.gc()(-XX:+DisableExplicitGC),可能导致直接内存OOM提前出现。
Cleaner vs finalize
Cleaner机制与finalize()方法有相似之处(都是对象回收时的回调),但有关键区别:
| 对比 | finalize() | Cleaner/PhantomReference |
|---|---|---|
| 引用阶段 | FinalReference (强/弱) | PhantomReference (虚) |
| 调用时机 | GC前加入Finalizer队列 | GC后进入ReferenceQueue |
| 对象状态 | 对象可能被"复活" | 对象已确定被回收 |
| 线程 | Finalizer线程 | ReferenceHandler线程 |
| JDK状态 | JDK 9+废弃 | JDK 9+推荐替代finalize |
虚引用的优势:对象进入Cleaner时已经被GC判定为不可达,不会被"复活",回收更安全。而finalize()中可以重新建立引用,导致对象复活,引发难以排查的问题。
零拷贝
零拷贝的含义
“零拷贝”(Zero Copy)不是真的零次拷贝,而是指减少CPU参与的数据拷贝次数,将拷贝交给DMA(Direct Memory Access)硬件完成。
传统read+write (4次拷贝, 4次上下文切换):
用户态 内核态
┌─────┐ read() ┌────────┐
│ app │ ────────→ │ 读取磁盘│
│ │ │ 内核缓冲│
│ │ ←──────── │ ↓ │
│ │ 数据拷贝 │ 拷贝到 │
│ buf │ │ 用户态 │
│ │ write() │ ↓ │
│ │ ────────→ │ 拷贝回 │
│ │ │ 内核缓冲│
│ │ │ ↓ │
│ │ │ DMA→网卡│
└─────┘ └────────┘
零拷贝transferTo (2次拷贝, 2次上下文切换):
用户态 内核态
┌─────┐ transferTo ┌────────┐
│ app │ ────────→ │ DMA读磁盘│
│ │ │ ↓ │
│ │ │ 内核缓冲 │
│ │ │ ↓ │
│ │ │ DMA→网卡 │
└─────┘ └────────┘
↑
数据从不进入用户态
transferTo / transferFrom
FileChannel.transferTo()利用操作系统的sendfile系统调用,实现文件到网络的零拷贝:
// 适用: JDK 8/11/17
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.io.*;
public class ZeroCopyDemo {
// 零拷贝: 文件 → 网络
public static void sendFile(FileChannel fileCh,
SocketChannel socketCh) throws IOException {
long transferred = 0;
long fileSize = fileCh.size();
// transferTo: 内部调用 sendfile (Linux) / TransmitFile (Windows)
while (transferred < fileSize) {
transferred += fileCh.transferTo(
transferred, fileSize - transferred, socketCh);
}
}
// 零拷贝: 网络 → 文件
public static void recvFile(SocketChannel socketCh,
FileChannel fileCh) throws IOException {
fileCh.transferFrom(socketCh, 0, Long.MAX_VALUE);
}
}
常见零拷贝技术对比
| 技术 | 系统调用 | 拷贝次数 | 适用场景 |
|---|---|---|---|
| 传统read+write | read+write | 4次(2次CPU+2次DMA) | 通用 |
| mmap+write | mmap+write | 3次(1次CPU+2次DMA) | 需修改数据 |
| sendfile | sendfile | 3次(1次CPU+2次DMA) | 文件→socket |
| sendfile+SG-DMA | sendfile (scatter-gather) | 2次(0次CPU+2次DMA) | 文件→socket,硬件支持 |
transferTo在Linux上优先使用sendfile+scatter-gather DMA,实现真正的零CPU拷贝。
零拷贝的性能优势
// 适用: JDK 8/11/17
// 对比传统IO传输 vs 零拷贝传输
import java.io.*;
import java.nio.channels.*;
public class ZeroCopyPerf {
private static final long FILE_SIZE = 1024 * 1024 * 500; // 500MB
// 传统IO: read到byte[]再write
public static long traditionalTransfer(
FileChannel src, WritableByteChannel dest) throws IOException {
long start = System.nanoTime();
ByteBuffer buf = ByteBuffer.allocate(64 * 1024);
while (src.read(buf) != -1) {
buf.flip();
dest.write(buf);
buf.clear();
}
return System.nanoTime() - start;
}
// 零拷贝: transferTo
public static long zeroCopyTransfer(
FileChannel src, WritableByteChannel dest) throws IOException {
long start = System.nanoTime();
src.transferTo(0, src.size(), dest);
return System.nanoTime() - start;
}
}
典型结果(500MB文件,本地传输):
传统IO: 约 1500-2000 ms
零拷贝: 约 400-600 ms
零拷贝优势随文件增大而放大。Kafka、Netty等高性能框架大量使用transferTo和FileChannel.map(mmap)实现零拷贝。
实践要点
-
合理设置
-XX:MaxDirectMemorySize:使用Netty、NIO框架的应用,直接内存可能占用较大。建议显式设置(如堆大小的1/2到1倍),并通过JMX监控实际使用量。不设置时默认与-Xmx一致,可能导致OOM Killer先于Java OOM触发。 -
慎用
-XX:+DisableExplicitGC:这个参数禁用System.gc(),但JVM在直接内存分配失败时会依赖System.gc()触发Full GC来回收闲置的DirectByteBuffer。禁用后可能导致直接内存OOM提前出现。如果必须禁用,建议配合-XX:MaxDirectMemorySize留足余量。 -
DirectByteBuffer的复用:直接内存分配开销大(系统调用+内存清零),适合长期复用。Netty使用
PooledDirectByteBufAllocator实现堆外内存池化,避免频繁分配释放。如果自行管理DirectByteBuffer,建议用对象池复用。 -
内存泄漏排查:DirectByteBuffer泄漏比堆内存泄漏更难排查——堆外内存不在heap dump中。排查方法:
- 使用JMX监控
java.nio:type=BufferPool,name=direct的memoryUsed - JDK 11+用
jcmd VM.native_memory summary查看Internal区域 - Netty提供了
ResourceLeakDetector检测ByteBuf泄漏 - 使用
-Dio.netty.leakDetection.level=PARANOID(Netty)
- 使用JMX监控
-
堆外内存的生命周期管理:DirectByteBuffer的回收依赖GC,如果应用
-XX:MaxDirectMemorySize设得大、堆也大、GC不频繁,直接内存可能堆积。建议在高吞吐IO场景下主动管理DirectByteBuffer的生命周期(如Netty的引用计数)。 -
DirectByteBuffer vs Unsafe的选择:业务代码应优先使用
ByteBuffer.allocateDirect,它有完整的配额管理和Cleaner回收机制。直接使用Unsafe.allocateMemory需要自行管理内存生命周期,容易泄漏,仅适合框架级开发。 -
JDK版本差异:JDK 9+对
sun.misc.Unsafe做了模块化封装,allocateMemory等方法在JDK 9-16通过反射仍可访问,JDK 17+严格限制。新代码应考虑使用MemorySegment(JDK 14+ incubator,JDK 16+正式),它是更安全的堆外内存API。 -
不要忽略ReferenceHandler线程:如果应用有大量DirectByteBuffer且频繁GC,ReferenceHandler线程可能成为瓶颈。监控其CPU占用,必要时减少DirectByteBuffer的创建频率。
小结
- 直接内存是堆外内存,不归GC管理,通过
-XX:MaxDirectMemorySize限制大小。它让IO操作无需经过Java堆中转,是高性能IO的基础。 - NIO ByteBuffer分HeapByteBuffer(堆内,分配快但IO有额外拷贝)和DirectByteBuffer(堆外,IO零拷贝但分配慢)。大文件和高吞吐网络IO用Direct,小数据用Heap。
- Unsafe.allocateMemory是直接内存分配的底层,最终调用
malloc。直接使用Unsafe需手动freeMemory,容易泄漏,应优先用ByteBuffer.allocateDirect。 - Cleaner机制通过PhantomReference在DirectByteBuffer被GC回收时释放对应的堆外内存,ReferenceHandler线程负责执行释放。回收是异步的,依赖GC触发。
- 零拷贝通过
transferTo/sendfile减少CPU参与的拷贝,将数据传输交给DMA,是Kafka、Netty等高性能框架的核心优化手段。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)