深入理解Java虚拟机:堆外内存导致的溢出错误

深入理解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异常。

直接内存特性

  1. 直接内存(Direct Memory)是一个数据缓冲区,属于操作系统内存,不由jvm管理
  2. 特点:常用于NIO(ByteBuffer分配的就是直接内存),分配回收成本较高,但读写性能高
  3. 正常文件读写过程:磁盘文件 -> 系统缓冲区 -> java缓冲区(new Byte[])
    直接内存:在操作系统里划分出一块直接内存,java代码和系统都可以直接访问它
  4. 直接内存的分配与释放是通过一个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>

原因

虚拟机对堆方法区内存进行回收,但是无法对直接内存进行回收,程序中有大量的NIO操作会用到直接内存。即使FULL GC也无法进行回收。并非在jvm管理之列。
除了Java堆和方法区之外,所有的内存总和受到操作系统进程最大内存的限制:直接内存、·线程堆栈、·Socket缓存区、·JNI代码、虚拟机和垃圾收集器

解决方案

  1. 在此案例中,可降低java虚拟机堆大小,比如降低为1G,从而给直接内存等更大的空间。并且手动调节过-XX:MaxDirectMemorySize直接内存大小。或者换更大的机器。






如有错误欢迎指正

你可能感兴趣的:(JVM,java,开发语言)