Java直接内存的介绍和实现
目录
1、直接内存介绍
1.1、定义
1.2、直接内存用法
1.3、java程序内存使用
2、使用原因
2.1、未使用直接内存
2.2、工作原理
3、直接内存和堆内存对比
4、回收与清理
5、实现
前言
Java中的内存从广义上可以划分为两个部分,一部分是受JVM管理的堆内存,另一部分则是不受JVM管理的堆外内存,也称为直接内存。
直接内存由操作系统来管理,这部分内存的应用可以减少垃圾收集GC对应用程序的影响。
如下所示:

在Java中,“直接内存”(Direct Memory)是Java堆外的一块内存区域。
它不是JVM堆(heap)的一部分,也不是物理内存独有的名字,而是指通过sun.misc.Unsafe(底层JNI)或java.nio.ByteBuffer.allocateDirect()分配的,由操作系统本地实现管理的内存区域。
关于更多JVM内存的介绍,可参考:
关于对JVM的知识整理_jvm知识-CSDN博客文章浏览阅读1.5k次,点赞24次,收藏27次。前言关于jvm的介绍,这块相对抽象一点,更多是底层的理论知识。通过对jvm的全方位的理解,可以加深我们在日常开发中代码的理解,以便于编写更健壮的逻辑代码。JVM是Java程序运行的核心组成部分,它负责将编译后的Java字节码(.class文件)转换为计算机能够理解的指令,从而执行Java程序。_jvm知识https://dyclt.blog.csdn.net/article/details/147593364?spm=1011.2415.3001.5331JVM 内存、JMM内存与集群机器节点内存的联系_节点核心内存与xmx30g关系-CSDN博客文章浏览阅读1.3k次,点赞30次,收藏16次。在日常开发过程中,不知道你是否考虑。_节点核心内存与xmx30g关系
https://dyclt.blog.csdn.net/article/details/148635597?spm=1011.2415.3001.5331
1、直接内存介绍
1.1、定义
直接内存(Direct Memory)是不受JVM垃圾回收器(GC)直接管理的内存。在系统内存和Java堆内存之间开辟出的一块共享区域,供操作系统和Java代码访问。
如下所示:
物理来源是系统的虚拟内存(Native Memory),而非JVM的Java对象堆。
1.2、直接内存用法
ByteBuffer.allocateDirect(int capacity)这会分配一块操作系统本地内存,然后返回一个Java层提供封装的DirectByteBuffer对象用以操作。
import java.nio.ByteBuffer;
import java.util.Scanner;public class BufferTest {private static final int BUFFER = 1024 * 1024 * 1024;//1GBpublic static void main(String[] args) {//直接分配本地内存空间ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);System.out.println("直接内存分配完毕,请求指示!");Scanner scanner = new Scanner(System.in);scanner.next();System.out.println("直接内存开始释放!");byteBuffer = null;System.gc();scanner.next();}
}
直接分配后的本地内存空间如图所示:
释放内存后的本地内存空间如图所示:
通常访问直接内存的速度会优于Java堆,读写性能更高。因此出于性能考虑,读写频繁的场合可能会考虑使用直接内存。Java的NIO库允许Java程序使用直接内存,用于数据缓冲区。
1.3、java程序内存使用
在Java进程占用的内存分别是JVM内存和直接内存。
JDK 7使用永久代实现方法区,永久代中的数据还是使用JVM内存存储数据。
JDK 8使用元空间实现方法区,元空间中的数据放在了本地内存当中,直接内存和元空间一样都属于堆外内存。
如下所示:
⚠️注意:
由于直接内存在Java堆外,因此它的大小不会直接受限于
-Xmx指定的最大堆大小,但是系统内存也是有限的,Java堆和直接内存的总和依然受限于操作系统能给出的最大内存。
2、使用原因
2.1、未使用直接内存
文件读写必然涉及磁盘的读写,但是Java本身不具备磁盘读写的能力,因此借助操作系统提供的方法,就是Java中的本地方法接口调用本地方法库。
关于内核态和用户态场景介绍,可参考:
操作系统的内核态和用户态场景-CSDN博客文章浏览阅读1k次,点赞29次,收藏20次。用户态和内核态是操作系统的两种运行状态。内核态:定义:操作系统内核运行的特权模式。特点可以执行所有CPU指令可以访问全部内存空间可以直接操作硬件设备权限最高,但风险也大处于内核态的 CPU 可以访问任意的数据,包括外围设备,比如网卡、硬盘等,处于内核态的 CPU 可以从一个程序切换到另外一个程序,并且占用 CPU 不会发生抢占情况,一般处于特权级 0 的状态我们称之为内核态。用户态:定义:应用程序运行的普通权限模式。特点只能访问受限的CPU指令集和内存空间不能直接访问硬件设备。https://dyclt.blog.csdn.net/article/details/148189950?spm=1011.2415.3001.5331
普通IO读取一份物理磁盘的文件到内存中,需要下面两步。
1:把磁盘文件中的数据读取到系统内存中。
2:把系统内存中的数据读取到JVM堆内存中。
如下所示:
为了使得数据可以在系统内存和JVM堆内存之间相互复制,需要在系统内存和JVM堆内存都复制一份磁盘文件。这样做不仅浪费空间,而且传输效率低下。
当使用NIO时,如下所示:
操作系统划出一块直接缓冲区可以被Java代码直接访问。
这样当读取文件的时候步骤如下。
1、物理磁盘读取文件到直接内存。
2、JVM通过NIO库直接访问数据。
小结:
以上步骤便省略了系统内存和JVM内存直接互相复制的过程,不仅节省了内存空间,也提高了数据传输效率。
2.2、工作原理
如下所示:

JVM的Heap内存(通常受-Xmx限制)适合一般对象分配和GC管理。
但对于大数据量、高速I/O(如网络数据、文件缓冲等),频繁在用户态和内核态切换、Java堆和Native堆间拷贝,性能损耗很大。直接内存可以让Java对象“零拷贝”地跟操作系统交互,减少Heap到C代码的二次copy。
3、直接内存和堆内存对比
如下所示:
-XX:MaxDirectMemorySize 是用来限制直接内存的最大上限的参数,默认(Java 8/11)是等于最大堆内存或无上限(新版JVM会更加严谨建议显式指定)。
- 直接内存分配不会占用Java堆,也不会被GC直接统计,但直接内存泄露会造成物理内存耗尽,严重时会引发系统OOM。
- 某些框架(如Netty、Spark、HBase、Cassandra等)会过度分配direct buffer导致系统异常。
4、回收与清理
直接内存也可能导致OutOfMemoryError异常。
代码示例:
import sun.misc.VM;import java.nio.ByteBuffer;
import java.util.ArrayList;/*** 直接内存内存的OOM: OutOfMemoryError:Direct buffer memory** -XX:MaxDirectMemorySize 设置最大直接内存* -Xmx60m 设置最大堆内存* 默认 MaxDirectMemorySize 等于 Xmx*/
public class BufferTest2 {private static final int BUFFER = 1024 * 1024 * 20;//20MBpublic static void main(String[] args) {// 获取Java虚拟机中的Runtime实例Runtime runtime = Runtime.getRuntime();// 获取JVM的最大内存long maxMemory = runtime.maxMemory();System.out.println("Max Memory: " + maxMemory + " bytes");long maxDirectMemory = VM.maxDirectMemory();System.out.println("Max Direct Memory: " + maxDirectMemory + " bytes");ArrayList<ByteBuffer> list = new ArrayList<>();int count = 0;try {while (true) {ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);list.add(byteBuffer);count++;try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}} finally {System.out.println(count);}}
}
运行结果:该代码可能会在运行时抛出OutOfMemoryError(Direct buffer memory),因为它试图持续分配超出系统限制的直接缓冲区内存。实际能否运行成功取决于JVM的MaxDirectMemorySize设置和可用系统资源。
直接内存由于不受JVM的内存管理,通常有两种方式处理内存。
1、当ByteBuffer对象不再使用的时候置为null,调用System.gc()方法告诉JVM可以回收ByteBuffer对象,最终系统调用freemermory()方法释放内存。System.gc()会引起一次Full GC,通常比较耗时,影响程序执行。
2、可以通过参数-XX:MaxDirectMemorySize来指定直接内存的最大值。若不设置-XX:MaxDirectMemorySize参数,其默认值与-Xmx参数配置的堆内存的最大值一致。
5、实现
直接内存在 Java 里最常见的用法,就是通过 ByteBuffer.allocateDirect。
代码示例如下:
1. 用 ByteBuffer.allocateDirect 分配直接内存
import java.nio.ByteBuffer;public class DirectMemoryExample {public static void main(String[] args) {// 分配一块100字节的直接内存ByteBuffer directBuffer = ByteBuffer.allocateDirect(100);// 写入数据directBuffer.put((byte) 0x01);directBuffer.put((byte) 0x02);// 翻转位置,准备读取directBuffer.flip();// 读取数据while (directBuffer.hasRemaining()) {byte b = directBuffer.get();System.out.println(b); // 输出1、2}// 不需要像关闭stream那样显示释放,JVM会在DirectByteBuffer回收时自动释放native内存// 但如果大量直接内存频繁分配,有可能因JVM回收不及时导致OOM}
}
总结
它通过避免内存拷贝、减少GC压力等方式,显著提升了Java应用程序在文件操作、网络通信等I/O密集型场景下的性能。然而,其堆外特性也带来了内存泄漏、调试困难等挑战。
参考文章:
1、java直接内存_java 直接内存-CSDN博客文章浏览阅读1.3k次,点赞30次,收藏26次。直接内存不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。直接内存是在Java堆外的、直接向操作系统申请的内存区间。直接内存来源于,可以通过ByteBuffer类操作。ByteBuffer类调用方法可以申请直接内存,方法内部创建了一个对象,对象存储直接内存的起始地址和大小,据此就可以操作直接内存。直接内存和堆内存之间的关系如图所示。_java 直接内存https://blog.csdn.net/qq_37362891/article/details/141569415?ops_request_misc=%257B%2522request%255Fid%2522%253A%25221aaf4bf5f69e9933f73781c0c7da7075%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=1aaf4bf5f69e9933f73781c0c7da7075&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-141569415-null-null.142^v102^control&utm_term=java%E4%B8%AD%E7%9A%84%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98&spm=1018.2226.3001.4187
2、java基础:Java直接内存(堆外内存)深度解析_java 直接内存-CSDN博客文章浏览阅读1k次,点赞13次,收藏10次。适用场景需要操作超过100MB的大型数据对GC停顿敏感的高性能场景需要与本地库/Native代码交互避坑指南始终实现ReferenceQueue监控泄漏避免频繁分配/释放小块内存设置合理的-XX:MaxDirectMemorySize未来趋势Project Panama的统一内存访问GraalVM本地镜像的增强支持持久化内存(PMEM)集成大厂实践箴言“直接内存是把双刃剑,用好了是性能神器,用不好是内存泄漏的万恶之源”—— 阿里云JVM团队。_java 直接内存https://blog.csdn.net/weixin_43290370/article/details/148264985?ops_request_misc=&request_id=&biz_id=102&utm_term=java%E4%B8%AD%E7%9A%84%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-0-148264985.142^v102^control&spm=1018.2226.3001.4187
