1.导致OOM问题的原因
Java.lang.OutOfMemeoryError异常:
1.1Java堆溢出(Java.lang.OutOfMemeoryError:Java heap space)
新产生的对象最初分配在新生代,新生代满后会进行一次Minor GC,如果Minor GC后空间不足会把该对象和新生代满足条件的对象放入老年代。
老年代空间不足时进行FullGC,之后如果空间还不足以存放新对象则抛出OutOfMemoryErro异常
常见原因:
1)堆内存分配不合理
2) 代码中存在死循环或循环产生过多重复对象
3)集合对对象引用过多且使用完没有清空
4)内存中加载的数据过多,如一次从数据库中取出过多数据
/*** 设置最大堆最小堆:-Xms20m -Xmx20m */
public class HeapOOM {
static class OOMObject { }
public static void main(String[] args) {
List<OOMObject> oomObjectList = new ArrayList<>();
while (true){
oomObjectList.add(new OOMObject());
}
}
1.2虚拟机栈溢出和本地方法栈溢出(java.lang.StackOverflowError)
JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的
出现StackOverflowError异常时,会有明确错误堆栈可供分析,相对而言比较容易定位到问题所在。
如果使用HotSpot虚拟机默认参数,栈深度在大多数情况下(因为每个方法压入栈的帧大小并不是一样的,所以只能说大多
数情况下) 到达1000~2000是完全没有问题.对于正常的方法调用(包括不能做尾递归优化的递归调用)这个深度应该完全够用了。
但是,如果是建立过多线程导致的内存溢出,在不能减少线程数量或者更换64位虚拟机的情况下,就只能通过减少最大堆和减少栈容量来换取更多的线程。 这种通过“减少内存”的手段来解决内存溢出的方式,如果没有这方面处理经验,一般比 较难以想到,这一点读者需要在开发32位系统的多线程应用时意。
也是由于这种问题较为隐蔽,从 JDK 7起,以上提示信息中“unable to create native thread”后面,虚拟机会特别注明
原因可能是“possibly out of memory or process/resource limits reached”。
1.3 运行时常量池和方法区溢出 java.lang.OutOfMemoryError: PermGen space
1)运行时常量池内存溢出
运行时常量池溢出时 在OutOfMemoryError异常后面跟随的提示信息 是“PermGen space”。
说明运行时常量池的确是属于方法区(即JDK 6的HotSpot虚拟机中的永久代)的 一部分。
2)方法区内存溢出
方法区的其他部分的内容,方法区的主要职责是用于存放类型的相关信息, 如类名、 访问修饰符、 常量池、 字段描述、 方法描述等。
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,要达成的条件是比较苛刻的。
在经常运行时生成大量动态类的应用场景里,就应该特别关注这些类的回收状况。
这类场景除了之前提到的程序使用了CGLib字节码增强和动态语言外.常见的还有大量JSP或动态产生JSP 文件的应用(JSP第一次运行时需要编译为Java类)、 基于OSGi的应用(即使是同一个类文件, 被不同 的加载器加载也会视为不同的类) 等。
在JDK 8以后,永久代便完全退出了历史舞台,元空间作为其替代者登场。
在默认设置下,前面列举的那些正常的动态创建新类型的测试用例已经很难再迫使虚拟机产生方法区的溢出异常了。
不过 为了让使用者有预防实际应用里出现类
HotSpot提供了一些参数作为元空间的防御措施,主要包括:
-XX: MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存 大小。
-XX: MetaspaceSize: 指定元空间的初始空间大小, 以字节为单位, 达到该值就会触发垃圾收集 进行类型卸载,同时收集器会对该值进行调整: 如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过-XX: axMetaspaceSize(如果设置了的话)的情况下适当提高值。
-XX: MinMetaspaceFreeRatio: 作用是在垃圾收集之后控制最小的元空间剩余容量的百分比,可减少因为元空间不足导致的垃圾收集的频率。
类似的还有-XX: Max-MetaspaceFreeRatio, 用于控制最 大的元空间剩余容量的百分比。
'内存泄露':申请使用完的内存没有释放,导致虚拟机不能再次使用该内存.此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
'内存溢出':申请的内存超出了JVM能提供的内存大小,此时称之为溢出
2.排查思路:
2.1.先通过内存映像工具对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的。
也就是要先分清楚到底是出现了'内存泄漏还是内存溢出'
2.2.'如果是内存泄漏',可进一步通过工具查看泄漏对象到GC Roots的引用链。
这样就能够找到泄漏的对象是通过怎么样的路径与GC Roots相关联的导致垃圾回收机制无法将其回收。
掌握了泄漏对象的类信息和GC Roots引用链的信息,就可以比较准确地定位泄漏代码的位置。
2.3 '如果不存在泄漏',那么就是内存中的对象确实必须存活着,那么此时就需要通过虚拟机的堆参数( -Xmx和-Xms)来适当调大参数;
从代码上检查是否存在某些对象存活时间过长、持有时间过长的情况,尝试减少运行时内存的消耗。