• 【JVM技术专题】 深入分析回顾堆外内存使用和分析「分析篇」


    堆外内存

    堆外内存,其实就是不受JVM控制的内存。简单来说,除了堆栈内存,剩下的就都是堆外内存了(当然,这是从Java运行时内存的角度来看),堆外内存直接受操作系统管理,而不是虚拟机。而使用堆外内存的原因,

    相比于堆内内存有几个优势:

    1. 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作(可能使用多线程或者时间片的方式,根本感觉不到)

    堆外内存是直接受操作系统管理的,而不是JVM,因此使用堆外内存的话,就可以保持一个比较小的堆内内存,减少垃圾回收对程序性能的影响

    1. 就是提高IO操作的效率!这里就涉及用户态与内核态,以及内核缓冲区的概念,如果从堆内向磁盘写数据,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,但使用堆外内存的话则可以避免这个复制操作。

    堆内内存其实就是用户进程的【进程缓冲区】,属于用户态;堆外内存由操作系统管理【内核缓冲区】,属于内核态。

    自然也有不好的一面:

    1. 堆外内存难以控制,如果内存泄漏,那么很难排查

    2. 堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合

    3. 因为是操作系统的内存机制,所以需要通过本地方法进行分配,较为复杂和缓慢

    直接内存使用

    1. 堆外内存通过java.nioByteBuffer来创建,调用allocateDirect方法申请即可。

    2. 可以通过设置-XX:MaxDirectMemorySize=10M控制堆外内存的大小。

    堆外内存的垃圾回收

    1. 由于堆外内存并不直接控制于JVM,因此只能等到full GC的时候才能垃圾回收!Full GC,一般发生在年老代垃圾回收以及调用System.gc的时候,这样肯定不能满足我们的需求!

    2. 手动的控制回收堆外内存了!其中sun.nio其实是java.nio的内部实现。

    package xing.test;
    import java.nio.ByteBuffer;
    import sun.nio.ch.DirectBuffer;
    public class NonHeapTest {
      public static void clean(final ByteBuffer byteBuffer) { 
        if (byteBuffer.isDirect()) { 
          ((DirectBuffer)byteBuffer).cleaner().clean(); 
        } 
     } 
      public static void sleep(long i) { 
        try { 
           Thread.sleep(i); 
         }catch(Exception e) { 
           /*skip*/
         } 
      } 
      public static void main(String []args) throws Exception { 
          ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 200); 
          System.out.println("start"); 
          sleep(5000); 
          clean(buffer);//执行垃圾回收
    //     System.gc();//执行Full gc进行垃圾回收
          System.out.println("end"); 
          sleep(5000); 
      } 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    零拷贝

    1. 用户进程需要像磁盘写数据时,需要将用户缓冲区(进程缓冲区)堆内内存中的内容拷贝到内核缓冲区(堆外内存)中,操作系统调度内核再将内核缓冲区中的内容写进磁盘中

    2. 通过在用户进程中,直接申请堆外内存,存储其需要写进磁盘的数据,就能够省掉上述拷贝操作

    实现方式

    Java提供了一些使用堆外内存以及【DMA】的方法,能够在很大程度上优化用户进程的IO效率。这里,给出一份拷贝文件的代码,分别使用BIO、NIO和使用堆外内存的NIO进行文件复制,简单对比其耗时。

    使用一个200MB左右的pdf文件进行拷贝操作,你可以另外指定更大的文件,文件越大对比越明显。这里我运行出来的延时,BIO的平均耗时1500ms上下,NIO耗时120ms左右, 使用堆外内存的NIO耗时100ms上下。

    package top.jiangnanmax.nio;
    import java.io.*;
    import java.nio.MappedByteBuffer;
    import java.nio.channels.FileChannel;
    
    public class CopyCompare {
     
        public static void main(String[] args) throws Exception {
            String inputFile = "/tmp/nio/input/HyperLedger.pdf";
            String outputFile = "/tmp/nio/output/HyperLedger.pdf";
     
            long start = System.currentTimeMillis();
     
            nioCopyByDirectMem(inputFile, outputFile);
     
            long end = System.currentTimeMillis();
     
            System.out.println("cost time: " + (end - start) + " ms");
     
            deleteFile(outputFile);
        }
     
        /**
         * 使用传统IO进行文件复制
         *
         * 平均耗时 15** ms
         *
         * @param sourcePath
         * @param destPath
         */
        private static void bioCopy(String sourcePath, String destPath) throws Exception {
            File sourceFile = new File(sourcePath);
            File destFile = new File(destPath);
            if (!destFile.exists()) {
                destFile.createNewFile();
            }
     
            FileInputStream inputStream = new FileInputStream(sourceFile);
            FileOutputStream outputStream = new FileOutputStream(destFile);
     
            byte[] buffer = new byte[512];
            int lenRead;
     
            while ((lenRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, lenRead);
            }
     
            inputStream.close();
            outputStream.close();
        }
     
        /**
         * 使用NIO进行文件复制,但不使用堆外内存
         * 平均耗时 1** ms, 比BIO直接快了一个数量级???
         * @param sourcePath
         * @param destPath
         */
        private static void nioCopy(String sourcePath, String destPath) throws Exception {
            File sourceFile = new File(sourcePath);
            File destFile = new File(destPath);
            if (!destFile.exists()) {
                destFile.createNewFile();
            }
            FileInputStream inputStream = new FileInputStream(sourceFile);
            FileOutputStream outputStream = new FileOutputStream(destFile);
            FileChannel inputChannel = inputStream.getChannel();
            FileChannel outputChannel = outputStream.getChannel();
            // transferFrom底层调用的应该是sendfile
            // 直接在两个文件描述符之间进行了数据传输
            // DMA
            outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
            inputChannel.close();
            outputChannel.close();
            inputStream.close();
            outputStream.close();
        }
        /**
         * 使用NIO进行文件复制,并使用堆外内存
         * 平均耗时100ms上下,比没使用堆外内存的NIO快一点
         * @param sourcePath
         * @param destPath
         */
        private static void nioCopyByDirectMem(String sourcePath, String destPath) throws Exception {
            File sourceFile = new File(sourcePath);
            File destFile = new File(destPath);
            if (!destFile.exists()) {
                destFile.createNewFile();
            }
            FileInputStream inputStream = new FileInputStream(sourceFile);
            FileOutputStream outputStream = new FileOutputStream(destFile);
            FileChannel inputChannel = inputStream.getChannel();
            FileChannel outputChannel = outputStream.getChannel();
            MappedByteBuffer buffer = inputChannel.map(FileChannel.MapMode.READ_ONLY, 0, inputChannel.size());
            outputChannel.write(buffer);
            inputChannel.close();
            outputChannel.close();
            inputStream.close();
            outputStream.close();
     
        }
     
        /**
         * 删除目标文件
         *
         * @param target
         */
        private static void deleteFile(String target) {
            File file = new File(target);
            file.delete();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
  • 相关阅读:
    谐云课堂 | 一文详解分布式改造理论与实战
    032:vue中三元运算, style、class、type、 event等多种场景示例
    [附源码]Python计算机毕业设计SSM开放式实验室预约系统(程序+LW)
    论文阅读-Dr.Deep_基于医疗特征上下文学习的患者健康状态可解释评估
    Read-Easy Excel源码解析(一)
    人工智能神经网络的应用,人工神经网络最新应用
    Python Flask教程学习02
    数据库优化方法及思路分析
    LAMP搭建WordPress
    python pycharm安装pexpect
  • 原文地址:https://blog.csdn.net/l569590478/article/details/127648160