调优实例 - 堆外内存导致的溢出错误

前言

在服务器运行过程中,会遇到各种各样的内存溢出异常OutOfMemory,今天介绍一种容易被忽视的异常内存情况:堆外内存

现象

  1. 发现服务器不定时地会抛出内存溢出异常;
  2. 尝试把堆开到最大(32位系统最多到1,6G),没有任何效果,反而异常频繁;
  3. 加入-XX:+HeapDumpOnOutOfMemoryError,没有任何反应,内存溢出时不会产生dump文件;
  4. jstat工具监测,GC并不频繁;
  5. 查看系统日志如下:
    [org.eclipse.jetty.util.log] handle failed java.lang.OutOfMemoryError: null 
    at sun.misc.Unsafe.allocateMemory(Natave Method)
    at java.nio.DirecByteBuffer.<init>(DirectByteBuffer.java:99)
    at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)\
    at org.eclipse.jetty.io.nio.DirectNIOBuffer.<init>
    ......

分析

此时,看到上面的异常现象后,可以得知异常的原因是因为堆外内存不够引起。32位系统下,给Java堆分配的内存越大,堆外内存越小,因为总量是固定的,最大是2G。

异常关键原因:
垃圾收集时,虚拟机会对Direct Memory进行回收,但是不会主动回收。只能等到老年代满了后,发送Full GC时,才会对Direct Memory进行回收,这是一个“顺便清理”的过程。所以,当Direct Memory不够用时,自然会抛出内存溢出异常。

扩展

除了Java堆和永久代之外,下面这几个区域也会占用较多的内存,这里所有内存的总和受到操作系统进程最大内存的限制。

  1. Direct Memory
    可通过-XX:MaxDirectMemorySize调整大小。
    内存不足时,抛出OurtOfMemoryError或者OutOfMemoryError:Direct buffer memory。
  2. 线程堆栈
    可通过-Xss调整大小。
    内存不足时,抛出StackOverflowError(纵向无法分配,即无法分配新的栈帧);或者OutOfMemoryError:unable to create new thread(横向无法分配,即无法建立新的线程)。
  3. Socket缓存区
    每个Socket连接都会有相应的两个缓存区,即Receive和Send,大小分别约37KB和25KB。链接数量多的话,这块内存区域的开销也比较大。如果无法分配,则可能会抛出IOException:Too many open files异常。
  4. JNI代码
    如果代码中使用JNI调用本地库,那本地库使用的内存也不在堆中。
  5. 虚拟机和GC
    虚拟机、GC的代码执行,也要消耗一定的内存。

猜你喜欢

转载自my.oschina.net/xiaowangqiongyou/blog/1790267