`

使用堆外内存

阅读更多

有时候对内存进行大对象的读写,会引起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的版本看能否解决问题。

文章系本人原创,转载请保持完整性并注明出自《四火的唠叨》

0
0
分享到:
评论
1 楼 zy101843 2013-07-29  
感觉楼主应该标注一下是非GC管理的内存,只要是new数据都是属于堆内存的。

相关推荐

    HBase堆外内存测试

    - **主机B**:采用堆内和堆外内存组合的方式,其中堆内内存使用量较少,而堆外内存中存储的数据块量达到1008.39MB。 这种配置使得主机B能够在保持相同缓存容量的情况下,更好地平衡堆内和堆外内存的使用。 #### 五...

    堆外内存部分代码

    5. **性能优化**:堆外内存的使用可以提高大数据处理或高并发场景下的性能,但过度依赖堆外内存可能导致内存碎片,需要权衡利弊并进行适当优化。 6. **元空间(Metaspace)**:自Java 8开始,元空间取代了之前的...

    Java堆外内存的使用Java开发Java经验技巧共5页

    Java开发中的堆外内存使用是优化程序性能的重要策略之一,尤其在处理大数据、高并发或者内存敏感的应用场景中。堆外内存,也被称为直接内存(Direct Memory),是指不在JVM的堆内存中分配的内存,而是直接在操作系统...

    深入解读 Java 堆外内存(直接内存)1

    - **性能监控**:在使用堆外内存时,需要定期监控系统的内存使用情况,确保系统稳定运行。 总的来说,Java堆外内存是一种高级特性,适用于对性能有极高要求的应用场景。但同时也需要开发者具备一定的内存管理知识,...

    Java开发者必须了解的堆外内存技术.docx

    了解堆外内存的创建过程有助于开发者优化内存使用,尤其是在开发高性能、低延迟的应用时。然而,过度使用堆外内存也可能导致系统资源紧张,甚至引发稳定性问题。因此,合理控制堆外内存的使用量,监控...

    largecollections-retired:使用堆外内存扩展到数百万个元素的 HashMap 实现

    Java Collections 实现,使用堆外内存扩展到数百万个元素,而不会遇到 GC 错误。 它利用 Google 的 LevelDB 项目来实现这一点。 目前这个 API 支持三种类型的 Collections java.util.Map java.util.List...

    Java堆外内存使用分析详细

    这个视频指导我们java开发遇到堆内存使用正常、gc正常,但实际使用的物理内存缺非常高,特别是容器化部署很容易因内存使用超limit限制导致pod重启,怀疑堆外内存泄露?如何排查堆外内存的使用情况,这个视频详细讲解...

    Java堆外内存泄露场景总结.pdf

    Java堆外内存泄露场景总结,包含几个常用的可能,如:JNI,NIO,AWT/Swing,Inflater&Deflater;

    分析堆外内存.pdf

    在Linux下,使用gperftools分析对外内存泄漏。介绍了基本的安装和使用。

    Java堆外内存的使用

     堆外内存其实并无特别之处。线程栈,应用程序代码,NIO缓存用的都是堆外内存。事实上在C或者C++中,你只能使用未托管内存,因为它们默认是没有托管堆(managed heap)的。在Java中使用托管内存或者“堆”内存是这...

    理论:第十三章:堆溢出,栈溢出的出现场景以及解决方案.docx

    * 使用堆外内存,例如使用 DirectByteBuffer 等。 栈溢出是指函数调用栈空间不足,导致函数调用无法继续执行的错误。常见的栈溢出情况有: 1. 局部数组过大:当函数内部的数组过大时,有可能导致堆栈溢出。 2. ...

    Java直接(堆外)内存使用详解

    本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明:  相关背景–>读写操作–>关键属性–>读写实践–>扩展–>参考说明  希望对想使用直接内存的朋友,提供点快捷的参考。  数据类型  ...

    slice:Java库,可有效处理堆内存和堆外内存

    以下是一个简单的使用`Slice`库创建和操作堆外内存的示例: ```java import org.jheaps.slice.Slice; import org.jheaps.slice.SliceAllocator; public class SliceExample { public static void main(String[] ...

    生产环境里的堆外内存HBase读路径——阿里巴巴的故事

    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 ...

    内存管理答案1

    Elasticsearch可能会使用堆内存来存储索引数据,使用堆外内存(Off-Heap)来减少GC压力,以及通过缓存策略来优化内存使用,如使用Lucene的内存缓冲区来提升写入性能。此外,Elasticsearch还会有内存分配策略,比如...

    浅谈Java堆外内存之突破JVM枷锁

    我们可以使用java.nio的ByteBuffer来创建堆外内存,并通过调用allocateDirect方法申请内存。同时,我们也可以通过设置-XX:MaxDirectMemorySize=10M控制堆外内存的大小。 结论 本文详细介绍了Java堆外内存的概念,...

    scruffy-marshall_2.10-1.4.0.zip

    通过深入研究Scruffy-Marshall库,开发者不仅可以学习到如何在Java中高效地使用堆外内存,还可以了解到内存管理的底层原理,这对于优化性能、解决内存泄漏问题以及设计大规模系统都大有裨益。此外,开源的特性使得...

    2018美团点评后台开发干货.zip

    然而,如果不正确地使用堆外内存,可能会导致内存泄露,影响系统稳定性和性能。 堆外内存泄露通常难以察觉,因为它不会被JVM的垃圾回收机制自动清理。排查此类问题需要对Netty的内存分配机制有深入理解。首先,...

    Spark面对OOM问题的解决方法及优化总结1

    堆外内存可以通过两种方式使用:一是通过`StorageLevel.OFF_HEAP`配合Tachyon;二是通过设置`spark.memory.offHeap.enabled`为true,但该功能在1.6.0版本尚不可用,未来版本会支持。 当面临OOM问题时,有以下几种...

Global site tag (gtag.js) - Google Analytics