OutOfMemory详解

标签: outofmemory 内存溢出 jvm调优
20人阅读 评论(0) 收藏 举报
分类:

       Java虚拟机运行时数据区域分为5个:虚拟机栈,本地方法栈,程序计数器,堆,方法区。其中虚拟机栈,程序计数器,本地方法栈都是线程私有的,生命周期和线程生命周期相同,随线程生而生,随线程灭而灭。堆和方法区是线程共享的,存放了大量的对象实例,通常说的GC指的就是这个,程序计数器是线程所执行的行号指示器,是唯一一个不会产生OutOfMemory(一下简称OOM)的区域。

产生OOM的主要原因有以下4点

1.java堆溢出

        java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制来清除这些对象,那么在对象容量达到最大堆的容量限制后就会产生内存溢出异常。代码如下

/**
 * VM Args : -Xms20m -Xmx20m -XX:+HeapDumpOnOutofMemoryError
 * 
 * @author msi-
 *
 */
public class HeapOOM {
	static class OOMObject {
	};

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

       运行结果:

     1.2 常见场景

      List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。数据库一次查询返回到内存中的数据量太大,有大循环重复产生新对象实体等等

     1.3 解决方案:

      java堆内存的OOM异常是实际应用中经常发生的内存溢出异常情况。一般的手段是先通过内存映像分析工具(Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,确认内存中的对象是否是必要的。

      若是不必要的,则发生了内存泄漏,可进一步通过工具查看对象到GC Roots的引用链。找到泄漏对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收的。掌握了泄漏对象的类型信息及GC Roots引用链的信息及类型信息即可准确定位泄漏代码的位置。

      如果不存在泄漏,也就是说内存中的对象都必须活着,那就增加虚拟机的堆参数(-Xmx堆最大值与-Xms堆初始值)。若从代码上检查则查看是否存在某些对象声明周期过长,持有状态时间多长的情况。

2.虚拟机栈和本地方法栈溢出

      在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,栈容量由-Xss参数设定。关于此,java虚拟机规范中描述了两种异常。

     2.1 StackOverflowError

     如果线程请求的栈深度大于虚拟机所允许的最大深度,将跑出此异常。将异常分为两种,看似严谨却存在着一些互相重叠的地方:当栈空间无法继续分配内存时,到底是内存太小,还是已使用的栈空间太大。代码如下:

/**
 * VM Args:-Xss128k
 * 
 * @author msi-
 *
 */
public class JavaVMStackSOF {
	private int stackLength = 1;

	public void stackLeak() {
		stackLength++;
		stackLeak();
	}

	public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
			oom.stackLeak();
		} catch (Throwable e) {
			System.out.println("stack length: " + oom.stackLength);
            throw e;
		}
	}
}

运行结果:   

      在测试中,将实验范围限制于单线程的操作,无论是下面两种方法都无法让虚拟机产生OOM异常,结果都是StackOverflowError异常

       1)使用-Xss参数减少栈内存容量。结果:抛出StackOverflowError异常,异常出现时输出的栈深度相应缩小

       2)定义了大量的本地变量,增大此方法帧中本地变量表的长度。结果同上。

     测试结果表明:在单个线程下,无所事由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,都抛出StackOverflowError异常。

2.2 OutOfMemoryError

          如果测试时不限于单线程,通过不断简历线程的方式倒是可以初上内存溢出异常,  产生这样的内存溢出与栈空间是否足够大并不存在任何联系。在此情况下,为每个线程的栈分配的内存越大,越容易产生溢出异常。代码如下:

/**
 * VM Args:-Xss2M
 * 
 * @author msi-
 *
 */
public class JavaVMStackOOM {
	private void  dontStop() {
		while(true) {
		}
	}
	
	public void stackLeakByThread() {
		while(true) {
			Thread thread = new Thread(new Runnable() {
				@Override
				public void run() {
					dontStop();
				}
			});
			thread.start();
		}
	}
	
	public static void main(String[] args) {
		JavaVMStackOOM oom = new JavaVMStackOOM();
		oom.stackLeakByThread();
	}
}

     运行结果:


   注意:此段代码不要轻易运行,可能会死机。

   2.3 解决方案

       因为操作系统分配给每个进程的内存时有限制的,譬如32位的windows的限制为2G。虚拟机提供了参数来空值Java堆和方法区的两部分内存的最大值,剩余的内存=2GB(操作系统限制)-Xmx(最大堆容量)-MaxPermSize(最大方法区容量),程序计数器小号内存很小,可以忽略。如果虚拟机进程本身耗费的内存不计算在内,剩下的内存就由虚拟机栈和本地方法栈“瓜分”了。每个线程分配到的栈容量越大,可以建立的线程数自然求越少,建立线程时月容易把剩下的内存耗尽。

       正常情况下,栈深度达到1000~2000完全没问题,对于正常的方法调用(包括递归),这个深度够用了。但是,如果尽力过多线程导致内存溢出,在不能减少线程数或者更换64位虚拟机的情况下,只能通过减少最大堆和减少栈容量来换取更多的线程。如果没有这方面的处理经验,这种通过“减少内存”的手段来解决内存溢出的方式很难想到。

3.运行时常量池和方法区溢出

   3.1 运行时常量池溢出

        如果 要向 运行时 常量 池 中 添加 内容, 最简单 的 做法 就是 使用 String. intern() 这个 Native 方法。 该 方法 的 作用 是: 如果 池 中 已经 包含 一个 等于 此 String 对象 的 字符串, 则 返回 代表 池 中 这个 字符串 的 String 对象; 否则, 将此 String 对象 包含 的 字符串 添加 到 常量 池 中, 并且 返回 此 String 对象 的 引用。 由于 常量 池 分配 在 方法 区内, 我们 可以 通过- XX: PermSize 和- XX: MaxPermSize 限制 方法 区 的 大小, 从而 间接 限制 其中 常量 池 的 容量。代码如下    

/**
 * VM Args : -XX:PermSize=10M -XX:MaxPermSize=10M
 * 
 * @author msi-
 *
 */
public class RuntimeConstantPoolOOM {
	public static void main(String[] args) {
		// 使用List保持着常量池引用,避免Full GC回收常量池薪给
		ArrayList<String> list = new ArrayList<String>();
		// 10MB的PermSize在integer范围内足够产生OOM了
		int i = 1;
		while (true) {
			list.add(String.valueOf(i++).intern());
		}
	}
}

    运行结果:


     注意:在JDK1.6之前,常量池分配在永久代,以上代码在JDK1.6下运行才回发生内存溢出,如果在JDK1.7及其之后的版本运行,则是死循环:  

 3.2方法区溢出

         方法 区 用于 存放 Class 的 相关 信息, 如 类 名、 访问 修饰 符、 常量 池、 字段 描述、 方法 描述 等。 对于 这个 区域 的 测试, 基本 的 思路 是 运行时 产生 大量 的 类 去 填满 方法 区, 直到 溢出。 虽然 直接 使用 Java SE API 也可以 动态 产生 类( 如 反射 时 的 GeneratedConstructorAccessor 和 动态 代理 等), 但在本次实验中操作起来比较麻烦。本次测试借助 CGLib[ 3] 直接 操作 字节 码 运行时, 生成 了 大量 的 动态 类。 代码如下

* VM Arg是:-XX:PermSize=10M -XX:MaxPermSize=10M
 *  
 * @author msi-
 *
 */
public class JavaMethodAreaOOM {
	public static void main(String[] args) {
		while(true) {
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(OOMObject.class);
		enhancer.setUseCache(false);
		enhancer.setCallback(new MethodInterceptor() {
			
			@Override
			public Object intercept(Object obj, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {
				return proxy.invokeSuper(obj, arg2);
			}
		});
		enhancer.create();
		}
	}
	static class OOMObject {};
}

     运行结果:


    3.3 原因分析及解决方案

     PermGen space溢出是一种非常常见的溢出,此类异常常见场景有:大量JSP或动态产生JSP文件的应用,基于OSGi的应用(即使是同一个类文件,被不同的加载器加载也会视为不同的类),

    解决方法为增加PermGen space内存

4.直接内存溢出

     DirectMemory容量可通过-XX:MaxDirectMemorySize指定,如果不指定,则默认与java堆的最大值一样。通过反射获取Unsafe实例并进行内存分配(Unsafe类的getUnsafe()方法限制了只有引导类加载器才会返回实例,也就是设计者希望只有rt.jar中的类才能使用Unsafe的功能)。因为,虽然使用DirectByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,于是手动抛出异常,真正申请分配内存的方法是unsafe.allocateMemory();  代码如下:

 * VM Args :-Xmx20M -XX:MaxDirectMemorySize=10M
 * 
 * @author msi-
 *
 */
public class DirectMemoryOOM {
	private static final int _1MB = 1024 * 1024;

	public static void main(String[] args) throws Exception {
       java.lang.reflect.Field unsafeField = Unsafe.class.getDeclaredFields()[0];
       unsafeField.setAccessible(true);
       Unsafe  unsafe = (Unsafe) unsafeField.get(null);
       while(true) {
    	   unsafe.allocateMemory(_1MB);
       }
	}
}

  运行结果:



查看评论

outOfMemory产生原因以及解决约办法

1.tomcat 的outOfMemory 问题的产生原因以及解决方法:详情见:http://www.cnblogs.com/dartagnan/archive/2010/12/24/2003469....
  • lixiaobing1999
  • lixiaobing1999
  • 2017-05-19 11:45:51
  • 219

常见的异常产生的原因和解决办法

一、数组越界异常 Java.lang.ArrayIndexOutofBoundsException 产生的原因:访问了不存在的索引 解决的办法:索引0到数组长度-1的范围内取值 二、空指针异常 Jav...
  • qq_36859561
  • qq_36859561
  • 2017-12-13 17:43:54
  • 170

java程序中三种OutOfMemory异常

jvm中使用了三种不同类型的内存区域:Permanent Generation space(永久保存区域)、Heap space(堆区域)、Java Stacks(Java栈)。     其中永久保...
  • LZW190
  • LZW190
  • 2013-08-29 23:22:30
  • 7712

中死锁产生的原因及解决办法

  • zgqtxwd
  • zgqtxwd
  • 2008-04-28 17:01:00
  • 115

编写各种outofmemory/stackoverflow程序

最近在网上看到一片文章Java工程师成神之路,对其中的 1.1.5. 自己编写各种outofmemory,stackoverflow程序 HeapOutOfMemory Young OutOfM...
  • junranhuigu
  • junranhuigu
  • 2016-02-02 18:03:35
  • 1568

几种outofmemory的解决方法

几种outofmemory的解决方法: 1. java.lang.OutOfMemoryError: PermGen space 2. java.lang.OutOfMemoryError: Ja...
  • kingofworld
  • kingofworld
  • 2014-11-05 09:42:20
  • 1860

Android OOM产生原因及如何解决

产生原因OOM产生可能的原因 OOM产生的本质是什么 如何解决和避免OOM 内存泄露问题...
  • hnulwt
  • hnulwt
  • 2015-04-07 09:23:52
  • 9277

Java多线程产生死锁的原因和解决方法

一般造成死锁必须同时满足如下4个条件:  1,互斥条件:线程使用的资源必须至少有一个是不能共享的;  2,请求与保持条件:至少有一个线程必须持有一个资源并且正在等待获取一个当前被其它线程持有的资源; ...
  • hongtashan11
  • hongtashan11
  • 2011-05-24 20:11:00
  • 1547

【深入理解JVM】:OutOfMemoryError异常总结

JVM内存区域中,除了程序计数器外,其他几个运行时区域都有可能发生OutOfMemoryError(OOM)异常。本文对OOM异常进行总结,通过代码验证JVM规范中描述的运行时区域存储的内容;了解可能...
  • u011080472
  • u011080472
  • 2016-05-05 13:21:56
  • 5124

tomcat与servlet乱码解决办法

  • 2011年08月08日 20:46
  • 1KB
  • 下载
    个人资料
    等级:
    访问量: 184
    积分: 35
    排名: 211万+
    文章分类
    文章存档