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

一、堆栈内存

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

java 堆外内存分析工具 java堆外内存_java

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

1. public static void main(String[] args) {

2.      Object o = new Object();
3. }

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

二、堆外内存

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

  • 一定程度上减少了GC,堆外内存是直接受操作系统管理的,而不是JVM,因此使用堆外内存的话,就可以保持一个比较小的堆内内存,减少垃圾回收对程序性能的影响。这一块,在Kafka中就应用得很好,感兴趣的同学可以去了解一下;
  • 还有一个更大的优点,就是提高IO操作的效率!这里就涉及用户态与内核态,以及内核缓冲区的概念,具体可以看笔者之前的一篇文章Java随笔记 - 内核缓冲区与进程缓冲区。其中,堆内内存其实就是用户进程的进程缓冲区,属于用户态,而堆外内存由操作系统管理,属于内核态。如果从堆内向磁盘写数据,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,但使用堆外内存的话则可以避免这个复制操作。

java 堆外内存分析工具 java堆外内存_堆栈_02

三、零拷贝

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

  • 用户进程需要像磁盘写数据时,需要将用户缓冲区(堆内内存)中的内容拷贝到内核缓冲区(堆外内存)中,操作系统再将内核缓冲区中的内容写进磁盘中;
  • 通过在用户进程中,直接申请堆外内存,存储其需要写进磁盘的数据,就能够省掉上述拷贝操作。

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

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

1. package top.jiangnanmax.nio;
2. 
3. import java.io.*;
4. import java.nio.MappedByteBuffer;
5. import java.nio.channels.FileChannel;
6. 
7. /**
8.      * @author jiangnanmax
9.      * @email jiangnanmax@gmail.com
10.      * @description CopyCompare
11.      * @date 2021/5/7
12.      **/
13. 
14. public class CopyCompare {

15. 
16.      public static void main(String[] args) throws Exception {

17.          String inputFile = "/tmp/nio/input/HyperLedger.pdf";
18.          String outputFile = "/tmp/nio/output/HyperLedger.pdf";
19. 
20.          long start = System.currentTimeMillis();
21. 
22.          nioCopyByDirectMem(inputFile, outputFile);
23. 
24.          long end = System.currentTimeMillis();
25. 
26.          System.out.println("cost time: " + (end - start) + " ms");
27. 
28.          deleteFile(outputFile);
29.      }
30. 
31.      /**
32.          * 使用传统IO进行文件复制
33.          *
34.          * 平均耗时 15** ms
35.          *
36.          * @param sourcePath
37.          * @param destPath
38.          */
39.      private static void bioCopy(String sourcePath, String destPath) throws Exception {

40.          File sourceFile = new File(sourcePath);
41.          File destFile = new File(destPath);
42.          if (!destFile.exists()) {

43.              destFile.createNewFile();
44.          }
45. 
46.          FileInputStream inputStream = new FileInputStream(sourceFile);
47.          FileOutputStream outputStream = new FileOutputStream(destFile);
48. 
49.          byte[] buffer = new byte[512];
50.          int lenRead;
51. 
52.          while ((lenRead = inputStream.read(buffer)) != -1) {

53.              outputStream.write(buffer, 0, lenRead);
54.          }
55. 
56.          inputStream.close();
57.          outputStream.close();
58.      }
59. 
60.      /**
61.          * 使用NIO进行文件复制,但不使用堆外内存
62.          *
63.          * 平均耗时 1** ms, 比BIO直接快了一个数量级???
64.          *
65.          * @param sourcePath
66.          * @param destPath
67.          */
68.      private static void nioCopy(String sourcePath, String destPath) throws Exception {

69.          File sourceFile = new File(sourcePath);
70.          File destFile = new File(destPath);
71.          if (!destFile.exists()) {

72.              destFile.createNewFile();
73.          }
74. 
75.          FileInputStream inputStream = new FileInputStream(sourceFile);
76.          FileOutputStream outputStream = new FileOutputStream(destFile);
77. 
78.          FileChannel inputChannel = inputStream.getChannel();
79.          FileChannel outputChannel = outputStream.getChannel();
80. 
81.          // transferFrom底层调用的应该是sendfile
82.          // 直接在两个文件描述符之间进行了数据传输
83.          // DMA
84. 
85.          outputChannel.transferFrom(inputChannel, 0, inputChannel.size());
86. 
87.          inputChannel.close();
88.          outputChannel.close();
89.          inputStream.close();
90.          outputStream.close();
91. 
92.      }
93. 
94.      /**
95.          * 使用NIO进行文件复制,并使用堆外内存
96.          *
97.          * 平均耗时100ms上下,比没使用堆外内存的NIO快一点
98.          *
99.          * @param sourcePath
100.          * @param destPath
101.          */
102.      private static void nioCopyByDirectMem(String sourcePath, String destPath) throws Exception {

103.          File sourceFile = new File(sourcePath);
104.          File destFile = new File(destPath);
105.          if (!destFile.exists()) {

106.              destFile.createNewFile();
107.          }
108. 
109.          FileInputStream inputStream = new FileInputStream(sourceFile);
110.          FileOutputStream outputStream = new FileOutputStream(destFile);
111. 
112.          FileChannel inputChannel = inputStream.getChannel();
113.          FileChannel outputChannel = outputStream.getChannel();
114. 
115.          MappedByteBuffer buffer = inputChannel.map(FileChannel.MapMode.READ_ONLY, 0, inputChannel.size());
116. 
117.          outputChannel.write(buffer);
118. 
119.          inputChannel.close();
120.          outputChannel.close();
121.          inputStream.close();
122.          outputStream.close();
123. 
124.      }
125. 
126.      /**
127.          * 删除目标文件
128.          *
129.          * @param target
130.          */
131.      private static void deleteFile(String target) {

132.          File file = new File(target);
133.          file.delete();
134.      }
135. 
136. }

到此这篇关于详细总结Java堆栈内存、堆外内存、零拷贝浅析与代码实现的文章就介绍到这了!