原文出自【听云技术博客】:http://blog.tingyun.com/web/a...java
概述程序员
OutOfMemoryError,说的是java.lang.OutOfMemoryError,是JDK里自带的异常,顾名思义,说的就是内存溢出,当咱们的系统内存严重不足的时候就会抛出这个异常(PS:注意这是一个Error,不是一个Exception,因此当咱们要catch异常的时候要注意哦),这个异常说常见也常见,说不常见其实也见得很少,不过做为Java程序员至少应该都听过吧,若是你对jvm不是很熟,或者对OutOfMemoryError这个异常了解不是很深的话,这篇文章确定仍是能够给你带来一些惊喜的,经过这篇文章你至少能够了解到以下几点:web
OutOfMemoryError必定会被加载吗缓存
何时抛出OutOfMemoryError性能优化
会建立无数OutOfMemoryError实例吗框架
为何大部分OutOfMemoryError异常是无堆栈的jvm
咱们如何去分析这样的异常工具
OutOfMemoryError类加载oop
既然要说OutOfMemoryError,那就得从这个类的加载提及来,那这个类何时被加载呢?你或许会不假思索地说,根据java类的延迟加载机制,这个类通常状况下不会被加载,除非当咱们抛出OutOfMemoryError这个异常的时候才会第一次被加载,若是咱们的系统一直不抛出这个异常,那这个类将一直不会被加载。提及来好像挺对,不过我这里首先要纠正这个说法,要明确的告诉你这个类在jvm启动的时候就已经被加载了,不信你就执行java -verbose:class -version
打印JDK版本看看,看是否有OutOfMemoryError这个类被加载,再输出里你将能找到下面的内容:post
[Loaded java.lang.OutOfMemoryError from /Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/rt.jar]
这意味着这个类其实在vm启动的时候就已经被加载了,那JVM里到底在哪里进行加载的呢,且看下面的方法:
bool universe_post_init() { ... // Setup preallocated OutOfMemoryError errors k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_OutOfMemoryError(), true, CHECK_false); k_h = instanceKlassHandle(THREAD, k); Universe::_out_of_memory_error_java_heap = k_h->allocate_instance(CHECK_false); Universe::_out_of_memory_error_metaspace = k_h->allocate_instance(CHECK_false); Universe::_out_of_memory_error_class_metaspace = k_h->allocate_instance(CHECK_false); Universe::_out_of_memory_error_array_size = k_h->allocate_instance(CHECK_false); Universe::_out_of_memory_error_gc_overhead_limit = k_h->allocate_instance(CHECK_false); Universe::_out_of_memory_error_realloc_objects = k_h->allocate_instance(CHECK_false); ... if (!DumpSharedSpaces) { // These are the only Java fields that are currently set during shared space dumping. // We prefer to not handle this generally, so we always reinitialize these detail messages. Handle msg = java_lang_String::create_from_str("Java heap space", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_java_heap, msg()); msg = java_lang_String::create_from_str("Metaspace", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_metaspace, msg()); msg = java_lang_String::create_from_str("Compressed class space", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_class_metaspace, msg()); msg = java_lang_String::create_from_str("Requested array size exceeds VM limit", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_array_size, msg()); msg = java_lang_String::create_from_str("GC overhead limit exceeded", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_gc_overhead_limit, msg()); msg = java_lang_String::create_from_str("Java heap space: failed reallocation of scalar replaced objects", CHECK_false); java_lang_Throwable::set_message(Universe::_out_of_memory_error_realloc_objects, msg()); msg = java_lang_String::create_from_str("/ by zero", CHECK_false); java_lang_Throwable::set_message(Universe::_arithmetic_exception_instance, msg()); // Setup the array of errors that have preallocated backtrace k = Universe::_out_of_memory_error_java_heap->klass(); assert(k->name() == vmSymbols::java_lang_OutOfMemoryError(), "should be out of memory error"); k_h = instanceKlassHandle(THREAD, k); int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0; Universe::_preallocated_out_of_memory_error_array = oopFactory::new_objArray(k_h(), len, CHECK_false); for (int i=0; i<len; i++) { oop err = k_h->allocate_instance(CHECK_false); Handle err_h = Handle(THREAD, err); java_lang_Throwable::allocate_backtrace(err_h, CHECK_false); Universe::preallocated_out_of_memory_errors()->obj_at_put(i, err_h()); } Universe::_preallocated_out_of_memory_error_avail_count = (jint)len; } }
上面的代码其实就是在vm启动过程当中加载了OutOfMemoryError这个类,而且建立了好几个OutOfMemoryError对象,每一个OutOfMemoryError对象表明了一种内存溢出的场景,好比说Java heap space
不足致使的OutOfMemoryError,抑或Metaspace
不足致使的OutOfMemoryError,上面的代码来源于JDK8,因此能看到metaspace的内容,若是是JDK8以前,你将看到Perm的OutOfMemoryError,不过本文metaspace不是重点,因此不展开讨论,若是你们有兴趣,能够专门写一篇文章来介绍metsapce前因后果,说来这个坑填起来还挺大的。
能经过agent拦截到这个类加载吗
熟悉字节码加强的人,可能会条件反射地想到是否能够拦截到这个类的加载呢,这样咱们就能够作一些譬如内存溢出的监控啥的,哈哈,我要告诉你的是NO WAY
,由于经过agent的方式来监听类加载过程是在vm初始化完成以后才开始的,而这个类的加载是在vm初始化过程当中,所以不可能拦截到这个类的加载,于此相似的还有java.lang.Object
,java.lang.Class
等。
为何要在vm启动过程当中加载这个类
这个问题或许看了后面的内容你会有所体会,先卖个关子。包括为何要预先建立这几个实例对象后面也会解释。
什么时候抛出OutOfMemoryError
要抛出OutOfMemoryError,那确定是有地方须要进行内存分配,多是heap里,也多是metsapce里(若是是在JDK8以前的会是Perm里),不一样地方的分配,其策略也不同,简单来讲就是尝试分配,实在没办法就gc,gc仍是不能分配就抛出异常。
不过仍是以Heap里的分配为例说一下具体的过程:
正确状况下对象建立须要分配的内存是来自于Heap的Eden区域里,当Eden内存不够用的时候,某些状况下会尝试到Old里进行分配(好比说要分配的内存很大),若是仍是没有分配成功,因而会触发一次ygc的动做,而ygc完成以后咱们会再次尝试分配,若是仍不足以分配此时的内存,那会接着作一次full gc(不过此时的soft reference不会被强制回收),将老生代也回收一下,接着再作一次分配,仍然不够分配那会作一次强制将soft reference也回收的full gc,若是仍是不能分配,那这个时候就不得不抛出OutOfMemoryError了。这就是Heap里分配内存抛出OutOfMemoryError的具体过程了。
OutOfMemoryError对象可能会不少吗
想象有这么一种场景,咱们的代码写得足够烂,而且存在内存泄漏,这意味着系统跑到必定程度以后,只要咱们建立对象要分配内存的时候就会进行gc,可是gc没啥效果,进而抛出OutOfMemoryError的异常,那意味着每发生此类状况就应该建立一个OutOfMemoryError对象,而且抛出来,也就是说咱们会看到一个带有堆栈的OutOfMemoryError异常被抛出,那事实是如此吗?若是真是如此,那为何在VM启动的时候会建立那几个OutOfMemoryError对象呢?
抛出异常的java代码位置须要咱们关心吗
这个问题或许你仔细想一想就清楚了,若是没想清楚,请在这里停留一分钟仔细想一想再日后面看。
抛出OutOfMemoryError异常的java方法其实只是临门一脚而已,致使内存泄漏的不必定就是这个方法,固然也不排除多是这个方法,不过这种状况的可能性真的很是小。因此你大可没必要去关心抛出这个异常的堆栈。
既然能够不关心其异常堆栈,那意味着这个异常其实不必每次都建立一个不同的了,由于不须要堆栈的话,其余的东西均可以彻底相同,这样一来回到咱们前面提到的那个问题,为何要在vm启动过程当中加载这个类
,或许你已经有答案了,在vm启动过程当中咱们把类加载起来,并建立几个没有堆栈的对象缓存起来,只须要设置下不一样的提示信息便可,当须要抛出特定类型的OutOfMemoryError异常的时候,就直接拿出缓存里的这几个对象就能够了。
因此OutOfMemoryError的对象其实并不会太多,哪怕你代码写得再烂,固然,若是你代码里要不断new OutOfMemoryError()
,那我就无话可说啦。
为何咱们有时候仍是能够看到有堆栈的OutOfMemoryError
若是都是用jvm启动的时候建立的那几个OutOfMemoryError对象,那不该该再出现有堆栈的OutOfMemoryError异常,可是实际上咱们偶尔仍是能看到有堆栈的异常,若是你细心点的话,可能会总结出一个规律,发现最多出现4次有堆栈的OutOfMemoryError异常,当4次事后,你都将看到无堆栈的OutOfMemoryError异常。
这个其实在咱们上面贴的代码里也有体现,最后有一个for循环,这个循环里会建立几个OutOfMemoryError对象,若是咱们将StackTraceInThrowable
设置为true的话(默认就是true的),意味着咱们抛出来的异常正确状况下都将是有堆栈的,那根据PreallocatedOutOfMemoryErrorCount
这个参数来决定预先建立几个OutOfMemoryError异常对象,可是这个参数除非在debug版本下能够被设置以外,正常release出来的版本实际上是没法设置这个参数的,它会是一个常量,值为4,所以在jvm启动的时候会预先建立4个OutOfMemoryError异常对象,可是这几个异常对象的堆栈,是能够动态设置的,好比说某个地方要抛出OutOfMemoryError异常了,因而先从预存的OutOfMemoryError里取出一个(其余是预存的对象还有),将此时的堆栈填上,而后抛出来,而且这个对象的使用是一次性的,也就是这个对象被抛出以后将不会再次被利用,直到预设的这几个OutOfMemoryError对象被用完了,那接下来抛出的异常都将是一开始缓存的那几个无栈的OutOfMemoryError对象。
这就是咱们看到的最多出现4次有堆栈的OutOfMemoryError异常及大部分状况下都将看到没有堆栈的OutOfMemoryError对象的缘由。
如何分析OutOfMemoryError异常
既然看堆栈也没什么意义,那只能从提示上入手了,咱们看到这类异常,首先要肯定的究竟是哪块内存何种状况致使的内存溢出,好比说是Perm致使的,那抛出来的异常信息里会带有Perm
的关键信息,那咱们应该重点看Perm的大小,以及Perm里的内容;若是是Heap的,那咱们就必须作内存Dump,而后分析为何会发生这样的状况,内存里到底存了什么对象,至于内存分析的最佳的分析工具天然是MAT啦,不了解的请google之。———————————————【寒泉子】目前在阿里从事JVM相关工做,为各业务系统作性能优化,性能问题分析,以前主要从事支付宝框架容器的开发