NOTE
7.19 OOM问题举例
1. 为什么会发生OOM 得从GC回收过程说起(参考垃圾回收.md),可以看出GC之回收GCRoot达不到的对象的,GCRoot引用的对象是强引用,是没办法被回收的。 如果jvm内存中都是强引用对象,那么FullGC后jvm内存剩余空间很有可能不足以存放将要创建的新的对象,就会发生OOM 1.1.
这是历史学习笔记,可能存在过时或不完整的理解。
1. 为什么会发生OOM
得从GC回收过程说起(参考垃圾回收.md),可以看出GC之回收GCRoot达不到的对象的,GCRoot引用的对象是强引用,是没办法被回收的。 如果jvm内存中都是强引用对象,那么FullGC后jvm内存剩余空间很有可能不足以存放将要创建的新的对象,就会发生OOM
1.1. OOM的可能原因
1.1.1. 代码问题【50%】
根据不同的分区进行分析:
- 非堆内存
- 堆外内存【直接内存】
- 文件
- socket
- 数据库连接
- 方法区
- 堆
1.1.2. jvm参数配置问题【40%】
1.1.3. 是机器内存不够【10%】
升级硬件
2. 如何分析OOM
2.1. OutOfMemoryError【堆】
2.1.1. 代码
public class OOMTest
{
//jvm参数:-Xmx128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:\Users\[user]\Downloads
public static void main(String[] args)
{
Map<Integer, byte[]> map = new HashMap();
for (int i = 0; i < 128; i++)
{
byte[] bytes = new byte[1024 * 1024];//1m
map.put(i, bytes);
}
}
}
2.1.2. 获取内存Dump文件
- 加上jvm参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
- 使用jmap
jps -l //获取PID
jmap -dump:format=b,file=OOMTest.hprof PID
2.1.3. 使用MAT/JProfiler分析
2.1.3.1. MAT找到占用最大的空间的对象即可
- 整体功能

- 按照对象数量查看

- 按照对象内存占用查看

- MAT自动分析

2.1.3.2. JProfiler找到占用最大的空间的对象即可
- 按照字节占用排序

- 分析最大的对象

- 查看什么对象引用了这个对象

- 找到GCRoot

2.2. Metaspace【方法区】
2.2.1. 代码
//jvm参数:-XX:MetaspaceSize=20m -XX:MaxMetaspaceSize=20m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=C:\Users\[user]\Downloads
public class OOMMetaspace
{
static class OOMTest
{
}
public static void metaOOM()
{
while (true)
{
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.invokeSuper(o, null);
}
});
enhancer.create();
}
}
public static void main(String[] args)
{
metaOOM();
}
}
2.2.2. 获取内存Dump文件
- 加上jvm参数
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./
- 使用jmap
jps -l //获取PID
jmap -dump:format=b,file=OOMTest.hprof PID
2.2.3. 使用MAT/JProfiler分析
2.2.3.1. MAT
- 搜索Class

- 查看哪些引用了Class

- 找到最多的引用

2.2.3.2. JProfiler
-
按照ClassLoader分组

-
查看谁引用了Class

-
分析其中一个

-
查看哪个GCRoot引用了他

-
找到了GCRoot之后分析代码

2.3. 堆外内存
3. 其他OOM举例
3.1. StackOverflowError【栈】
递归没有设置正确的终止条件
public class OOMStackOverFlow
{
public static void stackOverflowError()
{
stackOverflowError();
}
public static void main(String[] args)
{
stackOverflowError();
}
}
3.2. GC overhead limit exceeded【GC次数太多但是效率低】
分配的内存不够用,就是用大量的时间用于GC但是回收的效率很低。官方定义超过98%的时间用来做GC并且回收了不到2%的堆内存时会抛出此异常。
//-Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails -XX:+PrintGCDateStamps
public class OOMGCOverHeadLimit
{
public static void gcOverhead()
{
int i = 0;
List<String> list = new ArrayList<>();
try
{
while (true)
{
list.add(String.valueOf(i++).intern());
}
}catch (Throwable e)
{
e.printStackTrace();
}
}
public static void main(String[] args)
{
gcOverhead();
}
}
3.3. Direct buffer memory【直接内存】
//-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m -XX:+PrintGCDateStamps
public class OOMDirectBuffer
{
public static void directBuffer()
{
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024 * 1024 * 100);
}
public static void main(String[] args)
{
directBuffer();
}
}
3.4. unable to create new native thread【线程】
public class OOMThreadTooMuch
{
public static void thredTooMuch()
{
while (true)
{
new Thread(()->{
try
{
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}).start();
}
}
public static void main(String[] args)
{
thredTooMuch();
}
}