六、OOM
1、概念
java.lang.OutOfMemoryError,内存溢出,简称OOM
这里的内存主要是指堆内存(Heap)和方法区(Method Area),因为这两块区域主要用于存储对象的相关信息,使用的内存空间较大,而程序在运行期间,如果因为编码不当(一般是因为这样),就会引发OOM
2、java.lang.OutOfMemoryError: Java heap space
【1】异常出现
-Xms1m -Xmx1m
JVM启动参数
public static void main(String[] args) {
byte[] arr = new byte[1024 * 1024];
System.out.println(arr.length);
}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at ???.main(???.java:???)
【2】异常原因
JVM堆内存主要用于存储对象的实例和数组等。因为我们设置了JVM堆的最大内存为1MB,而我们创建了一个byte数组,初始容量也为1MB。JVM在启动时,还需要加载其它类文件到内存当中,这样剩余的内存空间就不足1MB。如果此时创建的对象大于剩余内存空间,就会报该异常
此外如果程序在运行期间,创建的大对象,不能被GC回收掉,JVM也会报该异常
-Xms8m -Xmx8m
JVM启动参数
public static void main(String[] args) {
int loop = 8;
List<byte[]> list = new ArrayList<byte[]>(loop);
for (int i = 1; i <= loop; i++) {
byte[] arr = {
};
try {
arr = new byte[1024 * 1024];
} catch (Throwable t) {
System.out.println("异常了!i=" + i);
t.printStackTrace();
break;
}
list.add(arr);
}
}
异常了!i=7
java.lang.OutOfMemoryError: Java heap space
at ???.main(???.java:???)
【3】解决办法
注意大对象的创建
注意集合容器的remove或clear方法的使用
3、java.lang.OutOfMemoryError: Metaspace
【1】异常出现
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
Maven依赖
-XX:MetaspaceSize=16m -XX:MaxMetaspaceSize=16m
JVM启动参数
public class TestOutOfMemoryError {
public static void main(String[] args) {
for (;;) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(User.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
try {
Object proxy = enhancer.create();
} catch (Throwable t) {
t.printStackTrace();
break;
}
}
}
}
class User {
}
java.lang.OutOfMemoryError: Metaspace
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:386)
at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:219)
at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377)
at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)
at ???.TestOutOfMemoryError.main(TestOutOfMemoryError.java:???)
【2】异常原因
方法区主要用于存储类的相关信息,比如属性、方法等,每加载一个类,都需要把类的信息保存到该内存区域中
java version "1.8.0_211"
Java(TM) SE Runtime Environment (build 1.8.0_211-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.211-b12, mixed mode)
HopSpot通过永久代(PermGen space)来实现方法区,在JDK8的时候,将永久代改为元空间(Metaspace)
CGLIB(Code Generation Library),是基于类继承,通过创建子类代理类的方式,来扩展被代理类(父类)的方法。所以每创建一个代理类,都会把类的信息加载到方法区内存当中
当方法区内存不足时,就会抛出该异常
JDK7示例:
java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) 64-Bit Server VM (build 24.80-b11, mixed mode)
-XX:PermSize=2m -XX:MaxPermSize=2m
JVM启动参数
写个空的Main方法,并启动程序
Error occurred during initialization of VM
java.lang.OutOfMemoryError: PermGen space
at java.io.InputStreamReader.<init>(InputStreamReader.java:74)
at java.io.FileReader.<init>(FileReader.java:72)
at sun.misc.MetaIndex.registerDirectory(MetaIndex.java:166)
at sun.misc.Launcher$ExtClassLoader$1.run(Launcher.java:146)
at sun.misc.Launcher$ExtClassLoader$1.run(Launcher.java:142)
at java.security.AccessController.doPrivileged(Native Method)
at sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Launcher.java:141)
at sun.misc.Launcher.<init>(Launcher.java:71)
at sun.misc.Launcher.<clinit>(Launcher.java:57)
at java.lang.ClassLoader.initSystemClassLoader(ClassLoader.java:1489)
at java.lang.ClassLoader.getSystemClassLoader(ClassLoader.java:1474)
【3】解决办法
调整JVM的启动参数,给予合适的方法区内存大小
- JDK8:
MetaspaceSize
和MaxMetaspaceSize
- JDK7:
PermSize
和MaxPermSize
4、java.lang.OutOfMemoryError: unable to create new native thread
【1】异常出现
在Linux系统上,由非root用户执行如下代码:
public static void main(String[] args) {
for (int i = 1; ; i++) {
try {
new Thread(() -> {
for (;;) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
} catch (Throwable t) {
System.out.println(i);
t.printStackTrace();
System.exit(1);
}
}
}
java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
【2】异常原因
线程的创建是由操作系统来管理的,Java只负责调用本地方法(native)
所以能否创建线程,操作系统说了算
Linux操作系统对于线程创建的管理文件,位于/etc/security/limits.d/90-nproc.conf
# Default limit for number of user's processes to prevent
# accidental fork bombs.
# See rhbz #432903 for reasoning.
* soft nproc 1024
root soft nproc unlimited
可以看到非root用户,最多只能创建1024个线程(实际写代码创建不了1024个)。也可以通过命令ulimit -u
来查看这个限制数
【3】解决办法
使用线程池来管理和复用线程
5、java.lang.OutOfMemoryError: Direct buffer memory
【1】异常出现
-XX:MaxDirectMemorySize=1m
JVM启动参数
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 2048);
System.out.println(buffer.limit());
}
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
at java.nio.Bits.reserveMemory(Bits.java:694)
at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
at ???.main(???.java:???)
【2】异常原因
NIO,可以直接将数据分配到操作系统的物理内存当中,如果分配得过大,就会抛出该异常
可以通过JVM参数-XX:MaxDirectMemorySize=???
来调整,默认是不限制的
【3】解决办法
调整代码逻辑
6、java.lang.OutOfMemoryError: GC overhead limit exceeded
【1】异常出现
-Xms8m -Xmx8m -XX:MetaspaceSize=16m -XX:MaxMetaspaceSize=16m -XX:+PrintGCDetails
JVM启动参数
public class TestOutOfMemoryError {
public static void main(String[] args) {
List<MyArr> list = new ArrayList<MyArr>();
for (int i = 1; ; i++) {
try {
list.add(new MyArr(i));
} catch (Throwable t) {
System.out.println(i);
t.printStackTrace();
break;
}
}
}
}
class MyArr {
private final String str;
public MyArr(int num) {
str = Integer.toString(num);
}
}
java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Integer.java:401)
【2】异常原因
[Full GC (Ergonomics) [PSYoungGen: 1536K->1536K(2048K)] [ParOldGen: 5514K->5514K(5632K)] 7050K->7050K(7680K), [Metaspace: 3355K->3355K(1056768K)], 0.0345095 secs] [Times: user=0.05 sys=0.00, real=0.03 secs]
[Full GC (Ergonomics) [PSYoungGen: 1536K->1536K(2048K)] [ParOldGen: 5515K->5515K(5632K)] 7051K->7051K(7680K), [Metaspace: 3355K->3355K(1056768K)], 0.0259106 secs] [Times: user=0.08 sys=0.00, real=0.03 secs]
[Full GC (Ergonomics) [PSYoungGen: 1536K->1536K(2048K)] [ParOldGen: 5517K->5517K(5632K)] 7053K->7053K(7680K), [Metaspace: 3355K->3355K(1056768K)], 0.0221107 secs] [Times: user=0.05 sys=0.00, real=0.02 secs]
看一下距离JVM抛出异常信息的最近的三条GC信息,可以看到无论怎样Full GC,各个内存区域在GC回收前和GC回收后的内存大小都是一样的,JVM为了防止继续这种“无用功”,从而报出该异常
一般是因为字符串常量过多导致的,JDK7之后,将字符串常量池移动到堆内存当中,一旦还存在引用关系,就无法回收
【3】解决办法
调整代码逻辑
7、内存诊断工具 – Memory Analyzer (MAT)
【1】介绍
The Eclipse Memory Analyzer 是一个快速并且功能强大的 Java堆内存分析工具,它可以帮助我们找到内存泄漏,以及减少内存消耗
【2】示例
1)异常代码
-Xms8m -Xmx8m -XX:+HeapDumpOnOutOfMemoryError
JVM启动参数
package ???;
import java.util.ArrayList;
import java.util.List;
public class TestOutOfMemoryError {
public static void main(String[] args) {
int loop = 8;
List<byte[]> list = new ArrayList<byte[]>(loop);
for (int i = 0; i < loop; i++) {
byte[] arr = new byte[1024 * 1024];
list.add(arr);
}
}
}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid???.hprof ...
Heap dump file created [??? bytes in 0.019 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at ???.TestOutOfMemoryError.main(TestOutOfMemoryError.java:12)
2)分析内存快照文件
将内存快照文件java_pid???.hprof
复制到空文件夹中
File —> Open Heap Dump… —> 打开快照文件
分析完成后,在弹出的对话框中,选择 Leak Suspects Report,并点击 Finish
然后有一个问题,Problem Suspect 1
,点击 Details »
在 Accumulated Objects in Dominator Tree中,看到对象的内存分配情况
Class Name | Shallow Heap | Retained Heap | Percentage |
---|---|---|---|
java.lang.Thread @ 0xffe8a0f8 main | 120 | 6,292,112 | 92.46% |
java.util.ArrayList @ 0xffe8a028 | 24 | 6,291,624 | 92.46% |
java.lang.Object[8] @ 0xffeac700 | 48 | 6,291,600 | 92.46% |
byte[1048576] @ 0xff800000 | 1,048,592 | 1,048,592 | 15.41% |
byte[1048576] @ 0xff900010 | 1,048,592 | 1,048,592 | 15.41% |
byte[1048576] @ 0xffa00020 | 1,048,592 | 1,048,592 | 15.41% |
byte[1048576] @ 0xffb00030 | 1,048,592 | 1,048,592 | 15.41% |
byte[1048576] @ 0xffc00040 | 1,048,592 | 1,048,592 | 15.41% |
byte[1048576] @ 0xffd80000 | 1,048,592 | 1,048,592 | 15.41% |
Total: 6 entries | 6,291,552 | 6,291,552 | 92.45% |
在 Thread Stack中,看到线程栈信息
main
at java.lang.OutOfMemoryError.<init>()V (OutOfMemoryError.java:??)
at com.test.proxy.TestOutOfMemoryError.main([Ljava/lang/String;)V (TestOutOfMemoryError.java:12)
这样就可以定位到问题代码了
8、查看JVM堆内存信息 – jmap
使用JDK自带的工具jmap
,语法 jmap -heap 进程ID
例如:jmap -heap 4371
Attaching to process ID 4371, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.212-b10
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 522190848 (498.0MB)
NewSize = 11141120 (10.625MB)
MaxNewSize = 173342720 (165.3125MB)
OldSize = 22413312 (21.375MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 10027008 (9.5625MB)
used = 534928 (0.5101470947265625MB)
free = 9492080 (9.052352905273438MB)
5.334871578839869% used
Eden Space:
capacity = 8912896 (8.5MB)
used = 534928 (0.5101470947265625MB)
free = 8377968 (7.9898529052734375MB)
6.001730526194853% used
From Space:
capacity = 1114112 (1.0625MB)
used = 0 (0.0MB)
free = 1114112 (1.0625MB)
0.0% used
To Space:
capacity = 1114112 (1.0625MB)
used = 0 (0.0MB)
free = 1114112 (1.0625MB)
0.0% used
concurrent mark-sweep generation:
capacity = 22413312 (21.375MB)
used = 0 (0.0MB)
free = 22413312 (21.375MB)
0.0% used
703 interned Strings occupying 46848 bytes.
可以看到使用的垃圾收集器,堆内存的分配情况,以及堆内存的使用情况