【转】HeapDumpOnOutOfMemoryError堆转储实践和一些分析

标签: heapdumponoutofmemoryerror 实践 分析 | 发表时间:2015-03-16 02:45 | 作者:RoomFourteen224
出处:http://www.iteye.com

使用了标志-XX:+HeapDumpOnOutOfMemoryError,JVM会在遇到OutOfMemoryError时拍摄一个“堆转储快照”,并将其保存在一个文件中。

对如下一段代码,【代码1】

Java代码   收藏代码
  1. public static void main(String[] args) {  
  2.     long arr[];      
  3.     for (int i=1; i<=10000000; i*=2) {  
  4.         arr = new long[i];  
  5.     }  
  6. }  

 

      设置虚拟机参数为:-Xmx40m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\Java\dump

执行程序,很快会抛出异常:

java.lang.OutOfMemoryError: Java heap space

Dumping heap to E:\Java\dump\java_pid10400.hprof ...

Heap dump file created [1192880 bytes in 0.024 secs]

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

 

    奇怪的是Heap dump file只有一兆多一点,用JProfiler打开这个文件,并没有看到导致内存溢出的long[];刚开始以为是堆转储时,JVM会忽略掉只被线程栈引用的数组,进一步测试,发现并不是这个原因;查看相应的class文件,反编译后得到:

【将代码1通过JDK1.6编译后的字节码反编译后得到的代码】

Java代码   收藏代码
  1. public static void main(String[] args)  
  2. {  
  3.     for (int i = 1; i <= 10000000; i *= 2) {  
  4.       long[] arr = new long[i];  
  5.     }  
  6. }  

 

其中long[] arr 的定义从循环外面变到了循环里面,应该是编译器进行了优化,这样修改后,功能并没有变化,但long[] arr的生存范围变小了,生存范围是从声明到本次循环结束;每次循环开始时,在线程栈中声明一个指向long[]的引用 arr,然后在堆中创建一个指定大小的long[],把它的引用赋给arr;在每次循环结束,进入下次循环前,线程栈中的引用arr就会被销毁,它所指向的long[]就变成了没有被引用的实例;进入了下次循环,又重新在线程栈中声明一个引用arr,在堆中创建一个指定大小的long[],把它的引用赋给arr。

分析:虚拟机参数配置了-Xmx40m,在堆内存的使用量超过40M时,虚拟机就会抛出OutOfMemoryError: Java heap space,同时将堆内存转储到文件中,这时候前面循环创建的long[]实例没有被引用,应该已经被垃圾回收,所以Heap dump file中没有程序创建的long[]实例。

 

在源代码arr = new long[i];的后面加上显示堆内存使用的语句:

Java代码   收藏代码
  1. System.out.println("size : " + i);  
  2. Runtime runtime = Runtime.getRuntime();  
  3. System.out.printf("maxMemory : %.2fM\n", runtime.maxMemory()*1.0/1024/1024);  
  4. System.out.printf("totalMemory : %.2fM\n", runtime.totalMemory()*1.0/1024/1024);  
  5. System.out.printf("freeMemory : %.2fM\n", runtime.freeMemory()*1.0/1024/1024);  

可以看出每次执行arr = new long[i];后堆内存的使用情况;配置-Xmx40m的情况下,抛出异常前最后一次正常执行的循环的输出信息为:

size : 4194304

maxMemory : 39.75M

totalMemory : 38.50M

freeMemory : 6.27M

 

将代码1改为:【代码2】

Java代码   收藏代码
  1. public static void main(String[] args) {  
  2.     long arr[] = {};   
  3.     for (int i=1; i<=10000000; i*=2) {  
  4.         arr = new long[i];  
  5.     }  
  6. }  

 

查看相应的class文件,反编译后得到:

【将代码2通过JDK1.6编译后的字节码反编译后得到的代码】

Java代码   收藏代码
  1. public static void main(String[] args)  
  2. {  
  3.     long[] arr = new long[0];  
  4.     for (int i = 1; i <= 10000000; i *= 2) {  
  5.       arr = new long[i];  
  6.     }  
  7. }  

 

    其中long[] arr 的定义跟源代码一致,是在循环外面,应该是因为有初始化的代码,没办法再把定义移到循环里面;这种情况下,arr的生存范围是从声明到程序结束;每次循环开始时,会在堆中创建一个指定大小的long[],把它的引用赋给arr,这样arr之前指向的long[]就变成没有被引用的实例;进入了下次循环,在堆中创建一个指定大小的long[],把它的引用赋给arr,在赋值完成前,arr指向上次循环创建的long[]实例,赋值完成后,arr就指向本次循环创建的long[]实例,这时候上次循环创建的long[]实例就变成没有被引用的实例。

    设置虚拟机参数为:-Xmx40m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\Java\dump

执行程序,很快会抛出异常:

  java.lang.OutOfMemoryError: Java heap space

Dumping heap to E:\Java\dump\java_pid11020.hprof ...

Heap dump file created [17970188 bytes in 0.172 secs]

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

Heap dump file有十多兆,用JProfiler打开这个文件,可以找到一个占用16M内存的long[]。



 

分析:在程序因为没有足够的堆内存创建实例而抛出OutOfMemoryError时,引用arr仍然指向上次循环创建的long[]实例,在JVM将堆内存转储到文件中时,会把这个long[]实例也考虑进去;这个long[]实例被arr引用,arr位于线程栈中,所以上图中显示long[]实例被java stack引用。

 

    在源代码arr = new long[i];的后面加上显示堆内存使用的语句,

可以看到抛出异常前最后一次正常执行的循环的输出信息为:

size : 2097152

maxMemory : 39.75M

totalMemory : 33.26M

freeMemory : 8.92M

    最后一次正常创建的long[]的size为2097152,占用了16M内存,而代码1执行时最后一次正常创建的long[]的size为4194304,占用了32M内存。

分析:代码1在循环中创建long[]实例时,上次循环创建的long[]实例没有被引用,可以被垃圾回收掉,所以在参数Xmx40m下,代码1创建占用32M内存的long[]还是可以正常执行的,试图创建占用64M内存的long[]才抛出异常;代码2在循环中创建long[]实例时,上次循环创建的long[]实例还在被arr引用,不能被垃圾回收掉,代码2在创建占用16M内存的long[]实例时,前一个循环创建的占8M内存的long[]实例还不能被回收,8+16=24 < 40,所以这次能够正常执行,下一个循环要尝试创建占32M内存的long[]实例,这时候占16M内存的long[]实例还不能被回收,16+32=48>40,堆内存不够用,只好抛出异常。

  

小结:通过分析OutOfMemoryError时生成的堆转储文件,有助于找到内存不够用的原因;如果生成的堆转储文件的大小跟最大堆内存的配置有很大差异,就要分析抛出异常的代码,查找原因,还可以将字节码反编译,看看编译器是不是对代码的结构进行了调整。

 

转自:http://epy.iteye.com/blog/1914455



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [heapdumponoutofmemoryerror 实践 分析] 推荐:

【转】HeapDumpOnOutOfMemoryError堆转储实践和一些分析

- - 编程语言 - ITeye博客
使用了标志-XX:+HeapDumpOnOutOfMemoryError,JVM会在遇到OutOfMemoryError时拍摄一个“堆转储快照”,并将其保存在一个文件中. 对如下一段代码,【代码1】.       设置虚拟机参数为:-Xmx40m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=E:\Java\dump.

容器下 -XX:+HeapDumpOnOutOfMemoryError 未生成 dump 文件的问题

- - coderbee笔记
JVM 的启动命令一般都会加上参数 -XX:+HeapDumpOnOutOfMemoryError=/path/to/save/dump.hprof,用于在 JVM 发生 OOM 时自动生成内存 dump 文件. 应用在生产环境是运行在 Docker 容器里、由 K8S 负责管理容器. 但是有的应用发生 OOM 时,在 /path/to/save/dump.hprof 路径下并没有生成对应的 dump 文件.

程序分析-原理和实践

- 三十不归 - 弯曲评论
今年秋天在UCSB旁听一门Program Analysis的课(课程主页:http://www.cs.ucsb.edu/~benh/cs290/),觉得Ben的课程风格很实在,从头到尾没有口水话,几乎是干货. 回想之前对Program Analysis感兴趣却常常找不到合适的资料,而这个技术其实在很多方面都比较有用,因此想把这门课程记录下来的笔记陆续发出来,如果有朋友感觉有用,就没有白费一番介绍的力气了.

大数据分析最佳实践

- - 互联网分析
   转自:TTNN   Q先生杰作. 大概是从今年开始,big data一词逐渐成为术语,这跟整个世界的数据爆发当然有关系. 以前,人们喜欢用海量数据这个词,large-scale. 这看上去还是显得有点学术气, 像是BI人自己关起门来说自己的宝贝. 而big data更显通俗,在各行各业都显现出的一种势头,于是产生这个更加简单的词汇,大数据.

可测性分析和实践

- - 博客园_知识库
  软件测试中可测性一般是指对系统的可控性、可观测性进行的评估,借以反映系统设计、实现对测试的友好程度和相应的测试成本. 可测性在测试阶段会对系统的测试成本及关联产品代码的Patch次数产生重大影响. 如何提高可测性成为软件生命周期特别是前期(设计阶段、coding阶段)重要的一环. 本文带领大家探索在实际项目中可测性相关的实战经验和对应的改进措施.

技术团队看板方法实践的难点分析

- - csdnNews
CTO俱乐部看板研修班开课. 北京、上海、深圳三站火热报名中. 感兴趣的朋友可扫描左侧二维码加入看板公开课与路宁、何勉两位讲师直接沟通. 成功加入 CTO俱乐部会员并. 获赠6个月《程序员》iPad/Android版电子刊. 会员权益:个人主页、定期餐叙、最新周刊、折扣优惠、《程序员》杂志、大会门票、人才招聘、每月赠书等,.

MySQL与OLAP:分析型SQL查询最佳实践探索

- - Web前端 - ITeye博客
搞点多维分析,糙快猛的解决方案就是使用ROLAP(关系型OLAP)了. 数据经维度建模后存储在MySQL,ROLAP引擎(比如开源的Mondrian)负责将OLAP请求转化为SQL语句提交给数据库. OLAP计算分析功能导致MySQL需要进行较多复杂SQL查询,性能调优必不可少,本文总结了一些实用原则.

智能投放系统之场景分析最佳实践

- - 美团点评技术团队
新美大平台作为业内最大的O2O的平台,以短信/push作为运营手段触达用户的量级巨大,每日数以千万计. 美团点评线上存在超过千万的POI,覆盖超过2000城市、2.5万个后台商圈. 在海量数据存在的前提下,实时投放的用户在场景的选择上存在一些困难,所以我们提供对场景的颗粒化查询和智能建议,为用户解决三大难题:.

数据导入HBase最常用的三种方式及实践分析(2)

- - 开源软件 - ITeye博客
HBase提供importtsv工具支持从TSV文件中将数据导入HBase. 使用该工具将文本数据加载至HBase十分高效,因为它是通过MapReduce Job来实施导入的. 哪怕是要从现有的关系型数据库中加载数据,也可以先将数据导入文本文件中,然后使用importtsv 工具导入HBase. 在导入海量数据时,这个方式运行的很好,因为导出数据比在关系型数据库中执行SQL快很多.