OOM:Out Of Memory
- java.lang.StackOverflowError
- java.lang.OutOfMemoryError:Java heap space
- java.lang.OutOfMemoryError:GC overhead limit exceeded
- java.lang.OutOfMemoryError:Direct buffer memory
- java.lang.OutOfMemoryError:unable to create new native thread
- java.lang.OutOfMemoryError:Metaspace
StackOverflowError:栈溢出
栈,一般默认大小范围是: 512K~1024K。
StackOverflowError 是个错误 Error,不是异常。
错误和异常的关系图:
由此可见,StackOverflowError 和 OOM 都属于错误范畴。平时我们所说的 “报 OOM 异常了” 只是口头语而已。
StackOverflowError 代码实现很简单,我们知道方法存在栈中,因此我们直接无限递归的调用一个方法就可以做到。
public static void main(String[] args) {
stackOverflowError();
}
private static void stackOverflowError() {
stackOverflowError();
}
------------------------------
Exception in thread "main" java.lang.StackOverflowError
OutOfMemoryError:Java heap space
内存溢出,Demo实现时最好手动分配内存大小,我设置了 10M,然后 new 了一个 20M 的 byte 数组,直接溢出。
byte[] bytes = new byte[20 * 1024 * 1024];
---------------------
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
如何手动设置内存大小在 Java 强软弱虚 中有提到。
以上两种都是最简单最最常见的 “OOM” 错误类型,下面,重点、难点 来了...
OutOfMemoryError:GC overhead limit exceeded
什么意思?照样子翻译过来是:GC 超出开销限制。
具体什么含义呢:
- GC 回收时间过长,过长的意思就是:超过98%的时间都用来 GC 却回收了不到2%的堆内存。
- 连续多次 GC 都回收不到2%的极端情况下才会抛出
- 若不抛 GC overhead limit exceeded 异常,那不到2%的内存很快会被再次填满,GC不得不再次执行,导致恶性循环
代码测试之前,先将程序的内存手动分配一个很小的空间:
测试代码:
int i = 0;
List<String> list=new ArrayList<>();
try {
while (true){
list.add(String.valueOf(++i).intern());
}
}catch (Throwable throwable){
System.out.println("i=="+i);
throwable.printStackTrace();
throw throwable;
}
---------------------------
i==138412
java.lang.OutOfMemoryError: GC overhead limit exceeded
[Full GC (Ergonomics)
[PSYoungGen: 2047K->2047K(2560K)]
[ParOldGen: 7055K->7044K(7168K)] 9103K->9092K(9728K),
[Metaspace: 3651K->3651K(1056768K)], 0.0734731 secs]
[Times: user=0.38 sys=0.00, real=0.07 secs]
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
由上代码可见,在 i 累加到 138412 时报错,也就是程序执行了 138412 次。
看打印日志
- PSYoungGen 新生代 GC 之前内存占用 2047K,GC之后还是2047...内存总共就 2560K,GC 根本没起到作用
- ParOldGen 老生代 GC之前占用 7055K,GC之后占 7044K,就收了11K,几乎没起到作用
- Metaspace GC 之前是3651K,GC 之后还是3651
- 说明系统在GC执行了 N 次之后,发现实在是 GC 不动了,没啥用,所以干脆就报个错:GC overhead limit exceeded
Metaspace :元空间
- 本质和永久代类似,都是对JVM规范中方法区的实现
- 不在虚拟机中,使用的是本地内存,因此只收本地内存限制
OutOfMemoryError:Direct buffer memory
——直接缓冲存储器内存溢出
导致原因:
- NIO程序经常使用 ByteBuffer 来读写数据,这是一种基于通道(Channel)和缓冲区(Buffer)的IO方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作,这样能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native堆 中来回复制数据。
- ByteBuffer.allocate(capability):第一种方式是分配 JVM 堆内存,属于 GC 管辖范围,由于需要拷贝所以速度相对较慢。
- ByteBuffer.allocateDirect(capability):第二种方式是分配 OS 本地内存,不属于 GC 管辖范围,由于不需要拷贝所以速度相对较快。
- 但如果不断分配本地内存,堆内存又很少使用,那么 JVM 就不需要 GC,DirectByteBuffer 对象们就不会被回收,这时候堆内存充足,但本地内存可能已经快用光了,如果还继续分配本地内存,最终就会导致 OOM。
配置内存参数
-Xmx10m -Xms10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
代码
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
---------------
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
本地内存只分配了 5m,然后初始化了一个 6m 的 ByteBuffer,直接 OOM。
OutOfMemoryError:unable to create new native thread
——不能创建新的本地线程。在高并发的情况下会出现,与对应的平台有关。
导致原因:
- 创建了太多线程,超过系统承载极限
- 服务器不允许应用程序创建这么多线程,Linux 系统默认允许单个进程可以创建的线程数量上限是 1024 个。
解决办法:
- 降低线程数量
- 修改 Linux 服务器配置,扩大默认限制
伪代码:
while (true) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
----------------------------
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
at com.example.lpy.myapplication.javatest.MyOOMDemo.main(MyOOMDemo.java:23)
无限地新建线程,并且让每一个线程都等待很长很长时间,这样每个线程都会存在很长很长时间,最终导致 OOM。
OutOfMemoryError:Metaspace
- 元空间内存溢出
- 元空间是方法区,装类的信息,各种元素等。
- 查看 Metaspace 大小的方法:命令行输入:java -XX:+PrintFlagsInitial
- java 8 及其之后的版本用 Metaspase 代替永久代
- Metaspace 是方法区在 HotSpot 中的实现,它与持久代最大的区别在于:Metaspace 并不在 JVM内存中而是在本地内存
永久代(java 8之后被 Metaspace 取代)存放以下信息:
- 虚拟机加载的类信息
- 常量池
- 静态变量
- 即时编译后的代码
JVM参数:-XX:MetaspaseSize=8m -XX:MaxMetaspaseSize=8m
伪代码:
static class OOMTest{}
public static void main(final String[] args) {
int i = 0;
try {
while (true){
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperClass(OOMTest.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor()
{
@Override
public Object intercept(Object o, Method method,Object[] objects,MethodProxy methodProxy) throws Throwable{
return methodProxy.invokerSuper(o,args);
}
});
enhancer.create();
}
} catch (Throwable e) {
System.out.println(i+" 次后发生了异常");
e.printStackTrace();
}
}
-------------------
276 次后发生了异常
java.lang.OutOfMemoryError:Metaspace