`

java.lang.OutOfMemoryError: Direct buffer memory使用堆外内存[转]

 
阅读更多

转:http://blog.csdn.net/zhouhl_cn/article/details/6573213

创建Buffer对象时,可以选择从JVM堆中分配内存,也可以OS本地内存中分配,由于本地缓冲区避免了缓冲区复制,在性能上相对堆缓冲区有一定优势,但同时也存在一些弊端。

两种缓冲区对应的API如下:

  • JVM堆缓冲区:ByteBuffer.allocate(size)
  • 本地缓冲区:ByteBuffer.allocateDirect(size)

从堆中分配的缓冲区为普通的Java对象,生命周期与普通的Java对象一样,当不再被引用 时,Buffer对象会被回收。而直接缓冲区(DirectBuffer)为本地内存,并不在Java堆中,也不能被JVM垃圾回收。由于直接缓冲区在 JVM里被包装进Java对象DirectByteBuffer中,当它的包装类被垃圾回收时,会调用相应的JNI方法释放本地内存,所以本地内存的释放 也依赖于JVM中DirectByteBuffer对象的回收。

 

由于垃圾回收本身成本较高,一般JVM在堆内存未耗尽时,不会进行垃圾回收操作。我们知道在32位机器上,每个进程的最大可用内存为4G,用户可用 内存在大概为3G左右,如果为堆分配过大的内存时,本地内存可用空间就会相应减少。当我们为堆分配较多的内存时,JVM可能会在相当长的时间内不会进行垃 圾回收操作,从而本地内存不断分配,无法释放,最终导致OutOfMemoryError。

 

由此可见,在使用直接缓冲区之前,需要做出权衡:

  1. 堆缓冲区的性能已经相当高,若无必要,使用堆缓冲区足矣。若确实有提升性能的必要时,再考虑使用本地缓冲区。
  2. 为JVM分配堆内存时,并不是越大越好,堆内存越大,本地内存就越小,根据具体情况决定,主要针对32位机器,64位机器上不存在该问题。

-------------------------------------------------

转:http://www.raychase.net/1526

使用堆外内存

有时候对内存进行大对象的读写,会引起JVM长时间的停顿,有时候则是希望最大程度地提高JVM的效率,我们需要自己来管理内存(看起来很像是 Java像C++祖宗的妥协吧)。据我所知,很多缓存框架都会使用它,比如我以前使用过的EhCache(给它包装了个酷一点的名字,叫 BigMemory),以及现在项目中的Memcached。在nio以前,是没有光明正大的做法的,有一个work around的办法是直接访问Unsafe类。如果你使用Eclipse,默认是不允许访问sun.misc下面的类的,你需要稍微修改一下,给Type Access Rules里面添加一条所有类都可以访问的规则:

使用堆外内存

在使用Unsafe类的时候:

1
Unsafe f = Unsafe.getUnsafe();

发现还是被拒绝了,抛出异常:

1
java.lang.SecurityException: Unsafe

正如Unsafe的类注释中写道:

Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it.

于是,只能无耻地使用反射来做这件事; 

1
2
3
4
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe us = (Unsafe) f.get(null);
long id = us.allocateMemory(1000);

其中,allocateMemory返回一个指针,并且其中的数据是未初始化的。如果要释放这部分内存的话,需要调用freeMemory或者 reallocateMemory方法。Unsafe对象提供了一系列put/get方法,例如putByte,但是只能一个一个byte地put,我不 知道这样会不会影响效率,为什么不提供一个putByteArray的方法呢?

从nio时代开始,可以使用ByteBuffer等类来操纵堆外内存了: 

1
ByteBuffer buffer = ByteBuffer.allocateDirect(numBytes);

像Memcached等等很多缓存框架都会使用堆外内存,以提高效率,反复读写,去除它的GC的影响。可以通过指定JVM参数来确定堆外内存大小限制(有的VM默认是无限的,比如JRocket,JVM默认是64M): 

1
-XX:MaxDirectMemorySize=512m

对于这种direct buffer内存不够的时候会抛出错误: 

1
java.lang.OutOfMemoryError: Direct buffer memory

千万要注意的是,如果你要使用direct buffer,一定不要加上DisableExplicitGC这个参数,因为这个参数会把你的System.gc()视作空语句,最后很容易导致OOM。

对于heap的OOM我们可以通过执行jmap -heap来获取堆内内存情况,例如以下输出取自我上周定位的一个问题: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
 
Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 2147483648 (2048.0MB)
   NewSize          = 16777216 (16.0MB)
   MaxNewSize       = 33554432 (32.0MB)
   OldSize          = 50331648 (48.0MB)
   NewRatio         = 7
   SurvivorRatio    = 8
   PermSize         = 16777216 (16.0MB)
   MaxPermSize      = 67108864 (64.0MB)
 
Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 30212096 (28.8125MB)
   used     = 11911048 (11.359260559082031MB)
   free     = 18301048 (17.45323944091797MB)
   39.42476549789859% used
Eden Space:
   capacity = 26869760 (25.625MB)
   used     = 11576296 (11.040016174316406MB)
   free     = 15293464 (14.584983825683594MB)
   43.08298994855183% used
From Space:
   capacity = 3342336 (3.1875MB)
   used     = 334752 (0.319244384765625MB)
   free     = 3007584 (2.868255615234375MB)
   10.015510110294118% used
To Space:
   capacity = 3342336 (3.1875MB)
   used     = 0 (0.0MB)
   free     = 3342336 (3.1875MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 2113929216 (2016.0MB)
   used     = 546999648 (521.6595153808594MB)
   free     = 1566929568 (1494.3404846191406MB)
   25.875968024844216% used
Perm Generation:
   capacity = 45715456 (43.59765625MB)
   used     = 27495544 (26.22179412841797MB)
   free     = 18219912 (17.37586212158203MB)
   60.144962788952604% used

可见堆内存都是正常的,重新回到业务日志里寻找异常,发现出现在堆外内存的分配上: 

1
2
3
4
5
java.lang.OutOfMemoryError
 at sun.misc.Unsafe.allocateMemory(Native Method)
 at java.nio.DirectByteBuffer.(DirectByteBuffer.java:101)
 at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:288)
 at com.schooner.MemCached.SchoonerSockIOPool$TCPSockIO.(Unknown Source)

对于这个参数分配过小的情况下造成OOM,不妨执行jmap -histo:live看看(也可以用JConsole之类的外部触发GC),因为它会强制一次full GC,如果堆外内存明显下降,很有可能就是堆外内存过大引起的OOM。

对于堆外内存的使用率,可以使用rednaxelafx做的一个工具来查看:链接

BTW,如果在执行jmap命令时遇到:

1
Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process

这个算是JDK的一个bug(链接),只要是依赖于SA(Serviceability Agent)的工具,比如jinfo/jstack/jmap都会存在这个问题,但是Oracle说了“won’t fix”……

Ubuntu 10.10 and newer has a new default security policy that affects Serviceability commands. This policy prevents a process from attaching to another process owned by the same UID if the target process is not a descendant of the attaching process.

不过它也是给了解决方案的,需要修改/etc/sysctl.d/10-ptrace.conf:

1
kernel.yama.ptrace_scope = 0

如果你的操作系统不是Ubuntu,可以升级一下JDK的版本试试,我在RedHat上遇到过这样的问题,升级JDK版本以后解决了。

堆外内存泄露的问题定位通常比较麻烦,可以借助google-perftools这个工具,它可以输出不同方法申请堆外内存的数量。当然,如果你是64位系统,你需要先安装libunwind库

最后,JDK存在一些direct buffer的bug(比如这个这个),可能引发OOM,所以也不妨升级JDK的版本看能否解决问题。

 

文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接《四火的唠叨》

分享到:
评论

相关推荐

    系统稳定性——OutOfMemoryError常见原因及解决方法1

    当Java应用程序尝试分配新的对象,但堆内存(Heap Space)不足以容纳这些对象时,就会抛出`java.lang.OutOfMemoryError: Java heap space`。此错误可能由以下几个原因引起: - 创建超大数据结构,如大数组。 - ...

    关于java堆内存溢出的几种情况

    【情况四】:`java.lang.OutOfMemoryError: Direct buffer memory` 这种情况发生在NIO(非阻塞I/O)中,因为直接缓冲区内存分配失败。可以通过设置`-XX:MaxDirectMemorySize`参数来限制直接内存大小,如`-XX:...

    无私奉献-jvm面试备战

    OutOfMemoryError 异常有多种场景,包括 java.lang.OutOfMemoryError: Java heap space、java.lang.OutOfMemoryError: GC overhead limit exceeded 和 java.lang.OutOfMemoryError: Direct buffer memory 等。

    Java内存溢出解决办法

    如果直接内存过大,也会导致`java.lang.OutOfMemoryError: Direct buffer memory`。可以通过设置-Dsun.nio.ch maks.directBufferCount和-Djnio-direct-buffer-size来限制直接内存。 解决Java内存溢出问题通常需要...

    weblogic调优memory内存不足与内存泄漏问题

    当直接内存溢出时,会抛出`java.lang.OutOfMemoryError: Direct buffer memory`异常。 内存泄漏是指程序中已动态分配的堆内存由于某种原因无法释放回系统,导致内存占用持续增加。常见的内存泄漏原因有: 1. **长...

    教你分析9种OOM常见原因及解决方案.docx

    8. Java.lang.OutOfMemoryError: Direct Buffer 错误 * 原因分析:直接缓冲区的内存不足 * 解决方案:增加直接缓冲区的内存大小、检查直接缓冲区的使用情况、优化直接缓冲区的使用 9. Java.lang.OutOfMemoryError:...

    JVM+多线程.pdf

    - **java.lang.OutOfMemoryError: Direct buffer memory**:直接内存不足。 - **java.lang.StackOverflowError**:线程栈空间不足。 - **java.lang.OutOfMemoryError: unable to create new native thread**:无法为...

    OOMD-LAB

    如果过度使用,可能出现`OutOfMemoryError: Direct buffer memory`。 6. **内存诊断工具**:如JVisualVM、JProfiler等,可以帮助分析堆转储(heap dump)、GC日志,找出内存泄漏或不当内存使用。 7. **垃圾收集**...

Global site tag (gtag.js) - Google Analytics