刚刚开通了一个公众号,会分享一些技术博客和自己觉得比较好的项目,同时会更新一些自己使用的工具和图书资料,后面会整理一些面试资料进行分享,觉得有兴趣的可以关注一下。
前言
从今天开始会整理一些常见的面试题目,博客中会涉及一些
JVM
参数,可以关注一下公众号,回复JVM,即可领取最新版《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》
今天要聊的就是比较经典的OOM问题。主要有5种场景,喜下面分别介绍。
1.堆内存溢出
这是比较常见的一种内存溢出的异常。
堆内存主要用来存储对象实例,我们只要不停的创建对象,并且保证GCROOTS
和对象之间有可达路径避免被垃圾回收的话,那么在对象数量超过最大堆的大小限制之后,很快就能出现这个异常。我们可以通过设置堆内存位2M-Xms2m -Xmx2m
,然后执行测试代码:
public class OOM {
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
while (true){
list.add(new Object());
}
}
}
异常描述如下:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3512)
at java.base/java.util.Arrays.copyOf(Arrays.java:3481)
at java.base/java.util.ArrayList.grow(ArrayList.java:237)
at java.base/java.util.ArrayList.grow(ArrayList.java:244)
at java.base/java.util.ArrayList.add(ArrayList.java:454)
at java.base/java.util.ArrayList.add(ArrayList.java:467)
at OOM.main(OOM.java:8)
2.栈内存异常
栈是线程私有的,他的生命周期和线程相同,每个方法在执行的同时都会创建一个栈帧,栈帧里面保存了局部变量表、操作数栈、动态链接、方法出口。那方法在调用的过程呢,其实就是栈帧入栈和出栈的过程。
在Java虚拟机规范中对虚拟机栈定义了两种异常。
- 第一,如果线程请求的栈深度大于虚拟机所允许的深度,就会抛出StackOverflow异常。比如我们可以设置-Xss160k,那其中Xss代表每个线程的栈内存大小,通过测试发现,单线程下无论怎么设置参数,其实都是StackOverflow异常。
public class OOM {
private static int length = 1;
public static void main(String[] args) {
test();
}
static void test(){
System.out.println(length++);
test();
}
}
结果如下:
15261
Exception in thread "main" java.lang.StackOverflowError
at java.base/java.io.FileOutputStream.write(FileOutputStream.java:349)
at java.base/java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:81)
at java.base/java.io.BufferedOutputStream.flush(BufferedOutputStream.java:142)
at java.base/java.io.PrintStream.write(PrintStream.java:570)
at java.base/sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:234)
at java.base/sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:313)
at java.base/sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:111)
at java.base/java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:178)
at java.base/java.io.PrintStream.writeln(PrintStream.java:723)
at java.base/java.io.PrintStream.println(PrintStream.java:938)
at OOM.test(OOM.java:12)
at OOM.test(OOM.java:13)
- 第二种,如果虚拟机栈可以动态扩展的话,并且扩展时无法申请到足够的内存,就会抛出OOM异常。现在我们把代码改成多线程,并且调整参数为2m,
-Xss2m
,就可以看见OOM异常了,因为每个线程分配的内存越大,栈空间可容纳的线程总数量就越少,越容易产生内存溢出,那反之的话如果内存不够的情况,可以调小该参数来达到支撑更多线程的目的。
public class OOM {
public static void main(String[] args) {
while (true){
new Thread(() -> dontStop()).start();
}
}
static void dontStop(){
try {
TimeUnit.HOURS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
结果如下:
JVMDUMP039I ????????"systhrow",????"java/lang/OutOfMemoryError",?? 2024/04/26 17:19:54 - ????
3.直接内存溢出
直接内存并不是虚拟机运行时数据区域的一部分,也不是《Java虚拟机规范》中内定义的内存区域,并且不受堆内存大小的限制,但是受到机器内存大小的限制。比如常见在NIO
中可以使用native函数直接分配堆外内存,就有可能导致OOM的问题。那直接内存的大小可以通过参数来指定,那如果你不指定的话,他默认就和java堆内存最大值一样,直接内存导致内存溢出一个最明显的特征就是dump文件不会有明显的异常,如果发现OOM之后dump文件很小,而程序中又直接或间接使用了NIO,那么就可以考虑检查一下是不是这方面的原因。
异常信息如下:
java.lang.OutOfMemoryError: Direct buffer memory
代码里使用DirectByteBuffer
之类的,如下:
public class OOM {
public static void main(String[] args) {
List<ByteBuffer> list = new ArrayList<>();
while (true){
list.add(ByteBuffer.allocate(1024*1024*20));
}
}
}
4. 元空间溢出
JDK8
之后使用Metaspace
来代替永久代,Metaspace
是方法区在Hotspot
的实现。Metaspace
不在虚拟机内存,而是使用本地内存,也就是JDK8
中的ClassMetadata
,被存储在叫做Metaspace
的nativr memory
。
比如我们可以这样设置元空间的大小
-XX:MetaspaceSize=50m -XX:MaxMetaspaceSize=50m(1.8版本jdk)
1.8版本之前方法区存在于永久代,1.8版本之后取消了永久代的概念,如果是之前的版本的话,可以通过设置PermSize大小来设置永久代的大小。
代码:
public class OOM {
public static void main(String[] args) {
while (true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOM.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);
}
});
enhancer.create();
}
}
}
结果:
Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.OutOfMemoryError-->Metaspace
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:511)
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:585)
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:572)
at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:387)
at OOM.main(OOM.java:23)
5.GC OOM
GC OOM
是由于JVM在GC时,对象过多导致内存溢出。
代码如下:
public class OOM {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(15);
for (int i = 0; i < Integer.MAX_VALUE; i++) {
executorService.execute(() -> {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
}
}
异常信息:
java.lang.OutOfMemoryError: GC overhead limit