世界上并没有完美的程序,但是我们并不因此而沮丧,因为写程序就是一个不断追求完美的过程。
-
堆
产生方式:java堆主要用于存储对象,所以只要不断的创建对象并保持GC Roots到对象的可达性,就会出现内存溢出异常OOM。
产生原因:java堆无法扩展更多的内存来容纳新对象时,就会报异常OOM。
标志:所报异常中会出现"Java heap space"。
解决:先通过工具查看原因,查看GCRoots的引用链,是内存泄漏还是内存溢出,如果是内存泄漏,找出导致内存泄漏的代码的位置;如果是内存溢出,可以看一看是否可通过Xmx、Xms增加堆内存,再看一看有没有生命周期过长、持有状态时间过长、存储结构设计不合理的代码。
注意:JDK7以后,将字符串常量池放入了堆中,所以如果一直创建字符串,也会导致堆的OOM,而不是方法区。 -
栈
产生方式:栈是线程独占的,主要用于执行方法。线程申请的空间大于栈的容量或线程申请的栈深度大于栈所允许的最大深度,这两种情况都会报StackOverflowError。
产生原因:在Hotspot中栈无法扩容,运行时栈容量无法容纳新的栈帧,即栈容量不足;栈帧中本地变量表的长度太长,使栈帧所占空间太大;会报StackOverflow异常。
标志:“StackOverflow”。
解决:通过Xss调整栈容量的大小。
注意:如果在栈空间可以扩容的虚拟机上,空间不足导致的内存溢出会报OOM异常。在线程很多的情况下,有时可以通过减少栈容量和减少最大堆来换取更多的线程。 -
方法区
产生方式:方法区主要用于存储类型信息,包括类名、访问修饰符、常量池、字段描述、方法描述等。通过CGlib动态代理、JSP或动态语言中不断创建新的类,就会导致内存溢出OOM。
产生原因:产生大量的类导致方法区被填满,所以报OOM异常。
标志:在JDK7以前会在异常中出现:“PermGen space”
解决:在JDK7及以前方法区由永久代管理,所以可以通过设置MaxPermSize解决,JDK8以后使用元数据空间代替了永久代,可以设置MaxMetadataspaceSize,但是注意MaxMetadataspaceSize默认值为-1,即没有限制,或只受限于本地内存的大小,所以正常的创建类很少会造成方法区的OOM了。 -
本机直接内存
产生方式:通过Unsafe::allocateMemory()申请分配直接内存,如果不足,则报OOM异常。
产生原因:直接或间接(NIO)的使用DirectMemory。
解决:可以通过MaxDirectMomorySize指定,如果不指定默认为与Java堆最大值一致。
注意:如果Heap Dump文件中没有明显异常,但是出现OOM,很可能是直接内存导致的。