IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 系统的直接内存 -> 正文阅读

[Java知识库]系统的直接内存

直接内存

定义

直接内存(Direct Memory):直接内存是系统的直接内存,常见于NIO操作时,用于数据缓冲区。分配回收成本较高,但读写性能高,不受JVM内存回收管理。

下面,我们说一下啊为什么使用直接内存读写的性能会比较高。请看下图:
在这里插入图片描述

这里我们知道,我们的Java是运行在我们的虚拟机中的,通过虚拟机命令再去间接调用系统的内核函数,即我们上面看到的用户态跟内核态。(可以简单想象成,Java代码运行后调用本地方法区的代码,然后本地方法区的代码调用系统的内核函数在CPU上运行,CPU运行完再把结果返回给虚拟机)。而内存呢,我们也不是直接读取的,使用虚拟机我们需要让系统内存先读取我们的磁盘文件并缓存到系统内存中,然后虚拟机再分配对应的空间缓存来接受我们系统缓存区的数据。如此,我们的数据其实就需要缓存两份(一份在系统内存,一份在Java堆内存),自然就比较慢了。

但是,如果我们使用直接内存的话:
在这里插入图片描述
我们开辟的直接内存空间,是系统内存和Java堆内存公用的内存区域,读取磁盘文件后,Java堆内存可以直接访问。

直接内存的基本使用

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;

public class Demo1_9 {
    //读取的文件路径
    static final String FROM = "D:\\QLDownload\\完美世界\\video.qlv";
    //保存的文件路径
    static final String TO = "D:\\Download";
    //预申请1MB的直接内存表示
    static final int _1MB = 1024 * 1024;

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

传统读取文件的写法如下:

    private static void io(){
        long start = System.nanoTime();
        try(FileInputStream in = new FileInputStream(FROM);
            FileOutputStream out = new FileOutputStream(TO)){
            byte[] buf = new byte[_1MB];
            while (true){
                //每次读取1MB
                int len = in.read(buf);
                //读到文件尾部退出循环
                if(len == -1){
                    break;
                }
                //将每次读到的数据缓冲到out中
                out.write(buf, 0, len);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("io用时:"+ (end - start) / 1000000.0);
    }

使用直接内存的写法如下:

    private static void directBuffer(){
        long start = System.nanoTime();
        try(FileInputStream in = new FileInputStream(FROM);
            FileOutputStream out = new FileOutputStream(TO)){
            ByteBuffer buf = ByteBuffer.allocateDirect(_1MB);
            while (true){
                //每次读取1MB
                int len = in.read(buf);
                //读到文件尾部退出循环
                if(len == -1){
                    break;
                }
                buf.flip();
                //将每次读到的数据缓冲到out中
                out.write(buf, 0, len);
                buf.clear();
            }
        }catch (IOException e){
            e.printStackTrace();
        }
        long end = System.nanoTime();
        System.out.println("io用时:"+ (end - start) / 1000000.0);
    }

内存溢出

前面我们说到了,直接内存不受JVM内存回收管理。那么,他会不会发生内存溢出呢?答案是肯定的。我们可以演示一下:
我们在while循环中每次开辟100M的内存空间,并将其添加到List集合中,防止垃圾回收。
在这里插入图片描述
运行结果如下:
在这里插入图片描述
可以看到,运行36次后,程序就报错了,报错信息也直接告诉我们问题出在我们的直接缓冲内存中。

直接内存的释放及其原理

这里,我们演示一下:
在这里插入图片描述
这里,我们分配1G的直接内存,等分配完成后,我们让他等一下(使用System.in.read()读取键盘的操作)。然后再将byteBuffer的内存置空,并进行垃圾回收。回收了,为了方便观察,我们继续让他等一下。
OK,我们看一下我们的运行结果:
这里,因为我们的直接内存不属于Java虚拟机内存,所以我们得借助任务管理器来查看:
未运行程序,我们的IDEA大概占用1G的内存。
在这里插入图片描述
然后我们运行程序且等到控制台输出分配完毕…,我们再看一下我们的任务管理器:
此时我们的IDEA占用了大概2G的内存(其中1G是我们刚才运行的程序占用的):
在这里插入图片描述
在控制台输入回车后且控制台输出开始释放…后,我们再看一下任务管理器:
在这里插入图片描述
可以看到,直接内存被成功释放了。有人肯定会说了,说好的不受JVM内存回收管理呢?不急,我们来看一下他内部回收的原理:

我们借助IDEA工具来看一下,按住ctrl后点击byteBuffer的allocateDirect方法,我们来看一下源码:
在这里插入图片描述
他返回的是一个DirectByteBuffer对象。我们再看一下这个类的构造器:
在这里插入图片描述

这里,其实他是用了unsafe对象来请求我们的内存空间,而后面又通过Cleaner类(Cleaner类是JDK的一个虚引用类型,他的特点是,当他所关联的对象被回收的时候,Cleaner会触发虚引用的create方法。)来关联我们的DirectByteBuffer对象,当该对象被回收的时候,我们就调用后面的new Deallocator(base, size, cap)线程的run()方法。我们可以看一下clean()的源码:

在这里插入图片描述
接着,我们可以按住ctrl,回到前面看一下Deallocator类构造器方法。
在这里插入图片描述
这里可以看到,最后其实是通过调用unfase对象的freeMemory方法来主动释放直接内存的。

总结一下:
1、Java对直接内存的使用和回收,底层其实都是使用了Unsafe对象完成,且回收的时候,我们需要主动使用Unsafe对象调用freeMemory方法。
2、ByteBuffer的实现类内部,使用了Cleaner来监测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放内存。

做JVM调优的时候,我们经常会使用虚拟机命令

-XX:+DisableExplicitGC

禁止使用 System.gc() 方法显示地进行垃圾回收,因为他会回收新生代和老生代的资源,所以比较费时。此时,我们以下的代码将无法实现对直接内存的释放。
在这里插入图片描述
如此,我们就必须使用我们的Unsafe对象主动调用freeMemory()方法来释放直接内存,而我们的Unsafe对象也无法直接使用,必须要使用Java8的反射机制来调用这个对象。即,最后我们的代码应该改为:
在这里插入图片描述
在这里插入图片描述

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-01-29 22:56:59  更:2022-01-29 22:58:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 11:12:21-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码