13.常见的OOM

519 阅读6分钟

1 常见的几种OOM

常见的OOM错误有以下几种:

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
java.lang.OutOfMemoryError: Out of swap space
java.lang.OutOfMemoryError: Requested array s

OOM(OutOfMemoryError) 问题归根结底三点原因:

  • 本身资源不够
  • 申请的太多内存
  • 资源耗尽

2 java.lang.StackOverflowError

原因:在一个函数中调用自己会产生这个错误,函数调用栈太深了

//-Xms10m -Xmx10m

public class StackOverflowErrorDemo {
	public static void main ( String [] args) {
		 stackOverflowError();	    
	}
	private static void stackOverflowError () {
		 stackOverflowError();        
//Exception in thread "main" java.lang.StackOverflowError   
	}
}
//控制台结果
Exception in thread "main" java.lang.StackOverflowError

解决方案:1.避免循环递归调用,2.调大栈的内存,3.内存泄露(Memory leak)

3 java.lang.OutOfMemoryError : java heap space

原因:1. 创建了一个很大的对象 2.超出预期的访问量/数据量,3.内存泄漏

大对象

//-Xms2m -Xmx2m -XX:+HeapDumpOnOutOfMemoryError
public class OutOfMemoryErrorJavaHeapSpaceDemo {
	static final int SIZE = 3 * 1024 * 1024;

	public static void main(String[] a) {
 		int[] i = new int[SIZE];
	}
}
//控制台结果
Exception in thread "main"java.lang.OutOfMemoryError:Java heap space

一些提供close的资源未关闭导致内存泄露,如数据库链接,网络链接,和IO会造成内存泄漏。

4 java.lang.OutOfMemoryError : GC overhead limit exceeded

原因:执行垃圾收集的时间比例太大,有效的运算量太小,默认情况下,如果GC花费的时间超过98% 并且GC回收的内存少于2%,jvm就会抛出这个错误

//-Xmx10M -Xms10m -XX:MaxMetaspaceSize=10M
public static void main(String[] args) {
 	List < String > list = new ArrayList < String > ();
	while (true) {
		list.add(UUID.randomUUID().toString().intern());
	}
}
//Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

解决办法:1. 找到哪类对象占用了最多的内存,然后看是否增大堆内存,2. 需要进行GC turning

5 java.lang.OutOfMemoryError : Direct buffer memory

原因:主要是NIO 引起的,写NIO程序经常用到ByteBuffer来读取或者写入数据,这是一种基于通道和缓冲区的I/O方式,它可以使用native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在JAVA堆和native堆中来回切换。ByteBuffer.allocate(capability) 这种方法是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对慢

6 java.lang.OutOfMemoryError : unable to create new native thread

原因:1.应用创建了太多的线程,一个应用进程创建了多个线程,超过系统承载能力 2.你的服务器并不允许你的程序创建这么多线程,Linux系统普通用户默认单个进程可以创建的线程数是1024个

while (true) {
	new Thread(new Runnable() {
 		public void run() {
 			try {
				Thread.sleep(10000000);
			} catch(InterruptedException e) {

			}
		}
	}).start();
}

解决方案:1.想办法降低你的应用程序创建的线程数量。2. 对于确实需要创建很多线程的修改liunx配置,扩大Linux默认限制。命令:ulimit -u 查看 vi /etc/security/limits.d/90-nproc.conf 增加一条用户记录配置大小

7 java.lang.OutOfMemoryError : Metaspace

原因:由于方法区被移至 Metaspace,所以 Metaspace 的使用量与 JVM 加载到内存中的 class 数量/大小有关。主要原因, 是加载到内存中的 class 数量太多或者体积太大导致元数据区(Metaspace) 已被用满。Java8 后使用元空间代替了永久代,元空间是方法区在HotSpot中的实现,它与持久代最大的区别是:元空间并不在虚拟机中的内存中而是使用本地内存。 元空间存放的信息:1. 虚拟机加载的类信息 ;2 常量池;3.静态变量,4.即时编译后的代码

//-Xmx10M -Xms10m -XX:MetaspaceSize=10M -XX:MaxMetaspaceSize=10M
public class Metaspace {
 	static javassist.ClassPool cp = javassist.ClassPool.getDefault();

	public static void main(String[] args) throws Exception {
 		for (int i = 0; ; i++) {
 			Class c = cp.makeClass("Metaspace.demo.Generated" + i).toClass();
		}
	}
}

//Exception in thread "main" java.lang.OutOfMemoryError: Metaspace

解决方案: -XX:MaxMetaspaceSize=512m 但需要注意, 不限制Metaspace内存的大小, 假若物理内存不足, 有可能会引起内存交换(swapping), 严重拖累系统性能。此外,还可能造成native内存分配失败等问题。

8 java.lang.OutOfMemoryError: Out of swap space

原因:往往是由操作系统级别的问题引起的,例如:1.操作系统配置的交换空间不足。2.系统上的另一个进程消耗所有内存资源。3.还有可能是本地内存泄漏导致应用程序失败,比如:应用程序调用了native code连续分配内存,但却没有被释放。

解决方案:
第一种,升级机器以包含更多内存 也是最简单的方法, 增加虚拟内存(swap space) 的大小. 各操作系统的设置方法不太一样
第二种,优化应用程序以减少其内存占用

9 java.lang.OutOfMemoryError: Requested array size exceeds VM limit

原因:数组太大, 最终长度超过平台限制值, 但小于 Integer.MAX_INT

要取得JVM对数组大小的限制, 要分配长度差不多等于 Integer.MAXINT 的数组. 这个示例运行在64位的Mac OS X, Hotspot 7平台时, 只有两个长度会抛出这个错误: Integer.MAXINT-1 和 Integer.MAX_INT。

解决:

1.需要检查业务代码, 确认是否真的需要那么大的数组。如果可以减小数组长度,如果不行,可能需要把数据拆分为多个块, 然后根据需要按批次加载。
2.修改程序逻辑。例如拆分成多个小块,按批次加载; 或者放弃使用标准库,而是自己处理数据结构,比如使用 sun.misc.Unsafe 类, 通过Unsafe工具类可以像C语言一样直接分配内存。

10 Out of memory:Kill process or sacrifice child

原因:为了理解这个错误,我们需要补充一点操作系统的基础知识。操作系统是建立在进程的概念之上,这些进程在内核中作业,其中有一个非常特殊的进程,名叫“内存杀手(Out of memory killer)”。当内核检测到系统内存不足时,OOM killer被激活,然后选择一个进程杀掉。哪一个进程这么倒霉呢?选择的算法和想法都很朴实:谁占用内存最多,谁就被干掉。

解决方案 解决这个问题最有效也是最直接的方法就是升级内存,其他方法诸如:调整OOM Killer配置、水平扩展应用,将内存的负载分摊到若干小实例上


JVM完整目录

1. jvm概述
2.类加载机制
3.运行时数据区[PC寄存器、虚拟机栈、本地方法栈]
4.运行时数据区[堆]
5.运行时数据区[方法区]
6.暂缺
7. 运行时数据区[对象的实例化内存布局与访问定位、直接内存]
8.执行引擎(Execution Engine)
9.字符串常量池
10.垃圾回收[概述、相关算法]
11.垃圾回收[垃圾回收相关概念]
12.垃圾回收[垃圾回收器]
13.常见的OOM
14. JDK命令行工具