【JVM】详解直接内存

news2024/11/27 13:50:31

文章目录

  • 1. 直接内存概述
  • 2. 直接内存的使用
    • 2.1 Java缓冲区
    • 2.2 直接内存
  • 3. 直接内存的释放
    • 3.1 直接内存释放原理
  • 4. 禁用显式回收对直接内存的影响

1. 直接内存概述

下面是 《深入理解 Java 虚拟机 第三版》2.2.7 小节 关于 Java 直接内存的描述。

直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致 OutOfMemoryError 异常出现,所以我们放到这里一起讲解。  在 JDK 1.4 中新加入了 NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。  显然,本机直接内存的分配不会受到 Java 堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置 -Xmx 等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现 OutOfMemoryError 异常。

直接内存常用于NIO操作,用于数据缓冲区。

但是直接内存分配回收成本较高,但读写性能高。

最后就是不受JVM内存回收管理


2. 直接内存的使用

下面的例子使用了两种方式来讲文件拷贝到另外一个地方

  1. 传统的Java缓冲区
  2. 直接内存
static final String FROM = "D:\\BaiduNetdiskDownload\\《MYSQL内核:INNODB存储引擎 卷1》.zip";
static final String TO = "D:\\BaiduNetdiskDownload\\《MYSQL内核:INNODB存储引擎 卷1》(1).zip";
static final int _1Mb = 1024 * 1024;

public static void main(String[] args) {
    io();
    directBuffer();
}

private static void directBuffer() {
    long start = System.nanoTime();
    try (FileChannel from = new FileInputStream(FROM).getChannel();
         FileChannel to = new FileOutputStream(TO).getChannel();
        ) {
        ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);
        while (true) {
            int len = from.read(bb);
            if (len == -1) {
                break;
            }
            bb.flip();
            to.write(bb);
            bb.clear();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    long end = System.nanoTime();
    System.out.println("directBuffer 用时:" + (end - start) / 1000_000.0);
}

private static void io() {
    long start = System.nanoTime();
    try (FileInputStream from = new FileInputStream(FROM);
         FileOutputStream to = new FileOutputStream(TO);
        ) {
        byte[] buf = new byte[_1Mb];
        while (true) {
            int len = from.read(buf);
            if (len == -1) {
                break;
            }
            to.write(buf, 0, len);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    long end = System.nanoTime();
    System.out.println("io 用时:" + (end - start) / 1000_000.0);
}

image-20230127135245522


2.1 Java缓冲区

Java本身并不具备磁盘读写能力,若想使用磁盘读写的能力,就必须调用操作系统提供的函数

也就是内部会调用本地方法,同时CPU的状态会从用户态切换成内核态

在这里插入图片描述

同时内存也会作出相应变化,当切换到内核态的时候,会将磁盘文件先读进系统缓冲区(分次读取),Java是无法使用系统缓冲区,Java就会在堆内存中创建一个Java缓冲区(byte[] buf = new byte[_1Mb]),Java要想读取到系统缓冲区,就需要将系统缓冲区的数据间接读入到Java缓冲区,Java就能对Java缓冲区进行操作了。

image-20230127135942255

之所以用传统IO效率比较低,是因为磁盘文件需要先读入系统缓冲区,系统缓冲区再读入Java缓冲区,Java才能对磁盘文件进行处理,这里造成了不必要的数据复制。效率因而较低。


2.2 直接内存

当执行了ByteBuffer bb = ByteBuffer.allocateDirect(_1Mb);之后,操作系统会划分出一个1MB的内存。

这块内存Java代码可以直接访问,操作系统也可以直接访问。也就相当于Java和操作系统共享的一块内存。

这时候磁盘文件可以读入进直接内存,接着Java代码可以对直接内存进行操作,也就是比传统IO少了一次复制的操作,因而效率较高。

image-20230127140531917


3. 直接内存的释放

下面的代码分配一块1G的直接内存

public class Demo1_26 {
    static int _1Gb = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);
        System.out.println("分配完毕...");
        System.in.read();
        System.out.println("开始释放...");
        byteBuffer = null;
        System.gc(); // 显式的垃圾回收,Full GC
        System.in.read();
    }
}

当分配成功之后,在任务管理器可以看见Java程序的内存为1G多

image-20230127141209833

接着将byteBuffer设置为NULL,开始垃圾回收

image-20230127141321089

Java程序的内存直接下降1G左右,说明直接内存被释放了。


3.1 直接内存释放原理

前面不是说直接内存不受JVM内存回收管理嘛?为什么垃圾回收之后,直接内存就被释放了?

别急,先来介绍一下直接内存的释放原理

static int _1Gb = 1024 * 1024 * 1024;

public static void main(String[] args) throws IOException {
    Unsafe unsafe = getUnsafe();
    // 分配内存
    long base = unsafe.allocateMemory(_1Gb);
    unsafe.setMemory(base, _1Gb, (byte) 0);
    System.in.read();

    // 释放内存
    unsafe.freeMemory(base);
    System.in.read();
}

public static Unsafe getUnsafe() {
    try {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        return unsafe;
    } catch (NoSuchFieldException | IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

Unsafe是Java底层用来分配直接内存和释放直接的内存的类。但是一般并不建议使用这个类。

分配直接内存是靠Unsafe类的allocateMemory方法来实现的,其返回值就是分配内存的地址

而释放内存是靠Unsafe类的freeMemory方法实现的。这个方法需要传入需要释放的内存的地址。

也就是说,想要释放直接内存,需要主动调用freeMemory方法

接着,查看ByteBuffer.allocateDirect(_1Gb)的源码

public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

其内部是new DirectByteBuffer(capacity),接着查看这个构造方法

DirectByteBuffer(int cap) {                   // package-private

    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        //使用Unsafe类分配直接内存
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;



}

可以看见构造方法里使用unsafe.allocateMemory(size)来分配直接内存。

那么什么时候调用释放直接内存的方法呢?

需要关注这一行代码

cleaner = Cleaner.create(this, new Deallocator(base, size, cap));

其中Deallocator是一个回调任务对象

private static class Deallocator
    implements Runnable
{

    private static Unsafe unsafe = Unsafe.getUnsafe();

    private long address;
    private long size;
    private int capacity;

    private Deallocator(long address, long size, int capacity) {
        assert (address != 0);
        this.address = address;
        this.size = size;
        this.capacity = capacity;
    }

    public void run() {
        if (address == 0) {
            // Paranoia
            return;
        }
        //释放直接内存
        unsafe.freeMemory(address);
        address = 0;
        Bits.unreserveMemory(size, capacity);
    }

}

查看其源码,可以发现其实现了Runnable,它的run方法调用了释放直接内存的方法unsafe.freeMemory(address);

看完这些,也就是说,想要释放直接内存,就必须调用Deallocator中的run方法

接着继续来说说ClearClear在Java类库中是一个特殊的类型,称为虚引用类型

当虚引用关联的对象被回收时,就会触发虚引用对象的clean方法

private Cleaner(Object var1, Runnable var2) {
    super(var1, dummyQueue);
    this.thunk = var2;
}

public static Cleaner create(Object var0, Runnable var1) {
    return var1 == null ? null : add(new Cleaner(var0, var1));
}

public void clean() {
    if (remove(this)) {
        try {
            this.thunk.run();
        } catch (final Throwable var2) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (System.err != null) {
                        (new Error("Cleaner terminated abnormally", var2)).printStackTrace();
                    }

                    System.exit(1);
                    return null;
                }
            });
        }

    }
}

查看clean源码,可以发现在执行Cleaner.create方法的时候,会将new Deallocator(base, size, cap)作为参数传递给Runnable var1,在create方法调用new Cleaner(var0, var1),将this.thunk赋值Runnable var1

也就是说在clean方法中的this.thunk.run();调用的就是Deallocator中的run方法。从而释放直接内存。

也就是ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦 ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner clean 方法调 用 freeMemory 来释放直接内存


4. 禁用显式回收对直接内存的影响

可以通过下面的参数禁用显式回收

-XX:+DisableExplicitGC 显式的

什么是显式回收?下面就是显式回收的一个例子

System.gc(); // 显式的垃圾回收,Full GC

禁用显式回收之后,这行代码就变成无效了

这行代码无效,可能会直接影响到直接内存的释放

public class Demo1_26 {
    static int _1Gb = 1024 * 1024 * 1024;

    public static void main(String[] args) throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1Gb);
        System.out.println("分配完毕...");
        System.in.read();
        System.out.println("开始释放...");
        byteBuffer = null;
        System.gc(); // 显式的垃圾回收,Full GC
        System.in.read();
    }
}

同样,就这个例子来说,当执行到System.gc(); 并不会触发垃圾回收。

于是,byteBuffer对象虽然为NULL,但是并不会被回收掉,于是前面申请的1GB直接内存也不会被释放。

这个byteBuffer只能等到真正的垃圾回收触发时才会被回收,这个直接内存也随之释放。

这样造成的后果就是直接内存占用比较大。

对应的解决办法就是手动通过Unsafe释放直接内存。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/180624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

从零开始的数模(七)层次分析法

一、概念 1.1定义 应用场景&#xff1a; 1、最佳方案选取 2、评价类问题 3、指标体系的优选 步骤&#xff1a; 1、建立层次结构模型&#xff1b; 2、构造判断(成对比较)矩阵&#xff1b; 3、层次单排序及其一致性检验&#xff1b; 4、层次总排序及其一致性检验&#xff1b; …

Mybatis 分页插件使用

1、分页插件的使用步骤 需求分析&#xff1a; 我们在前端界面获取用户表的时候&#xff0c;在界面上一次显示出成百上千条数据&#xff0c;用户体验&#xff0c;软件性能都会很糟糕&#xff0c;假设数据库内存储十万条记录&#xff0c;后端一次性返回这么多数据&#xff0c;前…

C语言深度解剖-关键字(2)

目录 1.关键字 static 源文件与头文件 static修饰全局变量 static修饰局部变量 写在最后&#xff1a; 1.关键字 static 源文件与头文件 平时我们在练习的时候&#xff0c;都只会开一个用来测试的源文件&#xff0c; 但是&#xff0c;当我们在写一个项目的时候&#xff…

Hal GPIO控制--LED/Delay实现

环境配置在CubeMx Pinout view 中点击可以设置管脚模式 &#xff0c;右击 可以配置管脚名称这里以点PB8灯为例&#xff0c;可以设置灯输出电平 &#xff0c;模式为输出&#xff0c;不进行上下拉&#xff0c; 速度 模式设置以及用户自定义名称。。时钟树配置&#xff0c;使用HSI…

FreeRTOS源码获取-->FreeRTOS移植-->FreeRTOS源码文件了解 | FreeRTOS二

目录 说明&#xff1a; 一、获取源码 1.1、FreeRTOS官网获取 1.2、正点原子开发板A盘资料\6&#xff0c;软件资料\13&#xff0c;版本-->V10.4.6 二、移植源码 2.1、移植步骤 2.1.1、添加源码、头文件路径 2.1.2、添加FreeRTOSConfig.h文件路径 2.1.3、添加或修改相…

VisualSVN Server Enterprise 5.1.1 Crack

VisualSVN Server 提供以下主要功能。 Active Directory 单点登录 允许用户使用他们当前的 Active Directory 域凭据访问 VisualSVN Server。使用安全 Kerberos V5 或 NTLM 身份验证协议。支持双因素身份验证和智能卡。 多站点存储库复制 使用 VisualSVN 分布式文件系统 (VDF…

MySQL基础(1)—— 卸载与安装

文章目录MySQL卸载【windows】1、停止MySQL服务2、软件的卸载2.1 通过控制面板卸载软件2.2 通过360软件管家等第三方软件进行删除2.3 通过MySQL安装包提供的卸载功能卸载3、残余文件的清理4、清理注册表5、删除环境变量配置MySQL安装【windows】1、下载安装包2、安装3、配置环境…

Sharding-JDBC(六)5.1.0版本,实现按月分表、自动建表、自动刷新节点

目录1.Maven 依赖2.创建表结构3.yml 配置4.TimeShardingAlgorithm.java 分片算法类5.ShardingAlgorithmTool.java 分片工具类6.ShardingTablesLoadRunner.java 初始化缓存类7.SpringUtil.java Spring工具类8.源码测试9.测试结果10.代码地址背景&#xff1a; 项目用户数据库表量…

vscode运行C/C++程序

一、vsocde对C/C的支持 Visual Studio Code对C/C语言的支持由Microsoft C/C扩展程序提供。它使得C/C在Windows、Linux和macOS等跨平台开发成为可能。 二、安装扩展程序 打开VS Code软件选择任务栏上的扩展视图图标&#xff08;下图红色方框&#xff09;或使用快捷键(CtrlShif…

【自然语言处理】情感分析(四):基于 Tokenizer 和 Word2Vec 的 CNN 实现

情感分析&#xff08;四&#xff09;&#xff1a;基于 Tokenizer 和 Word2Vec 的 CNN 实现本文是 情感分析 系列的第 444 篇&#xff0c;前三篇分别是&#xff1a; 【自然语言处理】情感分析&#xff08;一&#xff09;&#xff1a;基于 NLTK 的 Naive Bayes 实现【自然语言处…

服务发现Discovery和Eureka自我保护机制

目录 一、服务发现Discovery ​二、Eureka自我保护 &#xff08;一&#xff09;故障现象 &#xff08;二&#xff09;导致原因 &#xff08;三&#xff09;怎么禁止自我保护 三、Eureka2.0的停更 一、服务发现Discovery 对于注册进eureka里面的微服务&#xff0c;可以通…

外挂、破解软件理论与实战

外挂、破解软件理论与实战 1 理论 1.1 不同操作系统下的可执行文件 Windows【PE】 PE 格式&#xff0c;可移植可执行格式&#xff08;Portable Executable&#xff09;&#xff0c; 是 Windows 下的主要可执行文件格式。别被名字迷惑了&#xff0c;PE 文件必须是 Windows 下…

第四十六章 动态规划——状态机模型

第四十六章 动态规划——状态机模型一、通俗理解状态机DP1、什么是状态机2、什么是状态机DP二、例题1、AcWing 1049. 大盗阿福&#xff08;1&#xff09;问题&#xff08;2&#xff09;分析a.状态定义b.状态转移c.循环设计d.初末状态&#xff08;3&#xff09;代码2、AcWing 10…

C++学习/温习:新型源码学编程(三)

写在前面(祝各位新春大吉&#xff01;兔年如意&#xff01;) 【本文持续更新中】面向初学者撰写专栏&#xff0c;个人原创的学习C/C笔记&#xff08;干货&#xff09;所作源代码输出内容为中文&#xff0c;便于理解如有错误之处请各位读者指正请读者评论回复、参与投票&#xf…

01 课程简介、HTML标签【尚硅谷JavaWeb教程】

1. 课程体系设计 2. HTML标签 服务器—浏览器&#xff08;字符串"" &#xff09; demo01.html 1&#xff09;html语言是解释型语言&#xff0c;不是编译型 浏览器是容错的 2&#xff09;html页面中由一对标签组成&#xff1a; < html>称为 开始标签 < /htm…

Java基础语法——数组概念、数组内存图解(一个数组、二个数组)及二元数组的应用

目录 数组概述 数组定义格式 数组概念 数组的定义格式 数组的初始化 数组初始化概述 数组的初始化方式 Java中的内存分配 Java中一个数组的内存图解 Java中二个数组的内存图解 两个数组指向同一个地址的内存图解 数组操作中两个常见的小问题 二维数组 二维数组概述…

c++11 标准模板(STL)(std::forward_list)(十一)

定义于头文件 <forward_list> template< class T, class Allocator std::allocator<T> > class forward_list;(1)(C11 起)namespace pmr { template <class T> using forward_list std::forward_list<T, std::pmr::polymorphic_…

前端架构处理Cookie、Session、Token

1. Cookie Cookie 总是保存在客户端中。按在客户端中的存储位置&#xff0c;可分为内存 Cookie 和硬盘 Cookie。 内存 Cookie 由浏览器维护&#xff0c;保存在内存中&#xff0c;浏览器关闭后就消失了&#xff0c;其存在时间是短暂的。硬盘 Cookie 保存在硬盘里&#xff0c;…

Spring Boot、Spring MVC热部署

一、相关概述 JVM能够识别的是字节码.class文件每次重新运行都是一个重新编译的过程&#xff0c;也就是说会生成新的target字节码文件&#xff1b;但是每次修改了代码之后也必须要重新运行&#xff0c;这样比较麻烦。热部署就能较好地解决该问题&#xff0c;直接刷新页面就可以…

(22)go-micro微服务kibana使用

文章目录一 kibana介绍二 Kibana主要功能三 Kibana侧边栏四 Kibana安装1.拉取镜像2.运行命令3.查看是否运行五 Kibana使用六 Kibana图形化界面七 最后一 kibana介绍 Kibana &#xff1a;是一个开源的分析和可视化平台&#xff0c;旨在与 Elasticsearch 合作。Kibana 提供搜索、…