java程序出现oom如何解决?什么场景下会出现oom?

java程序出现oom如何解决?什么场景下会出现oom?

    • 1.堆内存溢出
    • 2.⽅法区(运⾏时常量池)和元空间溢出
    • 3.直接内存溢出
    • 4.栈内存溢出

1.堆内存溢出

堆内存溢出太常见,⼤部分⼈都应该能想得到这⼀点,堆内存⽤来存储对象实例,我们只要不停的创建对象,并且保证GC Roots和对象之间有可达路径避免垃圾回收,那么在对象数量超过最⼤堆的⼤⼩限制后很快就能出现这个异常。
写⼀段代码测试⼀下,设置堆内存⼤⼩2M。
java程序出现oom如何解决?什么场景下会出现oom?_第1张图片

public class HeapOOM {
	public static void main(String[] args) 
		{List<HeapOOM> list = new ArrayList<>(); 
		while (true) {
			list.add(new HeapOOM());
		}
	}
}

运⾏代码,很快能看见OOM异常出现,这⾥的提⽰是Java heap space堆内存溢出。
java程序出现oom如何解决?什么场景下会出现oom?_第2张图片

⼀般的排查⽅式可以通过设置-XX: +HeapDumpOnOutOfMemoryError在发⽣异常时dump出当前的内存转储快照来分析,分析可以使
⽤Eclipse Memory Analyzer(MAT)来分析,独⽴⽂件可以在官⽹下载。

另外如果使⽤的是IDEA的话,可以使⽤商业版JProfiler或者开源版本的JVM-Profiler,ft外IDEA2018版本之后内置了分析⼯具,包括Flame Graph(⽕焰图)和Call Tree(调⽤树)功能。
java程序出现oom如何解决?什么场景下会出现oom?_第3张图片

⽕焰图

2.⽅法区(运⾏时常量池)和元空间溢出

⽅法区和堆⼀样,是线程共享的区域,包含Class⽂件信息、运⾏时常量池、常量池,运⾏时常量池和常量池的主要区别是具备动态性,也 就是不⼀定⾮要是在Class⽂件中的常量池中的内容才能进⼊运⾏时常量池,运⾏期间也可以可以将新的常量放⼊池中,⽐如String的intern()⽅法。

我们写⼀段代码验证⼀下String.intern(),同时我们设置-XX:MetaspaceSize=50m -XX:MaxMetaspaceSize=50m 元空间⼤⼩。由于我使⽤的是1.8版本的JDK,⽽1.8版本之前⽅法区存在于永久代(PermGen),1.8之后取消了永久代的概念,转为元空间(Metaspace),如果是之前版本可以设置PermSize MaxPermSize永久代的⼤⼩。

private static String str = "test";
	public static void main(String[] args) 
		{List<String> list = new 
		ArrayList<>(); while (true){
			String str2 = str + str; str = str2; 
			list.add(str.intern());
		}
}

运⾏代码,会发现代码报错。
java程序出现oom如何解决?什么场景下会出现oom?_第4张图片

再次修改配置,去除元空间限制,修改堆内存⼤⼩-Xms20m -Xmx20m,可以看见堆内存报错。
在这里插入图片描述
这是为什么呢?intern()本⾝是⼀个native⽅法,它的作⽤是:如果字符串常量池中已经包含⼀个等 于ftString对象的字符串,则返回代表池中这个字符串的String对象;否则,将ftString对象包含的字符串添加到常量池中,并且返回String对象的引⽤。
⽽在1.7版本之后,字符串常量池已经转移到堆区,所以会报出堆内存溢出的错误,如果1.7之前版本的话会看见PermGen space的报错。

3.直接内存溢出

直接内存并不是虚拟机运⾏时数据区域的⼀部分,并且不受堆内存的限制,但是受到机器内存⼤⼩的限制。常见的⽐如在NIO中可以使⽤native函数直接分配堆外内存就容易导致OOM的问题。
直接内存⼤⼩可以通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与Java 堆最⼤值-Xmx⼀样。

由直接内存导致的内存溢出,⼀个明显的特征是在Dump⽂件中不会看见明显的异常,如果发现OOM之后Dump⽂件很⼩,⽽程序中⼜直 接或间接使⽤了NIO,那就可以考虑检查⼀下是不是这⽅⾯的原因。

4.栈内存溢出

栈是线程私有,它的⽣命周期和线程相同。每个⽅法在执⾏的同时都会创建⼀个栈帧⽤于存储局部变量表、操作数栈、动态链接、⽅法出⼝ 等信息,⽅法调⽤的过程就是栈帧⼊栈和出栈的过程。
在java虚拟机规范中,对虚拟机栈定义了两种异常:
如果线程请求的栈深度⼤于虚拟机所允许的深度,将抛出StackOverflowError异常
如果虚拟机栈可以动态扩展,并且扩展时⽆法申请到⾜够的内存,抛出OutOfMemoryError异常先写⼀段代码测试⼀下,设置-Xss160k,-Xss代表每个线程的栈内存⼤⼩

public class StackOOM 
	{ private int length = 1;

	public void stackTest()
		{ System.out.println("stack lenght=" +
		length); length++;
		stackTest();
	}


	public static void main(String[] args) 
	{ StackOOM test = new 
	StackOOM(); test.stackTest();
	}
}

测试发现,单线程下⽆论怎么设置参数都是StackOverflow异常。
java程序出现oom如何解决?什么场景下会出现oom?_第5张图片

尝试把代码修改为多线程,调整-Xss2m,因为为每个线程分配的内存越⼤,栈空间可容纳的线程数量越少,越容易产⽣内存溢出。反之,如果内存不够的情况,可以调⼩该参数来达到⽀撑更多线程的⽬的。

java程序出现oom如何解决?什么场景下会出现oom?_第6张图片

你可能感兴趣的:(sre,java,jvm,开发语言)