JVM技术专题 深入分析回顾堆外内存使用和分析「分析篇」
Posted 浩宇の天尚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM技术专题 深入分析回顾堆外内存使用和分析「分析篇」相关的知识,希望对你有一定的参考价值。
堆外内存
堆外内存,其实就是不受JVM控制的内存。简单来说,除了堆栈内存,剩下的就都是堆外内存了(当然,这是从Java运行时内存的角度来看),堆外内存直接受操作系统管理,而不是虚拟机。而使用堆外内存的原因,
相比于堆内内存有几个优势:
- 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作(可能使用多线程或者时间片的方式,根本感觉不到)
堆外内存是直接受操作系统管理的,而不是JVM,因此使用堆外内存的话,就可以保持一个比较小的堆内内存,减少垃圾回收对程序性能的影响。
- 就是提高IO操作的效率!这里就涉及用户态与内核态,以及内核缓冲区的概念,如果从堆内向磁盘写数据,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,但使用堆外内存的话则可以避免这个复制操作。
堆内内存其实就是用户进程的【进程缓冲区】,属于用户态;堆外内存由操作系统管理【内核缓冲区】,属于内核态。
自然也有不好的一面:
-
堆外内存难以控制,如果内存泄漏,那么很难排查
-
堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合。
-
因为是操作系统的内存机制,所以需要通过本地方法进行分配,较为复杂和缓慢
直接内存使用
-
堆外内存通过
java.nio
的ByteBuffer
来创建,调用allocateDirect方法申请即可。 -
可以通过设置
-XX:MaxDirectMemorySize=10M
控制堆外内存的大小。
堆外内存的垃圾回收
-
由于堆外内存并不直接控制于JVM,因此只能等到full GC的时候才能垃圾回收!Full GC,一般发生在年老代垃圾回收以及调用System.gc的时候,这样肯定不能满足我们的需求!
-
手动的控制回收堆外内存了!其中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);
零拷贝
-
用户进程需要像磁盘写数据时,需要将用户缓冲区(进程缓冲区)堆内内存中的内容拷贝到内核缓冲区(堆外内存)中,操作系统调度内核再将内核缓冲区中的内容写进磁盘中。
-
通过在用户进程中,直接申请堆外内存,存储其需要写进磁盘的数据,就能够省掉上述拷贝操作。
实现方式
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();
以上是关于JVM技术专题 深入分析回顾堆外内存使用和分析「分析篇」的主要内容,如果未能解决你的问题,请参考以下文章
JVM技术专题「原理专题」深入剖析Java对象内存分配及跨代引用分析