文章目录
- 1、前言
- 2、直接内存
- 3、本机直接内存溢出
- 4、小结
1、前言
前面学习方法区的时候了解到:JDK8 的元空间使用的是直接内存。那么,直接内存是什么意思呢?它和虚拟机内存有什么区别吗?
直接内存不是虚拟机运行时数据区的一部分,也不是《Java 虚拟机规范》中定义的内存区域。直接内存是在 Java 堆外的、直接向系统申请的内存区间。
2、直接内存
一般来说,Java 虚拟机里面有一个运行时数据区,JVM 运行期间产生的信息都是在 JVM 管理的内存里面,但是在一些场景里,会使用到本地的内存,例如元空间。
我们的 JVM 要从物理磁盘中对文件进行操作,先是通过 JVM 的地址空间,然后经过操作系统的内核地址空间才操作物理磁盘。读写文件需要与磁盘交互,需要由用户态切换到内核态。如下图所示,需要两份内存存储重复数据,效率低。
在 JDK1.4 中加入了一个 NIO 类(New Input/Output),引入了一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆中来回复制数据。
我们运行以下代码:
package pers.klb.JvmDemo2020;
import java.nio.ByteBuffer;
import java.util.Scanner;
public class BufferTest {
private static final int BUFFER = 1024 * 1024 * 1024;//1GB
public static void main(String[] args){
//直接分配本地内存空间
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
System.out.println("直接内存分配完毕,请求指示!");
Scanner scanner = new Scanner(System.in);
scanner.next();
System.out.println("直接内存开始释放!");
byteBuffer = null;
System.gc();
scanner.next();
}
}
我们通过任何 JDK 的内存工具都看不到它占用的这 1GB 内存。
但是可以从任务管理器当中看到:
上面的情况进一步证明直接内存是不受 JVM 管理的。
显然,本机直接内存的分配不会受到 Java 堆大小的限制,但是,既然是内存,则肯定还会受到本机总内存大小以及处理器寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx
等参数信息,但经常忽略直接内存,是的各个内存区域总大小超过限制。
我们可以简单理解为:Java 运行总内存 = heap + native memory。
3、本机直接内存溢出
直接内存的容量可以通过参数-XX:MaxDirectMemorySize
参数来指定,默认是和 Java 堆最大值一致。
虽然使用 DirectBuffer 分配内存也会抛出内存溢出异常,但是它抛出异常的时候并没有真正向操作系统申请分配内存,而是通过计算得知直接内存无法分配,然后在代码里手动抛出溢出异常。
正是因为是计算它将会溢出,而不是做了才溢出,在 Heap Dump 文件我们就不会看到有什么内存异常的情况,如果你发现代码发生了内存溢出,然而 Dump 文件又很正常,那肯定是程序直接或者间接使用了直接内存,然后 JVM 计算出的内存不足,手动抛出的异常,而不是真的发生了异常。可以直接去检查直接内存方面的问题了。
4、小结
至此,我们明白了虚拟机里面的内存是如何划分的,哪部分区域、什么样的代码和操作可能导致内存溢出异常。下面将会继续学习 JVM 的其他组成部分。