JVM学习-直接内存

1.直接内存概述

它不能存在于JVM内存结构中
在这里插入图片描述

属于操作系统,常见于NIO操作时,用于数据缓冲区
bytebuffer所使用的内存就是直接内存
分配回收成本较高,但读写性能高

不受JVM内存回收管理

通过以下程序测试直接内存的读写性能

/**
 * 演示 ByteBuffer 作用
 */
public class Demo1_9 {
    
    
//文件大概200M
    static final String FROM = "E:\\编程资料\\第三方教学视频\\youtube\\Getting Started with Spring Boot-sbPSjI4tt10.mp4";
    static final String TO = "E:\\a.mp4";
    static final int _1Mb = 1024 * 1024;		//    //读写缓冲区代码都使用1M

    public static void main(String[] args) {
    
    
        io(); // io 用时:1535.586957 1766.963399 1359.240226
        directBuffer(); // directBuffer 用时:479.295165 702.291454 562.56592
    }
//使用by
    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.基本使用

在这里插入图片描述
为了使用直接内存,文件读写效率会大大提高。
Java要想读写磁盘,就需要调用操作系统提供的函数,也就是之前native函数。CPU状态会从用户态切换到内核态。当切换到内核态之后,CPU函数就能读取磁盘文件的内容了。内核态的时候,会在操作系统内核中划出一块缓冲区,称之为系统缓存区。分次进行读取。此时java代码不能够访问,Java又会在堆内存中划分一块java缓冲区,**然后再从系统缓存区中读取到Java缓冲区,**然后调用输入输出流的读写操作进行读写。
此时有两块缓冲区。这样就造成了不必要的数据复制。
在这里插入图片描述
direct memory:当我们调用bytebuffer的allocateDirect方法时,就会分配一块直接内存,我们会在操作系统这边划出一块缓冲区,与之前不一样的是,操作系统划出的这块内存,java代码可以直接访问。对两端内存都是共享的。比之前少了一次缓冲区的复制操作,文件读取效率得到了大的提升。
直接内存是操作系统和Java代码都可以访问的一块区域,无需将代码从系统内存复制到Java堆内存,从而提高了效率

3.内存溢出

direct memory不受Java虚拟机回收管理,不会去释放directmemory所占用的内存。

public class Demo1_10 {
    
    
    static int _100Mb = 1024 * 1024 * 100;	//每次分配100M的内存

    public static void main(String[] args) {
    
    
        List<ByteBuffer> 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 对方法区的实现称为元空间
    }
}

在这里插入图片描述
循环了17次,报了Java.lang.OutOfMemoryError:Direct buffer memory错误

4.释放原理

直接内存的回收不是通过JVM的垃圾回收来释放的,而是通过unsafe.freeMemory来手动释放

通过

//通过ByteBuffer申请1M的直接内存
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(_1M);

申请直接内存,但JVM并不能回收直接内存中的内容,它是如何实现回收的呢?

allocateDirect的实现

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

DirectByteBuffer类

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 {
    
    
        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)); //通过虚引用,来实现直接内存的释放,this为虚引用的实际对象
    att = null;
}

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

public void clean() {
    
    
       if (remove(this)) {
    
    
           try {
    
    
               this.thunk.run(); //调用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;
                   }
               });
           }

对应对象的run方法

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

直接内存的回收机制总结

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

ByteBuffer的实现内部使用了Cleaner(虚引用)来检测ByteBuffer。一旦ByteBuffer被垃圾回收,那么会由ReferenceHandler来调用Cleaner的clean方法调用freeMemory来释放内存

猜你喜欢

转载自blog.csdn.net/qq_39736597/article/details/113460716
今日推荐