记得笔试的时候出过这个问题,“写出OOM的原因及解决方案”。我能记住的就只有几个,还记不牢,特地在网上搜了一下,然后记录一下,图是网上盗的,内容是看完后根据自己的理解写的。
首先我找到源码看到原因分两类
- 第一个构造方法没有参数,报错信息是虚拟机给的;
- 第二个构造方法是有给出信息参数的。
构造方法是有给出信息参数的
Direct buffer memory
这个信息来自java.nio.Bits.reserveMemory()
函数,查看调用过程:java.nio.Bits.DirectByteBuffer
的构造函数 -> java.nio.Bits.ByteBuffer.allocateDirect()
分配函数,然后结合reserveMemory
源码帮助记忆。
解决方法:
- 分配时内存不够引起的错误,因为Direct ByteBuffer的默认大小为64MB,可以修改启动参数
-XX:MaxDirectMemorySize
; - 添加了-XX:+DisableExplicitGC参数导致无法正常gc导致的,删掉就好;
- 机器的内存太小了,没法调大,那就升级内存;
- 可以通过 Arthas 等在线诊断工具,排查
java.nio.Bits.ByteBuffer.allocateDirect()
分配函数的调用情况; jlra.tryHandlePendingReference()
调用了sun.misc.Cleaner.clean()
来清除Direct buffer,可以用反射来手动调用清除;- 检查使用nio的代码,毕竟这个函数在这个包用的最多。
static void reserveMemory(long size, int cap) {
...
// 执行gc,这个可以删除启动参数-XX:+DisableExplicitGC
System.gc();
// 会递增延迟时间的重试循环,
boolean interrupted = false;
try {
long sleepTime = 1;
int sleeps = 0;
while (true) {
if (tryReserveMemory(size, cap)) {
return;
}
// 9次到了就退出循环
if (sleeps >= 9) {
break;
}
// 挂起失败
if (!jlra.tryHandlePendingReference()) {
try {
Thread.sleep(sleepTime);
// 延迟时间是指数性增长的
sleepTime <<= 1;
sleeps++;
} catch (InterruptedException e) {
interrupted = true;
}
}
}
// 9次都没重试成功就会抛出oom
throw new OutOfMemoryError("Direct buffer memory");
} finally {
if (interrupted) {
// don't swallow interrupts
Thread.currentThread().interrupt();
}
}
...
}
Required array size too large
数组太大,找到报错的来源,有BufferedInputStream
,Files
,AbstractCollection
,ConcurrentHashMap
这几个类;
static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
if (n >= MAX_ARRAY_SIZE)
throw new OutOfMemoryError("Required array size too large");
从源码上可见,原因是超出数组分配的最大长度,如果数组真的需要这么大的,那就得考虑切分数组了。
构造方法是没有参数的
Java heap space
堆内存不够了。往报错位置里面找,基本都能找到分配内存相关的代码:
ByteBuffer.allocateDirect(0);
ByteBuffer.allocate(size);
// arrayList扩容时最小的容量小于0
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
解决办法:
- 调整jvm启动参数
-Xmn -Xms -Xmx
,比如-Xms64m -Xmx512m
; - 排查有没有大对象,或者内存泄露,如果是机器内存不够支持业务导致的,那就加机器或者限流降级。
GC overhead limit exceeded
这是gc回收时间占用98%以上,却回收不到2%的空间导致的。内存泄露得检查代码,特别是那些一直new对象的代码。解决方法和上面的差不多。
String a = "a" + "b";
while(true) {
...
new Object();
...
}
Metaspace
元数据区的内存不够。可能是在启动的时候报错,重新部署的时候报错,运行时报错,原因是加载的类太多或者太大。
解决办法:
- 调整
-XX:MaxMetaspaceSize
- 长期没有重启时进行了重新部署,可能会重复加载class,这种情况只要重新启动就好
- 运行时可能动态创建了大量class,而且这些class生命周期短,但是jvm默认不会删除这些class,只能设置
-XX:+CMSClassUnloadingEnabled
和-XX:+UseConcMarkSweepGC
Unable to create new native thread
无法再创建线程。可能是创建的线程的内存不足,或者创建的线程数太多,超过了系统的限制。
解决办法:
- 增加机器的内存;
- 假如代码中的线程是一直创建不回收的话,那就需要修改,使用线程池;
- 使用线程池还太多的话,可以减少线程池的线程数量;
- 降低堆内存heap space,把内存让给线程;
-Xss
减少线程的堆栈内存大小;
Out of swap space
虚拟内存溢出,虚拟内存是由物理内存和交换空间构成。原因是内存耗光,或者是某个程序内存泄露导致的。
解决办法:
- 内存不够大话,可以增加内存,或者代码分解部署到多台机子;
- 升级到64bit;
Kill process or sacrifice child
这是linux系统一种内核作业方式导致的,out of memory killer,它会个进程打分,然后内存不足的情况下优先杀死评分低的来释放内存。 解决方法:
- 分解代码部署到多个服务器;
- 调整out of memory killer的参数。
笔记就做到这里了,要是有什么记错的地方,记得提个醒哈