JVM--直接内存(Direct Memory)

JVM–直接内存(Direct Memory)

背景

  • JVM–直接内存(Direct Memory)

  • 博主以黑马JVM进行学习

定义

  • 直接内存(Direct Memory):

    • 不属于 JVM 运行时数据区(堆 / 栈 / 方法区等),而是操作系统的本地内存(用户态的直接内存);

    • 核心场景:NIO 操作时的 ByteBuffer.allocateDirect() 会分配直接内存,作为数据缓冲区;

    • 特性:分配 / 回收的成本高(需调用系统调用),但读写性能高(减少内存复制);

    • 管理:不受 JVM 垃圾回收(GC)直接管理,JVM 堆 GC 不会主动回收直接内存;

    • 核心 API:java.nio.ByteBufferallocateDirect() 分配直接内存,allocate() 分配堆内存)。

为什么使用了直接内存,读写文件的效率会变高?

  • java不能直接调用系统内存,有专门的java堆内存,java可以直接访问direct memory,少了一次复制操作
  • 总之就是减少 “内存复制” 次数,对比堆内存和直接内存的文件读写流程:
  • 堆内存(Heap Buffer)读写文件流程:
    • 缺陷:数据需从「内核缓冲区 → 堆内存 → Java 程序」,经历两次复制
    • 额外问题:堆内存中的 ByteBuffer 对象是 GC 管理的,若数据量大,会触发 GC,且堆内存到内核缓冲区的复制是 “冗余操作”。

  • 直接内存(Direct Buffer)读写文件流程:
    • 直接内存是「用户态」和「内核态」共享的内存区域,数据从内核缓冲区直接映射到直接内存,仅一次复制
    • Java 程序可直接访问直接内存(无需经过 JVM 堆的复制),大幅提升 IO 效率;
    • 避免堆内存频繁 GC 对 IO 性能的影响。

直接内存溢出

  • OutOfMemoryError: Direct buffer memory

  • 溢出原因:

    • 直接内存有上限(默认与堆最大值 -Xmx 一致,可通过 -XX:MaxDirectMemorySize 设置);
    • 频繁分配直接内存但未及时释放,导致超出上限;
    • 禁用显式 GC(-XX:+DisableExplicitGC)后,Direct ByteBuffer 的 Cleaner 机制失效,直接内存无法及时回收。

直接内存的释放原理

  • 分配内存
1
2
3
Unsafe unsafe = getUnsafe();
long base = unsafe.allocateMemory(_1Gb);
unsafe.setMemory(base,_1Gb,(byte) 0);
  • 释放内存
1
unsafe.freeMemory(base);
  • 不是通过gc管理,而是根据Unsafe这个底层类进行管理的

  • Unsafe类:干分配内存,释放内存的活

  • 借用了java中虚引用机制

  • 结论:

    • 使用了Unsafe对象完成了直接内存的分配回收,并回收需要主动调用freeMemory方法
    • ByteBuffer的实现类内部,使用了Cleaner(虚引用)来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存

禁用显示回收对直接内存的影响

  • -XX:+DisableExplicitGC 禁用显示回收

  • 代码中System.gc()就是一种显示的垃圾回收,会触发Full GC(牺牲性能),而 Full GC 时会清理堆中无用的 Direct ByteBuffer 对象,进而触发 Cleaner 释放直接内存;

  • 解决方法:

    • 避免使用 ByteBuffer.allocateDirect(),改用堆内存(ByteBuffer.allocate());
    • 手动通过 Unsafe.freeMemory () 释放直接内存(不依赖 GC);
    • 保留显式 GC(不设置 -XX:+DisableExplicitGC),牺牲少量性能换取内存安全。
  • 对比

维度 直接内存 堆内存
归属 操作系统本地内存 JVM 运行时数据区
GC 管理 不直接管理(依赖 Cleaner) 直接管理(Minor/Major GC)
分配 / 回收成本 高(系统调用) 低(JVM 内部操作)
读写性能 高(少一次复制) 低(多一次复制)
  • Direct ByteBuffer 的内存占用
    • Direct ByteBuffer 对象本身存储在堆中(仅占少量内存,记录直接内存的地址);
    • 真正的大数据存储在直接内存(操作系统本地内存)
    • JVM 堆 GC 回收的是 Direct ByteBuffer 对象,而非直接内存本身。

总结

  • 直接内存是操作系统本地内存,不属于 JVM 堆,NIO 中使用可减少内存复制,提升 IO 效率;
  • 直接内存的分配依赖 Unsafe.allocateMemory (),释放依赖「Cleaner(虚引用)+ ReferenceHandler 线程 + Unsafe.freeMemory ()」;
  • 直接内存溢出会报 Direct buffer memory,可通过 -XX:MaxDirectMemorySize 调整上限;
  • -XX:+DisableExplicitGC 会导致 Cleaner 机制失效,需手动释放直接内存或保留显式 GC;
  • 直接内存分配 / 回收成本高但读写性能高,适合大文件 / 高并发 IO 场景。