栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JVM专题(九)-直接内存

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JVM专题(九)-直接内存

文章目录
  • 1.定义
  • 2.基本使用
    • 2.1.Java操作磁盘文件
    • 2.2.NIO操作磁盘文件
    • 2.3.内存溢出
    • 2.4.分配和释放原理
  • 3.直接内存的回收机制总结

1.定义

不是虚拟机的内存,是系统内存。Direct Memory

  • 常见于NIO操作时,用于数据缓存区
  • 分配回收成本过高,但读写性能高
  • 不受JVM内存回收管理
2.基本使用
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;


public class Demo1_9 {
    static final String FROM = "E:\b.mp4";
    static final String TO = "E:\a.mp4";
    static final int _1Mb = 1024 * 1024;

    public static void main(String[] args) {
        io(); // io 用时:1535.586957 1766.963399 1359.240226
        directBuffer(); // directBuffer 用时:479.295165 702.291454 562.56592
    }

    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);
    }
}
2.1.Java操作磁盘文件


当java读取磁盘文件时,会从用户态切换到内核态,才能去操作系统内存。读取时,系统内存先开辟一块缓存空间,磁盘文件分块读取。然后java虚拟机内存再开辟缓存空间new Byte[]来读取系统内存的文件。由于有从系统内存读取到java虚拟机的内存,所以效率较低。

2.2.NIO操作磁盘文件


读取磁盘文件时,会有一块直接内存,Java虚拟机和视同内存都能访问使用,所以效率更高。

2.3.内存溢出
public class Demo1_10 {
    static int _100Mb = 1024 * 1024 * 100;

    public static void main(String[] args) {
        List list = new ArrayList<>();
        int i = 0;
        try {
            while (true) {
                ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_100Mb);
                list.add(byteBuffer);
                i++;
            }
        } finally {
            System.out.println(i);
        }
        // 方法区是jvm规范, jdk6 中对方法区的实现称为永久代
        //                  jdk8 对方法区的实现称为元空间
    }
}

2.4.分配和释放原理
  • 使用了Unsafe对象完成直接内存的分配回收,并且回收需要主动调用freeMemory方法。
  • ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象,一旦ByteBuffer对象被垃圾回收,那么就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存。
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();
    }
}
  • 直接内存不受jvm内存管理,但是这段代码示例中,当byteBuffer = null,执行垃圾回收后,直接内存却被释放了。这是因为跟jdk中的一个类Unsafe有关。
  • -XX:+DisableExplicitGC 禁用显式的垃圾回收。只有等到jvm自己进行垃圾回收才会回收。
public class Demo1_27 {
    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类调用其方法freeMemory来释放了直接内存。

ByteBuffer源码

ByteBuffer分配直接内存是通过new 了一个DirectByteBuffer

在DirectByteBuffer内部可以看到,确实是用Unsafe类去分配内存。

这里调用了一个Cleaner的create方法,且后台线程还会对虚引用的对象监测,如果虚引用的实际对象(这里是DirectByteBuffer)被回收以后,就会调用Cleaner的clean方法,来清除直接内存中占用的内存


DirectByteBuffer的run方法释放了直接内存。

3.直接内存的回收机制总结

使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法

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

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/820420.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号