详细总结Java堆栈内存、堆外内存、零拷贝浅析与代码实现

作者:JiangNanMax 时间:2023-11-21 01:52:29 

一、堆栈内存

堆栈内存,顾名思义,指的是堆内存以及栈内存,其中,堆内存是由Java GC进行管理的内存区域,而栈内存则是线程内存。关于栈内存,这里不去细说。以Hotspot为例,堆内存的简要结构如下图所示:

详细总结Java堆栈内存、堆外内存、零拷贝浅析与代码实现

而堆栈的关系,我们可以通过一行简单的代码来理解:


public static void main(String[] args) {
   Object o = new Object();
}

上述代码主要完成了两件事,new Object( ) 在堆上开辟了一块内存,也就是说,new Object( )是分配在堆上的;而变量o,则是在线程main的栈上面的,它指向了new Object( ) 开辟的堆内存地址。简单来说,程序中创建的对象,都存储在堆内存中,栈内存包含对它的引用。

二、堆外内存

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

  • 一定程度上减少了GC,堆外内存是直接受操作系统管理的,而不是JVM,因此使用堆外内存的话,就可以保持一个比较小的堆内内存,减少垃圾回收对程序性能的影响。这一块,在Kafka中就应用得很好,感兴趣的同学可以去了解一下;

  • 还有一个更大的优点,就是提高IO操作的效率!这里就涉及用户态与内核态,以及内核缓冲区的概念,具体可以看笔者之前的一篇文章Java随笔记 - 内核缓冲区与进程缓冲区。其中,堆内内存其实就是用户进程的进程缓冲区,属于用户态,而堆外内存由操作系统管理,属于内核态。如果从堆内向磁盘写数据,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,但使用堆外内存的话则可以避免这个复制操作。

详细总结Java堆栈内存、堆外内存、零拷贝浅析与代码实现

三、零拷贝

总结上述内容中对堆栈内存与堆外内存的说明,主要解决了两个疑问:“零拷贝”是从哪拷贝到哪?“零拷贝”是怎么优化掉这一拷贝操作的?

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

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

在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;

/**
* @author jiangnanmax
* @email jiangnanmax@gmail.com
* @description CopyCompare
* @date 2021/5/7
**/

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();
   }

}

来源:https://blog.csdn.net/J__Max/article/details/116651576

标签:Java,堆栈内存,堆外内存,零拷贝浅析
0
投稿

猜你喜欢

  • Android快速实现断点续传的方法

    2021-06-14 13:02:28
  • Java正则验证正整数的方法分析【测试可用】

    2022-08-02 21:50:05
  • React-Native之Android(6.0及以上)权限申请详解

    2023-07-28 01:56:55
  • spring boot和mybatis集成分页插件

    2021-11-05 10:21:17
  • C#中异步回调函数用法实例

    2023-01-05 13:10:53
  • Flutter 如何正确显示SnackBar

    2023-06-23 13:00:40
  • Idea自动生成Entity实现过程详解

    2022-06-03 12:18:24
  • Android互联网访问图片并在客户端显示的方法

    2021-12-26 21:25:10
  • Java开发之Lombok指南

    2022-11-19 21:49:28
  • Android 中ScrollView嵌套GridView,ListView的实例

    2023-06-15 15:49:56
  • SpringBoot之通过BeanPostProcessor动态注入ID生成器案例详解

    2023-11-24 22:17:26
  • Java实现排球比赛计分系统

    2021-07-11 09:11:14
  • Unity3D使用Shader实现腐蚀消失

    2022-01-07 20:57:59
  • winfrom 在业务层实现事务控制的小例子

    2021-11-16 14:19:50
  • Android实现长按back键退出应用程序的方法

    2023-03-26 21:49:43
  • 简单实现Android刮刮卡效果

    2022-08-12 01:49:00
  • Java的Spring框架中bean的继承与内部bean的注入

    2023-06-17 18:50:44
  • Android小工具自定义view课表

    2023-02-12 14:51:41
  • Java 比较接口comparable与comparator区别解析

    2022-11-26 20:54:24
  • Java编程实现判断网上邻居文件是否存在的方法

    2022-12-27 09:49:46
  • asp之家 软件编程 m.aspxhome.com