zhanjia 2014-04-02
转:http://blog.csdn.net/zhouhl_cn/article/details/6573213
创建Buffer对象时,可以选择从JVM堆中分配内存,也可以OS本地内存中分配,由于本地缓冲区避免了缓冲区复制,在性能上相对堆缓冲区有一定优势,但同时也存在一些弊端。
两种缓冲区对应的API如下:
从堆中分配的缓冲区为普通的Java对象,生命周期与普通的Java对象一样,当不再被引用 时,Buffer对象会被回收。而直接缓冲区(DirectBuffer)为本地内存,并不在Java堆中,也不能被JVM垃圾回收。由于直接缓冲区在 JVM里被包装进Java对象DirectByteBuffer中,当它的包装类被垃圾回收时,会调用相应的JNI方法释放本地内存,所以本地内存的释放 也依赖于JVM中DirectByteBuffer对象的回收。
由于垃圾回收本身成本较高,一般JVM在堆内存未耗尽时,不会进行垃圾回收操作。我们知道在32位机器上,每个进程的最大可用内存为4G,用户可用 内存在大概为3G左右,如果为堆分配过大的内存时,本地内存可用空间就会相应减少。当我们为堆分配较多的内存时,JVM可能会在相当长的时间内不会进行垃 圾回收操作,从而本地内存不断分配,无法释放,最终导致OutOfMemoryError。
由此可见,在使用直接缓冲区之前,需要做出权衡:
-------------------------------------------------
转: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的版本看能否解决问题。
文章未经特殊标明皆为本人原创,未经许可不得用于任何商业用途,转载请保持完整性并注明来源链接《四火的唠叨》