JVM说--直接内存的使用

2023-02-11  本文已影响0人  京东云开发者

作者:京东物流 刘作龙

前言:
学习底层原理有的时候不一定你是要用到他,而是学习他的设计思想和思路。再或者,当你在日常工作中遇到棘手的问题时候,可以多一条解决问题的方式

分享大纲:
本次分享主要由io与nio读取文件速度差异的情况,去了解nio为什么读取大文件的时候效率较高,查看nio是如何使用直接内存的,再深入到如何使用直接内存

image

1 nio与io读写文件的效率比对

首先上代码,有兴趣的同学可以将代码拿下来进行调试查看

package com.lzl.netty.study.jvm;

import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StopWatch;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * java对于直接内存使用的测试类
 *
 * @author liuzuolong
 * @date 2022/6/29
 **/
@Slf4j
public class DirectBufferTest {


    private static final int SIZE_10MB = 10 * 1024 * 1024;


    public static void main(String[] args) throws InterruptedException {
        //读取和写入不同的文件,保证互不影响
        String filePath1 = "/Users/liuzuolong/CODE/OWN/netty-study/src/main/resources/ioInputFile.zip";
        String filePath2 = "/Users/liuzuolong/CODE/OWN/netty-study/src/main/resources/nioDirectInputFile.zip";
        String filePath3 = "/Users/liuzuolong/CODE/OWN/netty-study/src/main/resources/nioHeapInputFile.zip";
        String toPath1 = "/Users/liuzuolong/CODE/OWN/netty-study/src/main/resources/ioOutputFile.zip";
        String toPath2 = "/Users/liuzuolong/CODE/OWN/netty-study/src/main/resources/nioDirectOutputFile.zip";
        String toPath3 = "/Users/liuzuolong/CODE/OWN/netty-study/src/main/resources/nioHeapOutputFile.zip";
        Integer fileByteLength = SIZE_10MB;
        //新建io读取文件的线程
        Thread commonIo = new Thread(() -> {
            commonIo(filePath1, fileByteLength, toPath1);
        });
        //新建nio使用直接内存读取文件的线程
        Thread nioWithDirectBuffer = new Thread(() -> {
            nioWithDirectBuffer(filePath2, fileByteLength, toPath2);
        });
        //新建nio使用堆内存读取文件的线程
        Thread nioWithHeapBuffer = new Thread(() -> {
            nioWithHeapBuffer(filePath3, fileByteLength, toPath3);
        });
        nioWithDirectBuffer.start();
        commonIo.start();
        nioWithHeapBuffer.start();
    }

    public static void commonIo(String filePath, Integer byteLength, String toPath) {
        //进行时间监控
        StopWatch ioTimeWatch = new StopWatch();
        ioTimeWatch.start("ioTimeWatch");
        try (FileInputStream fis = new FileInputStream(filePath);
             FileOutputStream fos = new FileOutputStream(toPath);
        ) {
            byte[] readByte = new byte[byteLength];
            int readCount = 0;
            while ((readCount = fis.read(readByte)) != -1) {
                // 读取了多少个字节,转换多少个。
                fos.write(readByte, 0, readCount);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        ioTimeWatch.stop();
        log.info(ioTimeWatch.prettyPrint());
    }





    public static void nioWithDirectBuffer(String filePath, Integer byteLength, String toPath) {
        StopWatch nioTimeWatch = new StopWatch();
        nioTimeWatch.start("nioDirectTimeWatch");
        try (FileChannel fci = new RandomAccessFile(filePath, "rw").getChannel();
             FileChannel fco = new RandomAccessFile(toPath, "rw").getChannel();
        ) {
            // 读写的缓冲区(分配一块儿直接内存)
            //要与allocate进行区分
            //进入到函数中
            ByteBuffer bb = ByteBuffer.allocateDirect(byteLength);
            while (true) {
                int len = fci.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();
                fco.write(bb);
                bb.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        nioTimeWatch.stop();
        log.info(nioTimeWatch.prettyPrint());
    }




    public static void nioWithHeapBuffer(String filePath, Integer byteLength, String toPath) {
        StopWatch nioTimeWatch = new StopWatch();
        nioTimeWatch.start("nioHeapTimeWatch");
        try (FileChannel fci = new RandomAccessFile(filePath, "rw").getChannel();
             FileChannel fco = new RandomAccessFile(toPath, "rw").getChannel();
        ) {
            // 读写的缓冲区(分配一块儿直接内存)
            //要与allocate进行区分
            ByteBuffer bb = ByteBuffer.allocate(byteLength);
            while (true) {
                int len = fci.read(bb);
                if (len == -1) {
                    break;
                }
                bb.flip();
                fco.write(bb);
                bb.clear();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        nioTimeWatch.stop();
        log.info(nioTimeWatch.prettyPrint());
    }

}

1.主函数调用
为排除当前环境不同导致的文件读写效率不同问题,使用多线程分别调用io方法和nio方法

image

2.分别进行IO调用和NIO调用
通过nio和io的读取写入文件方式进行操作

image

3.结果
经过多次测试后,发现nio读取文件的效率是高于io的,尤其是读取大文件的时候

11:12:26.606 [Thread-1] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 1157-----------------------------------------ms     %     Task name-----------------------------------------01157  100%  nioDirectTimeWatch11:12:27.146 [Thread-0] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 1704-----------------------------------------ms     %     Task name-----------------------------------------01704  100%  ioTimeWatch

4 提出疑问
那到底为什么nio的速度要快于普通的io呢,结合源码查看以及网上的资料,核心原因是:
nio读取文件的时候,使用直接内存进行读取,那么,如果在nio中也不使用直接内存的话,会是什么情况呢?

5.再次验证
新增使用堆内存读取文件

image

执行时间验证如下:

11:30:35.050 [Thread-1] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 2653-----------------------------------------ms     %     Task name-----------------------------------------02653  100%  nioDirectTimeWatch11:30:35.399 [Thread-2] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 3038-----------------------------------------ms     %     Task name-----------------------------------------03038  100%  nioHeapTimeWatch11:30:35.457 [Thread-0] INFO com.lzl.netty.study.jvm.DirectBufferTest - StopWatch '': running time (millis) = 3096-----------------------------------------ms     %     Task name-----------------------------------------03096  100%  ioTimeWatch

根据上述的实际验证,nio读写文件比较快的主要原因还是在于使用了直接内存,那么为什么会出现这种情况呢?

2 直接内存的读写性能强的原理

直接上图说明
1.堆内存读写文件

image

堆内存读写文件的步骤:
当JVM想要去和磁盘进行交互的时候,因为JVM和操作系统之间存在读写屏障,所以在进行数据交互的时候需要进行频繁的复制

2.直接内存读写文件

image

直接内存读写文件的步骤
如果使用直接内存进行文件读取的时候,步骤如下

那么,直接内存的使用方式是什么样的呢?

3 nio使用直接内存的源码解读

在阅读源码之前呢,我们首先对于两个知识进行补充

1.虚引用Cleaner sun.misc.Cleaner

什么是虚引用
虚引用所引用的对象,永远不会被回收,除非指向这个对象的所有虚引用都调用了clean函数,或者所有这些虚引用都不可达

上一篇 下一篇

猜你喜欢

热点阅读