`

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

阅读更多

使用了标志-XX:+HeapDumpOnOutOfMemoryErrorJVM会在遇到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[]size2097152,占用了16M内存,而代码1执行时最后一次正常创建的long[]size4194304,占用了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

分享到:
评论

相关推荐

    JVM内存分析工具.7z

    1. **捕获堆转储(Heap Dump)**:当JVM出现OOM时,或者需要定期检查时,可以通过JVM的命令行选项(如`-XX:+HeapDumpOnOutOfMemoryError`)或JMX接口来触发堆转储。 2. **导入堆转储**:使用ga456或ha456工具导入...

    Eclipse Memory Analyzer

    - **堆转储分析**:MAT可以从运行中的Java应用程序获取堆转储(Heap Dump),然后进行深入分析,找出内存占用量大的对象和可能的内存泄漏。 - **对象分配轨迹**:通过追踪对象的分配路径,帮助开发者了解对象是...

    MemoryAnalyzer-1.10.0.win32.x86-64-release.zip

    - 创建堆转储快照通常是分析的基础,可以通过JVM的`-XX:+HeapDumpOnOutOfMemoryError`参数配置自动创建,或者使用JVisualVM等工具手动触发。 - 分析结果的准确性依赖于堆转储的质量,因此在关键时刻捕获转储至关重要...

    Memory_Analyzer_Tool

    1. **快照分析**:MAT可以生成和加载Java堆转储快照,通过快照,我们可以看到程序运行时的内存状态,包括对象的数量、大小、类信息等。 2. **支配树分析**:MAT的支配树视图能够展示哪些对象是其他对象的GC根,帮助...

    Java性能分析20211022.rar

    诊断内存溢出通常需要分析堆转储(heap dump)文件,这可以通过Java虚拟机的JMX接口或命令行选项(-XX:+HeapDumpOnOutOfMemoryError)实现。使用分析工具,如Eclipse Memory Analyzer (MAT),可以帮助我们识别内存泄漏...

    JVM参数优化及JVM解析.docx

    当遇到OOM错误时,可以通过-XX:+HeapDumpOnOutOfMemoryError和-XX:HeapDumpPath来生成堆转储文件,以便后续分析问题原因。 总之,理解和掌握JVM参数优化、运行时数据区的结构以及垃圾回收机制,是每个Java开发者...

    jvm调试示例代码

    - 设置堆转储文件(`-XX:+HeapDumpOnOutOfMemoryError`),便于分析内存泄漏。 - 使用`-agentlib:jdwp`启动远程调试,例如`-agentlib:jdwp=transport=dt_socket,address=1234,server=y,suspend=n`。 3. **日志与...

    关于_JVM_命令行标志您不知道的_5_件事及命令行参数列表

    此时,使用**HeapDumpOnOutOfMemoryError** 参数能够在发生内存溢出时自动生成堆转储文件。 - **作用机制**:通过设置此参数,JVM会在检测到内存溢出时自动保存当前的堆状态到指定位置,便于后续分析。 - **应用...

    Weblogic内存溢出及常用参数配置

    - **-XX:+HeapDumpOnOutOfMemoryError**: 当发生内存溢出时,生成堆转储文件,便于分析内存状况。 除了调整内存参数,我们还需要注意以下几点: 1. 定期进行性能监控,以便尽早发现内存异常情况。 2. 使用内存分析...

    JVM参数调优及Java测试代码

    - `-XX:+HeapDumpOnOutOfMemoryError`:当出现内存溢出时,生成堆转储文件,便于后续分析。 5. **线程和栈内存**: - `-Xss`:设定每个线程的栈内存大小,影响线程创建和运行性能。 6. **方法区和运行时常量池**...

    jrockit 手册

    例如,`-XX:+HeapDumpOnOutOfMemoryError`可以在发生内存溢出时生成堆转储文件,这对于分析问题原因非常有帮助。 7. **应用设计优化**:良好的设计可以避免不必要的内存消耗。例如,使用缓存时要考虑缓存策略,避免...

    JVM和性能优化学习思维笔记.rar_java

    5. **内存溢出与性能监控**:内存溢出(OOM)是常见的性能问题,可以通过-XX:+HeapDumpOnOutOfMemoryError参数让JVM在发生OOM时生成堆转储文件进行分析。使用JVisualVM、VisualVM或JMX等工具可以实时监控JVM的运行...

    JVM 参数调优-optimization-jvm.zip

    7. **线程堆栈分析**:`-XX:+HeapDumpOnOutOfMemoryError`当出现OOM错误时生成堆转储文件,`-XX:HeapDumpPath`指定生成路径。使用`jstack`命令可以获取当前JVM的线程堆栈信息。 8. **编译优化**:JIT(Just-In-Time...

    JVM性能调优经典教程

    7. **JVM内存泄漏检测**:学习如何识别和处理内存泄漏问题,了解如何使用`-XX:+HeapDumpOnOutOfMemoryError`生成堆转储文件,然后通过分析工具查找泄漏根源。 8. **编译优化**:JIT(Just-In-Time)编译器能够将...

    monkey老师的jvm 调优

    例如,`-XX:+HeapDumpOnOutOfMemoryError`参数可在内存溢出时生成堆转储文件,便于后续分析。 5. **线程调度与并发**:理解JVM如何调度线程,以及如何利用`-XX:PreBlockSpin`、`-XX:ThreadPriorityPolicy`等参数...

    JVM相关的常见面试问题汇总.pdf

    - `-XX:+HeapDumpOnOutOfMemoryError`: 当内存溢出时生成堆转储文件。 - `-XX:HeapDumpPath`: 指定堆转储文件的路径。 #### 6.1 设置堆内存XMX应该考虑哪些因素? - **应用需求**: - 应用程序的实际内存消耗。 ...

    生产环境jvm调优的实例代码-jvm.zip

    6. **JVM性能日志与分析**:通过`-XX:+PrintFlagsFinal`查看默认JVM配置,`-XX:+PrintGC`和`-XX:+PrintGCDetails`记录GC日志,`-XX:+HeapDumpOnOutOfMemoryError`在内存溢出时生成堆转储文件,便于后期分析。...

    java 监视内存的使用情况

    使用`-XX:+HeapDumpOnOutOfMemoryError`参数可以设置在发生内存溢出时生成堆转储文件,便于后续分析。 总之,Java内存监控涉及多个层面,包括使用内置工具、编程接口、理解内存模型、掌握垃圾收集机制以及合理配置...

    java 虚拟机参数配置说明及Myeclipse内存不足

    - `-XX:+HeapDumpOnOutOfMemoryError`: 当出现内存溢出时,生成堆转储文件,便于分析问题原因。 在MyEclipse这样的集成开发环境中,由于同时运行多个项目和插件,内存需求会显著增加。如果遇到“内存不足”的错误...

    tomcat out of memory

    - **分析heap dump文件**:当内存溢出发生时,可以通过JVM的`-XX:+HeapDumpOnOutOfMemoryError`参数生成堆转储文件,然后用专门的工具分析。 4. **预防措施**: - **定期监控**:定期检查Tomcat的内存使用,及时...

Global site tag (gtag.js) - Google Analytics