当前位置: 首页 > news >正文

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

http://www.dtcms.com/a/314826.html

相关文章:

  • FastAPI后端工程化项目记录
  • STM32_Hal库学习SPI
  • MSPM0开发学习笔记:二维云台结合openmv实现小球追踪
  • 反阶持仓筹码副图指标,三红做多持股技术及指标案例
  • 图的存储方式-无向图-邻接多重表
  • 7.1、《软件工程》-软件生命周期-CMM-开发模型
  • 一文速通:命名实体识别(NER)训练方案与标注方法全解析
  • 我用一个 Postgres 实现一整套后端架构!
  • 【SpringAI】SpringAI的介绍与简单使用
  • Vue3核心语法进阶(生命周期)
  • 【笔记】ROS1|2 Turtlebot3汉堡Burger连接和远程控制【旧文转载】
  • P1002 [NOIP 2002 普及组] 过河卒
  • RocksDB 核心入口:DB类源码解析
  • 《C++多态详解:从虚函数到运行时动态绑定》
  • 强反射场景识别误差↓78%!陌讯多模态融合算法在水位监测的落地优化
  • Shell操作git,上传更新文档
  • Redshift 渲染器:GPU 加速渲染的高效之选
  • TGD第十一篇:卷积神经网络中的TGD特征
  • MS-DOS 常用指令集
  • OCR 精准识别验讫章:让登记与校验更智能
  • ssh连接VirtualBox中的Ubuntu24.04(win11、putty、NAT 模式)
  • 西门子PLC S7-1200单轴步进控制电动机
  • Exporters | 安装process_exporter
  • C语言:构造类型学习
  • 深入剖析Java Stream API性能优化实践指南
  • 【Django】-11- 后台管理界面定制
  • [机器学习]02-基于贝叶斯决策的鸢尾花数据集分类
  • 云原生攻防6(Kubernetes扩展知识)
  • 并发编程常用工具类(下):CyclicBarrier 与 Phaser 的协同应用
  • 政府财政行业云原生转型之路