一、OutOfMemoryError(内存溢出)
JVM 管理的内存大致包括三种区域:Heap space(堆区域)
、Java Stacks(Java 栈)
、Permanent Generation space(永久保存区域)
。由此,OOM 简单的分为堆溢出、栈溢出、永久代溢出(常量池/方法区)。Java 程序的每个线程中都有一个独立的堆栈。容易发生内存溢出问题的内存空间包括:Heap space 和 Permanent Generation space。
1️⃣堆区域用来存放 Class 的实例(即对象),对象需要存储的内容主要是非静态属性。每次用 new 创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被 JVM 的垃圾回收机制管理。
【Java 堆内存溢出】java.lang.OutOfMemoryError: Java heap space
此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。原因是 JVM 创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与 Heap space 有关。解决这类问题有两种思路:
- 对于内存泄露,可以通过内存监控软件查找程序中的泄露代码。检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。
- 增加 JVM 中 Xms(初始堆大小)和 Xmx(最大堆大小)参数的大小。如:
set JAVA_OPTS= -Xms256m -Xmx1024m
2️⃣Java 栈跟大多数编程语言包括汇编语言的栈功能相似,主要基本类型变量以及方法的输入输出参数。
【Java 栈内存溢出】java.lang.StackOverflowError
不会抛 OOM error,但也是比较常见的 Java 内存溢出。Java 栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。
3️⃣永久保存区域主要存放 Class 和 Meta 的信息,Class 第一次被 Load 的时候被放入 PermGen space 区域,Class 需要存储的内容主要包括方法和静态属性。
【Java 永久代溢出】java.lang.OutOfMemoryError: PermGen space
即方法区溢出了,一般出现于大量的 jar 或 Class 或者渲染视图(jsp、vm、ftl)页面,或者采用 cglib 等反射机制的情况,因为上述情况会产生大量的 Class 信息存储于方法区,使得 JVM 装载类的空间不够。解决这类问题有以下两种办法:
- 增加 JVM 中的初始永久保存区域大小
XX:PermSize
和最大永久保存区域大小XX:MaxPermSize
参数的大小。如针对 tomcat6.0,在 catalina.sh 或 catalina.bat 文件中一系列环境变量名说明结束处(大约在70行左右)增加一行:
JAVA_OPTS=" -XX:PermSize=64M -XX:MaxPermSize=128m"
如果是 windows 服务器还可以在系统环境变量中设置。 - 清理应用程序中 web-inf/lib 下的 jar。如果 tomcat 部署了多个应用,很多应用都使用了相同的 jar,可以将共同的 jar 移到 tomcat 共同的 lib 下,减少类的重复加载。
二、常见场景
1️⃣静态集合类声明为静态 static 的 HashMap、Vector 等集合。通俗来讲A中有B,当前只把B设置为空,A没有设置为空,回收时B无法被回收,因为被A引用。
2️⃣物理连接:DataSource.getConnection()
建立链接,必须通过close()关闭链接
3️⃣内部类和外部模块等的引用
GC 只会回收没有被引用或者根集不可到达的对象(取决于 GC 算法),内部类在生命周期内始终持有外部类的对象的引用,造成外部类的对象始终不满足 GC 的回收条件,反映在内存上就是内存泄露。常见解决方案:
- 将内部类定义为static
- 用static的变量引用匿名内部类的实例或将匿名内部类的实例化操作放到外部类的静态方法中
4️⃣【OutOfMemoryError:unable to create new native thread】Executors 返回的线程池对象的弊端如下:
- FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。 - CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
三、分析定位
1️⃣在未明确找到问题原因前,先添配置 JVM 启动参数,监控复原 OOM 场景自动dump:-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${目录}
2️⃣
- 获取内存的堆信息
jmap -dump:format=b,file=mem.dat pid
- 使用内存映像分析工具(如Eclipse Memory Analyzer)对 dump 出来的堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
- 看分析图,查找对应问题,分析内存占用情况 例如:localstore 存储问题分析,大对象。