有时候对内存进行大对象的读写,会引起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 |
对于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。
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
|
堆外内存泄露的问题定位通常比较麻烦,可以借助google-perftools这个工具,它可以输出不同方法申请堆外内存的数量。当然,如果你是64位系统,你需要先安装libunwind库。
最后,JDK存在一些direct buffer的bug(比如这个和这个),可能引发OOM,所以也不妨升级JDK的版本看能否解决问题。
文章系本人原创,转载请保持完整性并注明出自《四火的唠叨》
相关推荐
Java Collections 实现,使用堆外内存扩展到数百万个元素,而不会遇到 GC 错误。 它利用 Google 的 LevelDB 项目来实现这一点。 目前这个 API 支持三种类型的 Collections java.util.Map java.util.List...
介绍HBase堆外内存测试
这个视频指导我们java开发遇到堆内存使用正常、gc正常,但实际使用的物理内存缺非常高,特别是容器化部署很容易因内存使用超limit限制导致pod重启,怀疑堆外内存泄露?如何排查堆外内存的使用情况,这个视频详细讲解...
Java堆外内存泄露场景总结,包含几个常用的可能,如:JNI,NIO,AWT/Swing,Inflater&Deflater;
在Linux下,使用gperftools分析对外内存泄漏。介绍了基本的安装和使用。
堆外内存其实并无特别之处。线程栈,应用程序代码,NIO缓存用的都是堆外内存。事实上在C或者C++中,你只能使用未托管内存,因为它们默认是没有托管堆(managed heap)的。在Java中使用托管内存或者“堆”内存是这...
突破JVM的堆外内存部分代码
本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明: 相关背景–>读写操作–>关键属性–>读写实践–>扩展–>参考说明 希望对想使用直接内存的朋友,提供点快捷的参考。 数据类型 ...
Yu Li explains how Alibaba met the challenge of tens of millions requests per second to its Alibaba-Search HBase cluster on 2016 Singles' Day. With read-path off-heaping, Alibaba improved the ...
Java堆外内存的使用Java开发Java经验技巧共5页.pdf.zip
快速。 性能在多核CPU上扩展。线程安全的。 并发goroutine可以读写单个缓存实例。fastcache设计用于存储大量项目而无需GC开销。 Fastcache在创建期间达到设置的最大大小时会自动驱逐旧条目。
Java开发者必须了解的堆外内存技术.docx
1.引言很久没有遇到堆外内存相关的问题了,五一假期刚结束,便不期而遇,以前也处理过几次这类问题,但都没有总结,觉得是时候总结一下了。先来看一个 Demo:在 D
这是一个简单的项目,显示了如何使用来查找Haskell程序中的堆外内存泄漏。该程序测试程序以两种不同方式分配堆外内存。 首先,通过直接调用mallocBytes ,其次,通过调用本身发生的库函数间接调用mallocBytes 。 ...
主要介绍了浅谈Java堆外内存之突破JVM枷锁,涉及jvm内存分配,jvm垃圾回收,堆外内存的垃圾回收等相关内容,具有一定参考价值,需要的朋友可以了解下。
目前支持内存、堆外内存、磁盘缓存。 功能特点: 支持二级缓存:Memory、Persistence 各个缓存可以拥有有效时间,超过时间缓存会过期 Memory 默认支持 FIFO、LRU、LFU 算法的实现 Memory 额外支持 Guava Cache、...
片 Slice是一个Java库,用于有效地处理堆内存和堆外内存。
由于在开发和面试中往往都会遇到java内存泄露问题,所以综合个人平时遇到的和搜集了些网上的关于内存泄露问题写了这篇总结,同时加了点小例子,希望对大家有所帮组,如果有所不全,希望给我留言提出来,供大家相互...
该项目为HZ地图提供堆外存储。 这是非官方的扩展和Hazelcast公司内部HZ它将替换不支持ConcurrentHashMap与离堆HTreeMap从 。 它独立于其他非堆解决方案,并且在Apache License 2.0下免费提供,没有附加任何挂钩。 ...
不安全适配器一个工具包,用于协助使用Java Unsafe类来分配和管理本地堆外内存块。