`
ahuaxuan
  • 浏览: 640594 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

内存充裕下的OOM (一) 令人惊叹的rootcause

阅读更多


/*

  • @author: ahuaxuan
  • @date: 2010-4-30
  • /
在内存充裕的情况下的OOM

 


理解本文的前提是理解JVM的内存模型:包括

perm, old, young(eden, from(s0), to(s1)), 然后理解young中的垃圾搜集算法(拷贝算法,尤其是eden, from(s0), to(s1)它们分别扮演什么样的角色,为什么任意时间中from和to中必须要有一个是空的), old里的垃圾搜索算法(常见的有:标记压缩算法).


正文:

 

最近一台测试服务器抛出了OOM的错误,本着不放过一切问题的原则,ahuaxuan把这个OOM研究了一番,最后得出了令人意想不到的结论.


charpter 1, 现象


       看到这个OOM的信息之后,ahuaxuan的第一反应是,内存是否真的不够用了,于是使用jmap查看了一下内存的情况,如下:


我们的测试服务器给JVM进程分配的空间是4G,但是从上图中,我们可以看出内存并没有完全用完,尤其是From和TO这两个suvivor空间居然还有900M的空间剩余. Eden 也有112M的空间剩余,Old有288M的空间剩余.


这个不像是空间不足的情况.除非我们的对象真的有这么大,大到超过288M,不管eden还是old都放不下.一个对象要超过300M,那这是一个什么样的对象呀,初步分析,这个对象应该是一个byte数组或者char数组.


charpter2 大对象来源

         为了追到这个问题,接下来就是把heap的dump信息拿出来,通过jmap -dump:format=b,file=heap.bin <pid> 把当时的这个进程的dump拿了下来(2.7G).

然后用MAT打开一看.


内存基本被byte和char霸占了.寻找这些byte和char的root.得到


 

           嘿,好家伙,360M的对象,还不在少数(还有其他的图,包含了其他的大对象,就不列出了,主角就是上面的char[]), 而且这些对象是由Lucene创建的,经查,lucene会将索引中的field装载入内存,看似也是合理的,但是不合理的地方是它出现在了RAMInputStream这个类中,因为按照jackrabbit的逻辑(ahuaxuan曾经写过17篇文章来阐述jackrabbit中的搜索模块,详情查看:http://ahuaxuan.iteye.com/category/65829).这里ahuaxuan稍微阐述一下jackrabbit的搜索模块中是怎么使用RAMDirectory的:

 

新的索引请求到达索引模块,建索引完成之后,jackrabbit不会立即将索引数据刷入磁盘, 而是放在内存中,然后当内存中的数据满足一定量之后(可以在配置文件中设置),这批数据会被一起刷到磁盘中,也就是从RAMDirectory中刷到FSDirectory中,而刚才讲到满足一定的量,其实默认值是100, 也就是说RAMDirectory中最多保持了100个Document.


问题就在这里,100个Document能有360M的field?

这个可能性微乎其微. 如果不是这360M的量有问题,那么就是这100个Document的数量有问题,也许根本就超过了100个.


回过头来我们再来看看上面的逻辑:

1.为每个建立索引数据,存放在内存中

2.如果内存中document满100,刷入磁盘

3.删除内存中的document.


试想,在这个流程中,如果第二步出错.会导致什么情况? 内存中的索引数据不能输入到磁盘上,然后内存中的数据也不能删除(因为代码执行不到下面),所以内存中的数据越来越多,也就是RAMDirectory的数据越来越多.在索引数据不停的增加的过程中,char[]也在越来越大,由于char[]是不能扩容的,所以每次扩容必须新建更大的char[],然后把老的char[]中的数据copy过去,那么问题就是,要建一个比360M的char[]还大的char[]谈何容易? 因为我们的eden只有100M, old只有288M了, 于是OOM应运而生.


charpter 3 验证

 

查看服务器索引目录,发现确实索引目录的空间的使用率已经到达100%, 于是接着想查看日志文件,想找出IOException之类的异常,可惜的是QA已经将前一天的日志删除,现在的日志所有都是OOM的错误.真是死无对证,这个是比较遗憾的地方.

 

于是重启服务器,但是并不清空索引目录,不一会,程序又开始出错,打开dump,发现又是类似的问题,RAMDirectory的量还在增加,但是还没有到OOM的阶段(查看RAMDirectory中document的量,已经达到8000多个).


再次验证大对象的问题,写代码创建30m的对象,得到的内存图如下:

当eden不够的时候(共80M,但是已经使用了57M,而我们的新对象是30M),对象没有经过so(from)和s1(to)而直接进入了Old.如果old已经是286M,那么Old也放不下这个对象了,JVM直接抛出了OOM(该例子的系统平台是Mac os 10.5)


charpter 4 处理

 

将索引目录清空,重启服务器,重新查看dump文件,一切正常,于是跟QA约定保留5天之内的日志数据.从这个一点可以初步确定,OOM产生的原因就是因为磁盘空间不足,导致:

 

ahuaxuan 写道
1.为每个建立索引数据,存放在内存中
2.如果内存中document满100,刷入磁盘
3.删除内存中的document.
 

 

这个流程不能完成,结果就是内存中的数据悦来越多. 最终在为char[n]申请char[m](m >> n)的时候的产生了OOM


charpter 5 延伸

 

1.为何from space 和to space都达到了450M, 因为它的理论值应该是8:1:1,也就是如果young区有1G, 那么eden应该是800M, from和to都是100M. 也就是说jvm会动态的调整from 和to的值,这个应该也和大对象相关,比如说我们有300m大对象在young区,这个时候要执行copy算法,那么对象需要进入from或者to区,但是from 或者to的容量并不足以容纳这个对象,那么jvm可能会就会调整from和to的大小.以执行拷贝算法.

 

2.我们可以让生命周期较长的大对象直接进入old而无须进入young么.因为这些对象在young区会经过多次拷贝,然后才能进入Old,有时候并无必要.我们可以通过以下参数来设定:

XX:PretenureSizeThreshold=<byte size>:

 

 写道
• There is a flag (available in 1.4.2 and later) l-XX:PretenureSizeThreshold=<byte size> that can
be set to limit the size of allocations in the young generation. Any allocation larger than this will
not be attempted in the young generation and so will be allocated out of the old generation.
• Objects are now directly created in the older generation if the size of the young generation is
small, and the size of objects are big. The option -XX:PretenureSizeThreshold=<byte size> in the
concurrent collector that can be enabled to indicate the threshold for direct creation in the
older generation. This could effectively be used for creation of caches, lookup tables, etc. which
are long lived and do not have to go through a promotion cycle of being created in the younger
generation and then being copied to the older generation. Use this option with care, since it can
degrade performance instead of improving it. The default value is 0 i.e., no objects are created
directly in the older generation.
• If an allocation fails in the young generation and the object is a large array that does not
contain any references to objects, it can be allocated directly into the old generation. In some
select instances, this strategy was intended to avoid a collection of the young generation by
allocating from the old generation.
 

 

如果不设置这个参数,那么不管多大的对象都会在在young 区申请空间,如果设置了这个参数,那么超过规定的大小,这个对象就会直接在old区生成. 但是需要注意的是,如果你的大对象生命周期很短,那么让他们进入old区的必要性也不是很大.


        3.关于大的char数组扩容,ahuaxuan这里还有一个例子,是关于char数组的,详情请见

http://ahuaxuan.iteye.com/blog/669440.


to be continue

 

 

  • 大小: 141.8 KB
  • 大小: 259.2 KB
  • 大小: 65.7 KB
  • 大小: 181.7 KB
  • 大小: 242.8 KB
28
13
分享到:
评论
12 楼 ahuaxuan 2010-05-25  
magnesium 写道
请教楼主:

eden已占57M,新对象为30M,为什么JVM不会在Young GC的时候把部分eden中的对象移动到survivor中(7M即可),而是直接把新对象放到old中呢?old中的对象都是生命周期长的对象,新对象并不一定生命周期长啊。
那7m是不能分开的, 57m的数据其实就是2个对象, 每个对象,每个对象接近30m,所以不能把7m的数据单独拿出来作拷贝算法, gc的基本单元是对象.不能把某个对象的部分数据拿出来做gc
11 楼 magnesium 2010-05-24  
请教楼主:

eden已占57M,新对象为30M,为什么JVM不会在Young GC的时候把部分eden中的对象移动到survivor中(7M即可),而是直接把新对象放到old中呢?old中的对象都是生命周期长的对象,新对象并不一定生命周期长啊。
10 楼 RednaxelaFX 2010-05-24  
beneo 写道
此外你的内存图是什么软件?我想问问。

Visual GC
9 楼 beneo 2010-05-23  
ahuaxuan 写道
beneo 写道
很奇怪,如果Eden内存不足,不会经过S(2),即使到了old带也应该在OOM之前来一次Full GC呀?这样的话只要你的doc写入磁盘了,也应该OK了?而不是抛出OOM
你的

问题在于fullgc未必能回收多少空间是吧, 所以old里其实还是有288兆的空间(这288M是回收之后的), 但是我文章中描述的对象是300M的char[], 那就是说要申请的空间肯定是大于这300M的, 所以还是要OOM.

而且问题产生的原因是磁盘空间满了, 所以doc写不进磁盘, 但是写进了内存, 这部分我估计你还没有看吧


这两篇文字非常符合我的菜,我自己非常仔细的看了。不过jackrabbit我不了解,所以那段我忽略了。我准备把你之前的文字都看看。

此外你的内存图是什么软件?我想问问。
8 楼 ahuaxuan 2010-05-23  
beneo 写道
很奇怪,如果Eden内存不足,不会经过S(2),即使到了old带也应该在OOM之前来一次Full GC呀?这样的话只要你的doc写入磁盘了,也应该OK了?而不是抛出OOM
你的

问题在于fullgc未必能回收多少空间是吧, 所以old里其实还是有288兆的空间(这288M是回收之后的), 但是我文章中描述的对象是300M的char[], 那就是说要申请的空间肯定是大于这300M的, 所以还是要OOM.

而且问题产生的原因是磁盘空间满了, 所以doc写不进磁盘, 但是写进了内存, 这部分我估计你还没有看吧
7 楼 beneo 2010-05-22  
很奇怪,如果Eden内存不足,不会经过S(2),即使到了old带也应该在OOM之前来一次Full GC呀?这样的话只要你的doc写入磁盘了,也应该OK了?而不是抛出OOM
你的
6 楼 RednaxelaFX 2010-05-11  
ahuaxuan 写道
RednaxelaFX 写道
PretenureSizeThreshold参数在UseParallelGC或者UseG1GC的时候都是不起作用的。-server模式启动默认是用UseParallelGC,相信楼主在服务器上跑的JVM是以-server模式启动的。

hi 撒加, 你是在jvm源代码里看到的PretenureSizeThreshold和UseParallelGC的关系的还是在文档里看到的,如果是文档里看到的,方便给个链接么

是在源码里看到的。不过其实这个参数本来就是只影响“DefNew”区域,这个是Serial GC或者ParNew里起作用的。
5 楼 ahuaxuan 2010-05-11  
RednaxelaFX 写道
PretenureSizeThreshold参数在UseParallelGC或者UseG1GC的时候都是不起作用的。-server模式启动默认是用UseParallelGC,相信楼主在服务器上跑的JVM是以-server模式启动的。

hi 撒加, 你是在jvm源代码里看到的PretenureSizeThreshold和UseParallelGC的关系的还是在文档里看到的,如果是文档里看到的,方便给个链接么
4 楼 shengren0 2010-05-10  
ahuaxuan 写道
RednaxelaFX 写道
PretenureSizeThreshold参数在UseParallelGC或者UseG1GC的时候都是不起作用的。-server模式启动默认是用UseParallelGC,相信楼主在服务器上跑的JVM是以-server模式启动的。

我没有用PretenureSizeThreshold这个参数(所以把它放在:[charpter 5 延伸]这一章里了),这个OOM问题是测试环境上跑出来的.
pretenureSizeThreshold在parallelgc下的问题,它的文档上貌似没有写出来.

没研究过这么深入的
3 楼 ahuaxuan 2010-05-10  
RednaxelaFX 写道
PretenureSizeThreshold参数在UseParallelGC或者UseG1GC的时候都是不起作用的。-server模式启动默认是用UseParallelGC,相信楼主在服务器上跑的JVM是以-server模式启动的。

我没有用PretenureSizeThreshold这个参数(所以把它放在:[charpter 5 延伸]这一章里了),这个OOM问题是测试环境上跑出来的.
pretenureSizeThreshold在parallelgc下的问题,它的文档上貌似没有写出来.
2 楼 RednaxelaFX 2010-05-10  
PretenureSizeThreshold参数在UseParallelGC或者UseG1GC的时候都是不起作用的。-server模式启动默认是用UseParallelGC,相信楼主在服务器上跑的JVM是以-server模式启动的。
1 楼 ahuaxuan 2010-05-10  
是文章写的不够清楚么,还是文章本身有错误?
很多人在踩,踩的同时麻烦同学们顺便评论一下,谢谢

相关推荐

    MySQL OOM(内存溢出)的解决思路

    OOM全称”Out Of Memory”,即内存溢出。 内存溢出已经是软件开发历史上存在了近40年的“老大难”问题。在操作系统上运行各种软件时,软件所需申请的内存远远超出了物理内存所承受的大小,就叫内存溢出。 内存溢出...

    android 图片内存溢出(OOM)解决

    基本上解决了OOM问题 如果 方便可以直接引用BitmapManager类到 项目中使用 解决blog 地址http://www.cnblogs.com/liongname/articles/2345087.html

    Android 图片下载以及内存处理防止OOM内存溢出 源码

    在Android开发中,图片的加载和内存管理是一个关键问题,特别是考虑到防止因内存溢出(Out Of Memory,简称OOM)而导致应用崩溃。本教程将详细探讨如何在Android中有效地进行图片下载和内存处理,以避免OOM的发生。 ...

    实现图片下载以及内存处理防OOM功能_文件下载功能.zip

    在Android开发中,图片下载和内存管理是两个关键的环节,尤其在处理大量图片时,如何防止因内存溢出(Out Of Memory,简称OOM)而崩溃是开发者必须面对的问题。本教程将详细介绍如何实现图片的下载功能,并结合内存...

    安卓内存OOM分析

    如果一个应用持续占用大量内存,系统为了保护整体稳定性和用户体验,可能会触发OOM Killer,选择性地杀死一些内存占用高的进程。 **内存泄露分析:** 内存泄露是导致OOM的常见原因之一。当一个对象不再使用但仍然被...

    内存处理防OOM

    内存处理防OOM的源代码

    图片下载以及内存处理防OOM.zip

    "图片下载以及内存处理防OOM.zip"这个压缩包文件很可能包含了关于如何在Android应用中有效地处理图片下载和防止内存溢出(Out Of Memory, OOM)问题的源码示例和教程。 首先,我们来探讨一下图片下载。在Android中...

    JVM的基础和调优【JMM 内存结构 GC OOM 性能调优 ThreadLocal】

    JVM的基础和调优【JMM 内存结构 GC OOM 性能调优 ThreadLocal】 内存泄露:是指程序在申请内存后,无法释放已申请的内存空间就造成了内存泄露, 一次的内存泄露似乎不会有大的影响,但是内存泄露堆积的后果就是内存...

    安卓图片压缩类,避免内存溢出OOM

    在安卓开发中,图片处理是一项常见且重要的任务,然而,如果不妥善处理,它可能会导致一个严重的问题——内存溢出(Out Of Memory,简称OOM)。内存溢出是由于程序请求的内存超过了系统分配的最大内存,从而导致程序...

    使用 strace 命令来监控内存分配,找出OOM的原因

    使用 strace 命令来监控内存分配,找出OOM的原因 由于使用 Netty 导致的,那错误日志里可能会出现 OutOfDirectMemoryError 错误 如果直接是 DirectByteBuffer,那会报 OutOfMemoryError Direct buffer memory

    安卓Android源码——图片下载以及内存处理防OOM.rar

    本资料包“安卓Android源码——图片下载以及内存处理防OOM.rar”提供了相关的源码示例,帮助开发者理解并掌握这一领域的技巧。 首先,我们来看图片下载。在Android应用中,通常会从网络或者本地文件系统加载图片。...

    Android高级应用源码-图片下载以及内存处理防OOM.zip

    在Android开发中,图片的处理是一项非常重要的任务,因为不当的图片加载和内存管理往往会导致应用程序性能下降,甚至出现“Out of Memory”(OOM)错误。这个“Android高级应用源码-图片下载以及内存处理防OOM.zip”...

    Keras 快速解决OOM超内存的问题

    如果在Keras内部多次使用同一个Model,例如在不同的数据集上训练同一个模型进而得到结果,会存在内存泄露的问题。在运行几次循环之后,就会报错OOM。 解决方法是在每个代码后面接clear_session()函数,显示的关闭TF...

    安卓Android源码——图片下载以及内存处理防OOM.zip

    本资料包“安卓Android源码——图片下载以及内存处理防OOM.zip”聚焦于如何在Android应用中有效地下载图片并防止因内存溢出(Out Of Memory,简称OOM)而引发的应用崩溃。 一、图片下载 1. **异步下载**:为了提升...

    Android应用源码之图片下载以及内存处理防OOM.zip

    在Android应用开发中,图片加载和内存管理是关键部分,因为不当的操作可能导致应用程序出现内存溢出(Out Of Memory,简称OOM)错误,严重影响用户体验。本资料包“Android应用源码之图片下载以及内存处理防OOM.zip...

    ANDROIDBITMAP内存限制OOM,OUTOFMEMORY.pdf

    文档标题和描述中提到的“ANDROIDBITMAP内存限制OOM,OUTOFMEMORY”指的就是在处理位图(BITMAP)时超出了虚拟机(VM)的内存预算,导致系统抛出OutOfMemoryError异常。 根据给出的内容部分,我们可以推断出以下知识...

    图片下载以及内存处理防OOM.zip项目安卓应用源码下载

    在Android应用开发中,图片处理是一项关键任务,但不当的操作可能导致内存溢出(Out Of Memory,简称OOM),严重影响应用性能和用户体验。这个“图片下载以及内存处理防OOM”项目源码提供了一种解决方案来避免此类...

Global site tag (gtag.js) - Google Analytics