Sun JDK OOM
bluedavyjvm jvm, oom, sun jdk oom No Comments
Java的自动内存管理机制给开发人员带来了很多的便利,在设计、开发时可以完全不用考虑要分配多少内存,要记得回收内存等,但同时也带来了各种各样的问题,其中最典型的问题就是OOM,大部分Java开发人员估计都看到过java.lang.OutOfMemoryError这样的错误信息,在这篇文章中,就来介绍下Sun JDK中有哪几种OOM、OOM示例、造成OOM的原因的查找、解决以及Sun JDK代码中处理OOM的方式。
PDF版本请从此下载:http://blog.bluedavy.com/open/Sun-JDK-OOM.pdf
1 OOM的种类
在Sun JDK中运行时,Java程序有可能出现如下几种OOM错误:
java.lang.OutOfMemoryError: unable to create new native thread
当调用new Thread时,如已创建不了线程了,则会抛出此错误,如果是JDK内部必须创建成功的线程,那么会造成Java进程退出,如果是用户线程,则仅抛出OOM,创建不了的原因通常是创建了太多线程,耗尽了内存,通常可通过减少创建的线程数,或通过-Xss调小线程所占用的栈大小来减少对Java 对外内存的消耗。
java.lang.OutOfMemoryError: request bytes for . Out of swap space?
当JNI模块或JVM内部进行malloc操作(例如GC时做mark)时,需要消耗堆外的内存,如此时Java进程所占用的地址空间超过限制(例如windows: 2G,linux: 3G),或物理内存、swap区均使用完毕,那么则会出现此错误,当出现此错误时,Java进程将会退出。
java.lang.OutOfMemoryError: Java heap space
这是最常见的OOM错误,当通过new创建对象或数组时,如Java Heap空间不足(新生代不足,触发minor GC,还是不够,触发Full GC,还是不够),则抛出此错误。
java.lang.OutOfMemoryError: GC overhead limit execeeded
当通过new创建对象或数组时,如Java Heap空间不足,且GC所使用的时间占了程序总时间的98%,且Heap剩余空间小于2%,则抛出此错误,以避免Full GC一直执行,可通过UseGCOverheadLimit来决定是否开启这种策略,可通过GCTimeLimit和GCHeapFreeLimit来控制百分比。
java.lang.OutOfMemoryError: PermGen space
当加载class时,在进行了Full GC后如PermGen空间仍然不足,则抛出此错误。
对于以上几种OOM错误,其中容易造成严重后果的是Out of swap space这种,因为这种会造成Java进程退出,而其他几种只要不是在main线程抛出的,就不会造成Java进程退出。
2 OOM示例、原因查找和解决
这些示例的class以及源码请从http://blog.bluedavy.com/jvm/cases/oom/OOMCases.zip下载,建议在运行前不要看源码,毕竟源码是简单的例子,如果直接看源码的话,可能会少了查找原因的乐趣。
当Java程序运行时,会有很多种造成OOM的现象,这里面有些会比较容易查找出原因,而有些会非常困难,下面是来看一些OOM的Example。
Example 1
以-Xms20m -Xmx20m -Xmn10m -XX:+UseParallelGC参数运行com.bluedavy.oom.JavaHeapSpaceCase1
运行后在输出的日志中可看到大量的Full GC信息,以及:
java.lang.OutOfMemoryError: GC overhead limit exceeded
和java.lang.OutOfMemoryError: Java heap space
根据上面所说的OOM种类,可以知道这是在new对象或数组时Java Heap Space不足造成的,对于这种OOM,需要知道的是程序中哪些部分占用了Java Heap。
要知道程序中哪些部分占用了Java Heap,首先必须拿到Java Heap中的信息,尤其是OOM时的内存信息,在Sun JDK中可通过在启动参数上加上-XX:+ HeapDumpOnOutOfMemoryError来获得OOM时的Java Heap中的信息,当出现OOM时,会自动生成一个java_pid[pid].hprof的文件。
于是在启动参数上加上上面的参数,再次运行JavaHeapSpaceCase1,可看到在当前运行的路径下生成了一个java_pid10852.hprof(由于pid不同,你看到的可能是不一样的文件名)的文件,在拿到这个文件后,就可通过mat(http://www.eclipse.org/mat/)来进行分析了。
用mat打开上面的文件(默认情况下mat认为heap dump文件应以.bin结尾,因此请改名或以open file方式打开),打开后点击dominator_tree,可看到sun.misc.Launcher$AppClassLoader占据了大部分的内存,继续点开看,则可看到是由于com.bluedavy.oom.Caches中有一个ArrayList,其中存放的对象占据了大部分的内存,因此解决这个OOM的办法是,让Caches类中放的对象总大小是有限制的,或者限制放入Caches的ArrayList中的对象个数。
这种情况造成的OOM,在实际的场景中当使用缓存时很容易产生,对于所有的缓存、集合大小都应给定限制的最大大小,以避免出现缓存或集合太大,导致消耗了过多的内存,从而导致OOM。
Example 2
以-Xms20m -Xmx20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError执行com.bluedavy.oom.JavaHeapSpaceCase2
运行后在输出的日志中可看到大量的Full GC和java.lang.OutOfMemoryError: Java heap space。
同样,首先用mat打开需要分析的hprof文件,很惊讶的发现什么都看不出来,Java Heap Space还很充足,这就奇怪了,还好除了能在OOM时自动dump出Heap的信息外,还可通过jmap命令手工dump,于是,在运行期出现频繁Full GC、OOM的时候,手工通过jmap –dump:file=heap.bin,format=b [pid]生成一个heap.bin文件,把这个heap.bin文件也拿到mat中分析,杯具,还是什么都看不出来,还好,还有一招,就是直接用jmap –histo看看到底什么对象占用了大多数的内存,执行一次,看到[I占用了最多的内存,没用,因为没法知道这个[I到底是代码中哪个部分创建的,不甘心,多执行几次,很幸运,突然看到com.bluedavy.oom.Case2Object占据了最大的内存,于是查找代码中哪些地方创建了这个对象,发现了代码中有一个线程创建了大量的Case2Object,修改即可。
从这个例子中,可以看到,在分析OOM问题时,一方面是依赖OOM时dump出来的文件,但这个文件其实只会在Java进程中第一次出现OOM时生成,之后再OOM就不会生成了,这有可能出现真实的OOM的原因被假的OOM原因给掩盖掉;另一方面是依赖在出现OOM时的人工操作,这种人肉方式其实比较杯具,因为只能先等到频繁Full GC、OOM,首先通过jmap –histo来看看到底什么对象占用了大部分的内存(需要多执行几次,以确保正确性),上面的例子比较幸运,因为刚好是自定义的对象,如果是原生的类型,那就只能借助dump文件来分析了,通过jmap –dump手工dump出Heap文件,然后用MAT分析,但有可能会出现上面例子中的状况,就是mat分析上也看不出什么,顶多只能看到unreachable objects里某些对象占用了大部分的内存,而通常情况看到的可能都是原生类型,一旦真的碰到jmap –histo看到的是原生类型占用较多,jmap dump看到的是Java Heap Space也不满的话,那只能说杯具了,在这种情况下,唯一能做的是捕捉所有的异常,然后打印,从而判断OOM是在哪行代码抛出的。
Example 3
以-Xms20m -Xmx20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError执行com.bluedavy.oom.JavaHeapSpaceCase3
在控制台中可看到大量的java.lang.OutOfMemoryError: Java heap space,把生成的hprof文件放入MAT中进行分析,还好看到确实是Java Heap Space满了,这就好办了,点开Dominator Tree视图,可看到有一堆的线程,每个都占用了一定的内存,从而导致了OOM,要解决这个例子中的OOM,有四种办法:一是减少处理的线程数;二是处理线程需要消耗的内存;三是提升线程的处理速度;四是增加机器,减少单台机器所需承担的请求量。
上面这种状况在系统处理慢的时候比较容易出现。
Example 4
以-Xms20m -Xmx20m -Xmn10m -XX:+HeapDumpOnOutOfMemoryError执行com.bluedavy.oom.JavaHeapSpaceCase4
在控制台可看到大量的java.lang.OutOfMemoryError: Java heap space,把生成的hprof文件放入MAT中进行分析,可看到TaskExecutor中的handlers占据了大量的内存,分析代码,发现是由于在task处理完后没有清除掉对应的handler造成的,修改即可解决此OOM问题。
这是个典型的内存泄露的例子,如果不小心持有了本应释放引用的对象,那么就会造成内存泄露,这是在编写Java程序时需要特别注意的地方。
Example 5
以-Xms1536m -Xmx1536m -Xss100m执行com.bluedavy.oom.CannotCreateThreadCase
在控制台可看到java.lang.OutOfMemoryError: unable to create new native thread。
对于这种情况,一需要查看目前-Xss的大小,例如在这个例子中-Xss太大,导致连20个线程都无法创建,因此可解决的方法是降低-Xss的大小;如果Xss使用的是默认值,那么可通过jstack来查看下目前Java进程中是不是创建了过多的线程,或者是java heap太大,导致os没剩多少内存,从而创建不出线程。
Example 6
Out of swap的例子实在太难举了,就没在此列出了,对于Out of swap的OOM,需要首先观察是否因为Java Heap设置太大,导致物理内存+swap区不够用;如果不是的话,则需关注到底是哪些地方占用了堆外的内存,可通过google-perftools来跟踪是哪些代码在调用malloc,以及所耗的内存比例,在跟踪到后可继续查找原因。
总体来说,Out of swap通常是最难查的OOM,由于其是直接退出java进程的,因此需要结合core dump文件和hs_err_pid[pid].log进行分析,最关键的还是像查java heap那样,要查出到底是什么代码造成了native heap的消耗。
Example 7
PermGen空间满造成OOM的情况通常采取的解决方法是简单的扩大PermSize。
总结上面的例子来看,对于OOM的情况,最重要的是根据OOM的种类查找到底是代码中的什么部分造成的消耗。
对于Java Heap Space OOM和GC overhead limit exceeded这两种类型,可通过heap dump文件以及jmap –histo来进行分析,多数情况下可通过heap dump分析出原因,但也有少数情况会出现heap dump分析不出原因,而jmap –histo看到某原生类型占据了大部分的内存,这种情况就非常复杂了,只能是仔细查看代码,并捕捉OutOfMemoryError,从而来逐渐定位到代码中哪部分抛出的。
对于Out of swap这种类型,其主要是地址空间超过了限制或者对外内存不够用了造成的,首先需要查看Java Heap设置是否过大,然后可结合google-perftools来查看到底是哪些代码调用了malloc,在堆外分配内存。
3 Sun JDK代码中处理OOM的方式
在Sun JDK代码中,在不同的OOM时,会调用不同的处理方式来进行处理,下面就来看看JDK中几个典型的处理OOM的代码。
创建线程失败
compiler_thread = new CompilerThread(queue, counters);
if (compiler_thread == NULL || compiler_thread->osthread() == NULL){
vm_exit_during_initialization(“java.lang.OutOfMemoryError”,
“unable to create new native thread”);
}
对于JDK中必须创建成功的线程,如失败会通过调用vm_exit_during_initialization打印出OOM错误,并退出Java进程。
对于非必须创建成功的线程,通常会调用
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
“unable to create new native thread”);
抛出OOM错误信息。
调用os:malloc失败
void *p = os::malloc(bytes);
if (p == NULL)
vm_exit_out_of_memory(bytes, “Chunk::new”);
return p;
当os:malloc或os:commit_memory失败时,会直接输出错误信息,并退出Java进程。
Java Heap上分配失败后
report_java_out_of_memory(“Java heap space”);
调用这个就表明了不会退出vm,而只是抛出OOM错误。
例如PermGen分配失败时的代码更为直观:
HeapWord* result = Universe::heap()->permanent_mem_allocate(size);
if (result != NULL) {
NOT_PRODUCT(Universe::heap()->
check_for_non_bad_heap_word_value(result, size));
assert(!HAS_PENDING_EXCEPTION,
“Unexpected exception, will result in uninitialized storage”);
return result;
}
// -XX:+HeapDumpOnOutOfMemoryError and -XX:OnOutOfMemoryError support
report_java_out_of_memory(“PermGen space”);
总体而言,对于一个大型系统而言,通常OOM是难以避免的现象,最重要的还是一旦出现OOM,要掌握排查的方法,另外就是,随着现在内存越来越便宜,CMS GC越来越成熟,采用64 bit操作系统,开启大内存也是一种可选方式,基本上可以避免内存成为大问题,毕竟在Java中完全可能随便写几行代码就不知不觉消耗了很多内存。
ps: 感兴趣的同学还可参考sun官方的这篇关于OOM的文章:
http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/memleaks.html
分享到:
相关推荐
JAVA内存溢出 JAVA中OutOfMemoryError(内存溢出)的三种情况及解决办法 Java中的OutOfMemoryError(内存溢出)是一种常见的错误,本文将详细介绍OutOfMemoryError的三种情况及其解决方法。 首先,我们需要了解...
Java内存溢出(Out Of Memory,OOM)是Java应用程序运行时常见的问题,它通常发生在程序对内存需求超过了Java虚拟机(JVM)所能提供的可用内存时。本文将深入探讨Java内存溢出的原因、表现以及如何解决。 1. **Java...
Java内存溢出(Out of Memory,OOM)是Java应用程序中常见的问题,会导致程序崩溃或性能急剧下降。HeapAnalyzer是一款强大的工具,专为分析Java应用程序的内存状况,特别是针对内存溢出问题进行诊断。本文将详细介绍...
JAVA内存溢出问题总结 JAVA 内存溢出问题是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用的内存大于虚拟机能提供的最大内存。内存溢出问题可以从容器和程序类两个方面进行排查,容器问题...
Java内存溢出问题,全称为Java OutOfMemoryError,是Java开发者经常遇到的运行时异常。内存溢出通常发生在程序运行过程中,系统无法为运行的应用程序分配足够的内存资源,导致程序无法正常执行。理解并解决Java内存...
【Java面试题】Java内存溢出
Java内存溢出问题通常指的是Java应用程序在运行过程中由于内存分配不当或使用过度导致JVM无法分配更多的内存,从而抛出`java.lang.OutOfMemoryError`异常。本文将深入探讨Java内存溢出的不同类型及其解决方案。 1. ...
Java内存溢出问题通常指的是程序在试图分配内存时,无法找到足够的连续内存空间而抛出的异常。在Java中,内存分为几个区域:堆(Heap)、栈(Stack)、方法区(Method Area,Java 8后被元空间取代)以及程序计数器和...
Java内存溢出的详细解决方案 Java内存溢出是指Java虚拟机(JVM)中的内存使用超过了最大限制,导致程序崩溃或无法正常运行的错误。Java内存溢出主要有两种类型:PermGen space和Java heap space。 PermGen space ...
HeapAnalyzer456.jar 是一个专门用于分析Java内存溢出的专业工具,它可以帮助开发者深入理解内存的分配、使用和泄漏状况。本篇文章将详细介绍如何使用HeapAnalyzer456.jar来分析Java内存溢出问题。 首先,我们需要...
Java内存溢出(Out Of Memory, OOM)是开发者在编程过程中经常遇到的问题,尤其是在处理大量数据或长时间运行的应用程序时。本篇文章将详细解析三种常见的Java内存溢出类型:JVM PermGen space溢出、JVM heap space...
Java内存溢出的知识点 Java内存溢出是指Java虚拟机(JVM)在执行过程中无法分配对象所需的内存,导致程序崩溃或无法继续运行的错误。Java内存溢出类型有多种,下面将详细介绍其中的四种类型。 1. java.lang....
在探讨如何解决JAVA内存溢出问题时,我们首先需要理解几个关键的概念,包括JVM(Java虚拟机)、Tomcat服务器、以及与之相关的内存参数设置。本文将详细解析标题及描述中提到的知识点,帮助读者深入理解JAVA内存管理...
解决Java内存溢出和内存泄露的方法主要包括以下几点: 1. 适当调整JVM参数:通过设置-Xms和-Xmx指定堆内存的初始大小和最大大小,避免因动态扩展导致的溢出。同时,可以通过-Xss设置线程栈的大小,防止栈溢出。 2....
### 关于static的小结(Java内存溢出) #### 标题和描述中的知识点 在《关于static的小结(Java内存溢出)》这篇文章中,作者主要探讨了与Java中的`static`关键字相关的知识点,以及如何可能导致内存溢出的情况。...
Java内存溢出问题,是Java开发中常见的性能问题,它发生在程序运行时,由于系统无法分配足够的内存资源来满足程序的运行需求,导致程序异常终止。深入理解Java内存溢出,有助于我们优化程序,提高系统稳定性。下面...
Java内存溢出(Out of Memory, OOM)是开发者在编程过程中经常遇到的问题,尤其是在处理大量数据或长时间运行的应用程序时。为了解决这一问题,Java提供了多种内存分析工具,其中CoreAnalyzer是一款专业的Java内存...
Java内存管理是开发Java应用程序时的关键环节,内存泄露和溢出问题可能导致系统性能下降,甚至导致服务崩溃。本文将深入探讨如何检测和分析Java内存泄露与溢出,并介绍一种常用的工具——Memory Analyzer(MAT)。 ...
**JRockit JAVA内存溢出检测的使用** Java内存溢出是开发和运行Java应用程序时常见的问题,可能导致程序崩溃或性能急剧下降。JRockit JVM(Java Virtual Machine)由Oracle公司开发,它提供了强大的内存管理和分析...