深入理解Java虚拟机:堆外内存导致的溢出错误
- 直接内存是什么?
- 直接内存特性
- 问题背景
- 原因
- 解决方案
直接内存是什么?
直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是《Java虚拟机规范》中定义的内存区域。但是这部分内存也被频繁地使用,而且也可能导致OutOfMemoryError异常出现。
在JDK 1.4中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。
显然,本机直接内存的分配不会受到Java堆大小的限制,但是,既然是内存,则肯定还是会受到本机总内存(包括物理内存、SWAP分区或者分页文件)大小以及处理器寻址空间的限制,一般服务器管理员配置虚拟机参数时,会根据实际内存去设置-Xmx等参数信息,但经常忽略掉直接内存,使得各个内存区域总和大于物理内存限制(包括物理的和操作系统级的限制),从而导致动态扩展时出现OutOfMemoryError异常。
直接内存特性
- 直接内存(Direct Memory)是一个数据缓冲区,属于操作系统内存,不由jvm管理
- 特点:常用于NIO(ByteBuffer分配的就是直接内存),分配回收成本较高,但读写性能高
- 正常文件读写过程:磁盘文件 -> 系统缓冲区 -> java缓冲区(new Byte[])
直接内存:在操作系统里划分出一块直接内存,java代码和系统都可以直接访问它 - 直接内存的分配与释放是通过一个Unsafe类型对象(释放通过调用freeMemory),而不是GC
问题背景
在一个4GB内存,运行32位Linux操作系统上,运行一个小型管理系统。测试期间不定时抛出内存溢出异常,并不是稳定出现。这时将堆内存扩大到1.5GB就无法在扩大。设置-XX:+HeapDumpOnOutOfMemoryError也并未出现快照,通过jstat发现垃圾收集也一切正常稳定,但是依旧抛出内存溢出异常。
[org.eclipse.jetty.util.log] handle failed java.lang.OutOfMemoryError: null
at sun.misc.Unsafe.allocateMemory(Native Method)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:99)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
at org.eclipse.jetty.io.nio.DirectNIOBuffer.<init>
原因
虚拟机对堆方法区内存进行回收&#xff0c;但是无法对直接内存进行回收&#xff0c;程序中有大量的NIO操作会用到直接内存。即使FULL GC也无法进行回收。并非在jvm管理之列。
除了Java堆和方法区之外&#xff0c;所有的内存总和受到操作系统进程最大内存的限制&#xff1a;直接内存、·线程堆栈、·Socket缓存区、·JNI代码、虚拟机和垃圾收集器
解决方案
- 在此案例中&#xff0c;可降低java虚拟机堆大小&#xff0c;比如降低为1G&#xff0c;从而给直接内存等更大的空间。并且手动调节过-XX&#xff1a;MaxDirectMemorySize直接内存大小。或者换更大的机器。
如有错误欢迎指正