测试OOM异常

一、Java堆溢出

内存溢出和内存泄露

内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用

package com.weixuan.outofmemory;

import java.util.ArrayList;
import java.util.List;

/**
 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 * -XX:+HeapDumpOnOutOfMemoryError
 *
 * @author Nicholas
 * Java堆溢出
 * Java 堆用于存储对象实例,只要不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象
 * 那么当对象数量到达最大堆的容量限制后就会产生内存溢出异常
 */

/*
 * -Xms20M : 堆的最小值为20m
 * -Xmx20M : 堆的最大值为20m,最大值和最小值相同,可以避免堆自动扩展
 * -Xmn10M : 新生代大小为10m
 * -XX:+PrintGCDetails : 打印辅助信息
 * -XX:SurvivorRatio=8 : Java 堆中的Eden区与Survivor区的大小比值,设置为8,则两个Survivor区与一个Eden区的比值为2:8,一个Survivor区占整个年轻代的1/10
 * -XX:+HeapDumpOnOutOfMemoryError : 当出现内存溢出异常时,dump出当前的内存转储快照信息以便后期分析
 */

/**
 * 如何处理堆内存溢出?
 * 通过EMA eclipse memory analyzer 打开堆内存转储文件
 * 首先分清是内存泄露还是内存溢出
 * 如果是内存泄露:通过工具查看泄露对象到GC Roots的引用链,然后找出泄露对象是通过怎样的路径与GC roots 相关联并导致垃圾回收器无法自动回收他们的
 * 如果不存在泄露,也就是说内存中的对象还活着,那就应当检查虚拟机的堆参数 -Xmx -Xms 是否可以适当的调整
 *
 */
public class HeapOOM {

	static class OOMObject {

	}

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

二、虚拟机栈和本地方法栈溢出

package com.weixuan.outofmemory;

/**
 * 虚拟机栈和本地方法栈溢出
 * -Xss128k
 * @author Nicholas
 *	在hotspot虚拟机中不区分虚拟机栈和本地方法栈 也就是说 -Xoss参数无效
 *	栈容量只由 -Xss参数设置
 *
 *在单线程情况下,无论是栈帧太大还是虚拟机容量太小,当内存无法分配的时候,都是stackoverflowerror异常
 */

public class JavaVMStackSOF {

	private int stacklength = 1;

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

	public static void main(String[] args) {
		JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF();

		try {
			javaVMStackSOF.stackLeak();
		} catch (Throwable e) {
			System.out.println("Stack length : " + javaVMStackSOF.stacklength);
			throw e;
		}
	}
}

三、方法区和运行时常量池溢出

package com.weixuan.outofmemory;

import java.util.ArrayList;
import java.util.List;

/**
 * 运行时常量池的内存溢出
 * -XX:PermSize=10M -XX:MaxPermSize=10M
 * @author Nicholas
 * Java 8 已经将永久代从方法区移除
 */

/**
 * intern() 是一个native 方法
 *	如果字符串常量池中已经包含一个等于此string对象的字符串,则返回次string对象的引用
 */
public class RuntimeConstantPoolOOM {

	public void testIntern() {
		String string1 = new StringBuilder("计算机").append("软件").toString();
		System.out.println(string1==string1.intern());

		String string2 = new StringBuilder("ja").append("va").toString();
		System.out.println(string2==string2.intern());
	}

	public static void main(String[] args) {

		List<String> list = new ArrayList<String>();

		int i = 0;
		while (true) {
			list.add(String.valueOf(i++).intern());
		}
	}
}

Java 8 已经将永久代从方法区移除

四、本机直接内存溢出

package com.weixuan.outofmemory;

import java.lang.reflect.Field;

import sun.misc.Unsafe;

/**
 * -Xms20M -XX:MaxDirectMemorySize=10M
 * @author Nicholas
 *
 */
public class DirectMemoryOOM {

	private static final int _1MB = 1024 * 1024;

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Field unsafeField = Unsafe.class.getDeclaredFields()[0];
		unsafeField.setAccessible(true);
		Unsafe unsafe = null;
		try {
			unsafe = (Unsafe) unsafeField.get(null);
		} catch (IllegalArgumentException | IllegalAccessException e) {
			e.printStackTrace();
		}

		while (true) {
			unsafe.allocateMemory(_1MB);
		}
	}

}

五、总结

内存泄露的几种场景:

1、长生命周期的对象持有短生命周期对象的引用

这是内存泄露最常见的场景,也是代码设计中经常出现的问题。

例如:在全局静态map中缓存局部变量,且没有清空操作,随着时间的推移,这个map会越来越大,造成内存泄露。

2、修改hashset中对象的参数值,且参数是计算哈希值的字段

当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,造成内存泄露。

3、机器的连接数和关闭时间设置

长时间开启非常耗费资源的连接,也会造成内存泄露。

内存溢出的几种情况:

1、堆内存溢出(outOfMemoryError:java heap space)

在jvm规范中,堆中的内存是用来生成对象实例和数组的。

如果细分,堆内存还可以分为年轻代和年老代,年轻代包括一个eden区和两个survivor区。

当生成新对象时,内存的申请过程如下:

a、jvm先尝试在eden区分配新建对象所需的内存;

b、如果内存大小足够,申请结束,否则下一步;

c、jvm启动youngGC,试图将eden区中不活跃的对象释放掉,释放后若Eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区;

d、Survivor区被用来作为Eden及old的中间交换区域,当OLD区空间足够时,Survivor区的对象会被移到Old区,否则会被保留在Survivor区;

e、 当OLD区空间不够时,JVM会在OLD区进行full GC;

f、full GC后,若Survivor及OLD区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则出现”out of memory错误”:

outOfMemoryError:java heap space

2、方法区内存溢出(outOfMemoryError:permgem space)

在jvm规范中,方法区主要存放的是类信息、常量、静态变量等。

所以如果程序加载的类过多,或者使用反射、gclib等这种动态代理生成类的技术,就可能导致该区发生内存溢出,一般该区发生内存溢出时的错误信息为:

outOfMemoryError:permgem space

3、线程栈溢出(java.lang.StackOverflowError)

线程栈时线程独有的一块内存结构,所以线程栈发生问题必定是某个线程运行时产生的错误。

一般线程栈溢出是由于递归太深或方法调用层级过多导致的。

发生栈溢出的错误信息为:

java.lang.StackOverflowError

如何避免内存泄露

1、尽早释放无用对象的引用

2、使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存一块区域

3、尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收

4、避免在循环中创建对象

5、开启大型文件或从数据库一次拿了太多的数据很容易造成内存溢出,所以在这些地方要大概计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。

时间: 2024-08-11 13:06:46

测试OOM异常的相关文章

深入JVM——OOM异常解析

JVM对象访问解析 对象访问过程的内存情况 public void function(){ Object obj = new Object(); } function方法被执行的时候,JVM在JVM栈中为function创建一个栈帧,用于存放function在运行过程中的一些信息. Object obj被执行时,JVM在function方法对应的栈帧中的本地变量表中创建Object类型的引用obj. new Object()被执行时,JVM在堆内存中创建一块Object类型的.包含实例数据值的结

深入理解Java虚拟机03:OOM异常

OOM 异常 (OutOfMemoryError) OOM 异常 (OutOfMemoryError) Java 堆溢出 Java 虚拟机栈和本地方法栈溢出 方法区和运行时常量池溢出 直接内存溢出 Java 堆溢出 出现标志:java.lang.OutOfMemoryError: Java heap space 解决方法: 先通过内存对象分析工具分析Dump出来的堆转存储快照,确认内存中的对象是否是必要的,级分清楚是出现了内存泄漏还是内存溢出: 如果是内存泄漏,通过攻击查看泄漏对象到GC Roo

OOM异常产生的原因和处理方法

一般而言,android中常见的原因主要有以下几个: 1.数据库的cursor没有关闭. 2.构造adapter没有使用缓存contentview. 3.调用registerReceiver()后未调用unregisterReceiver(). 4.未关闭InputStream/OutputStream. 5.Bitmap使用后未调用recycle(). 6.Context泄漏. 7.static关键字等. 接下来分别对这些溢出情况说出解决的思路: 1.针对数据库cursor没有关闭的情况,如果

Android OOM异常解决方案

一,什么是OOM异常: OOM(out of Memory)即内存溢出异常,也就是说内存占有量超过了VM所分配的最大,导致应用程序异常终止: 二,为什么会产生OOM异常呢? OOM异常是Android中经常遇到的一个问题,程序员稍微不注意可能就导致其产生.通常OOM都发生在需要用到大量内存的情况下,因为Android的每一个应用都是一个Davlik虚拟机,该虚拟机的默认堆内存只有16M,远远无法跟我们的PC机比较,因此和容易导致OOM(Out Of Memory)异常的产生.导致这样的异常主要有

Junit测试出现异常:Exception in thread &quot;main&quot; java.lang.NoSuchMethodError: org.junit.platform.commons.util.

在进行单元测试时,测试出现异常 Exception in thread "main" java.lang.NoSuchMethodError: org.junit.platform.commons.util.ReflectionUtils.getDefaultClassLoader()Ljava/lang/ClassLoader; 错误就在pom.xml的依赖中,仔细查看控制台输出你会发现IntelliJ IDEA正在尝试使用JUnit5运行我的测试用例. at com.intelli

常? OOM 异常分析

---------作者: 捡?螺的?男孩 常? OOM 异常分析 堆溢出 栈溢出 方法区溢出 本机直接内存溢出 GC overhead limit exceeded 一.堆溢出 Java 堆?于存储对象实例,只要不断地创建对象,并且保证 GC Roots 到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么 在对象数量到达最?堆的容量限制后就会产?内存溢出异常. 原因 无法在java堆中分配对象 应用程序保存了无法被GC回收的对象 应用程序过度使用finalizer 排查解决思路 查找关键报

使用JUnit测试预期异常

开发人员常常使用单元测试来验证的一段儿代码的操作,很多时候单元测试可以检查抛出预期异常( expected exceptions)的代码.在Java语言中,JUnit是一套标准的单元测试方案,它提供了很多验证抛出的异常的机制.本文就探讨一下他们的优点. 我们拿下面的代码作为例子,写一个测试,确保canVote() 方法返回true或者false, 同时你也能写一个测试用来验证这个方法抛出的IllegalArgumentException异常. (Guava类库中提供了一个作参数检查的工具类--P

利用 Traceview 精准定位启动时间测试的异常方法 (工具开源)

机智的防爬虫标识原创博客地址:http://www.cnblogs.com/alexkn/p/7095855.html博客求关注: http://www.cnblogs.com/alexkn 1.启动时间测试常用方案介绍 如何精确测试启动时间,其实这个问题可大可小,主要需要看团队对启动时间的测试精度要求,当启动时间测试误差需要精确到小几十毫秒时,很多问题都会暴露,因为其实目前很难有一种方式去评估数据的有效性.当前设备状态,CPU温度,内存,系统GC,研发人员的代码以及线程模式等,都有可能导致启动

Android加载大图片OOM异常解决

尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图, 因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存. 因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source, decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsse