详细总结Java堆栈内存、堆外内存、零拷贝浅析与代码实现
作者:JiangNanMax 发布时间:2023-11-21 01:52:29
一、堆栈内存
堆栈内存,顾名思义,指的是堆内存以及栈内存,其中,堆内存是由Java GC进行管理的内存区域,而栈内存则是线程内存。关于栈内存,这里不去细说。以Hotspot为例,堆内存的简要结构如下图所示:
而堆栈的关系,我们可以通过一行简单的代码来理解:
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中,提供了一些使用堆外内存以及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


猜你喜欢
- 引言本文分析示例代码如下:launch(Dispatchers.Main) { flow { &nb
- 本文实例实现一个如下图所示的Android折线图,供大家参考,具体内容如下首先是控件绘图区域的划分,控件左边取一小部分(控件总宽度的八分之一
- 这里我先简单描述一下需求:服务器返回的是html页面的一部分带有标签的内容。解决的思路是:将服务器返回的内容片段拼凑成一个完整的页面。下面直
- CDMA猫真是!@#¥#%(*,连PDU都不支持,只能发文本短信。而且发中文短信居然是UNICODE,无法在超级终端里输入。只能写程序。 网
- 最近接了个项目其中有需要要实现此功能:seekbar需要显示最左和最右值,进度要跟随进度块移动。下面通过此图给大家展示下效果,可能比文字描述
- 一个发送验证码的需求:包括限制文本框输入长度和只允许输入数字按惯例 先上图:class MyBody extends StatefulWid
- 1、对称二叉树【OJ链接】分为以下几种情况:二叉树为空,是对称二叉树二叉树不为空,其左子树或者右子树为空,不是对称二叉树二叉树不为空,左右子
- 我们来简单实现一个cookie。一、简单介绍Cookie 是一些数据, 存储于你电脑上的文本文件中。当 web 服务器向浏览器发送 web
- C# 中可以将类、结构或接口的定义拆分到两个或多个源文件中,在类声明前添加partial关键字即可。1. 什么是局部类型?C# 2.0 引入
- 在Java 5以前,是用synchronized关键字来实现锁的功能。synchronized关键字可以作为方法的修饰符(同步方法),也可作
- 本文由老王家组装电脑引出——建造者设计模式,详细介绍建造者模式的基本概念和实现代码,为了便于理解建造
- 一、Spring Boot的特点首先我们要知道 Spring Boot 在底层已经为我们添加好了很多依赖。比如我们常用的Tomcat,Spr
- 1 支持文件类型.txt.html.htm.xml.bytes.json.csv.yaml.fnt2 寻找文件1 //Load textur
- 本文实例为大家分享了C#实现套接字发送接收数据的具体代码,供大家参考,具体内容如下服务端namespace TestServer{ &nbs
- 废话不多说了,直接给大家贴代码了,具体代码如下所示:html代码如下:<body><input id="file
- 在实际开发中,我们经常会需要在页面跳转的时候携带路由参数,典型的例子就是从列表到详情页的时候,需要携带详情的 id,以便详情页获取对应的数据
- 初始化方式一:@PostConstruct注解假设类UserController有个成员变量UserService被@Autowired修饰
- SearchView是android系统中内置的一个搜索框组件,可以很方便在添加在用户界面之上,但是也带来了一些问题,那就是searchvi
- 对于maven的部署和安装插件不熟的同学可以看一下上两篇文章maven的部署和安装:此笔记已经集成了maven的插件。一、构建Maven项目
- 什么是Dozer?Dozer是一种Java Bean到Java Bean的映射器,递归地将数据从一个对象复制到另一个对象,它是一个强大的,通