2.4 实战:OutOfMemoryError异常(myeclipse)

目录

2.4.1 Java堆溢出

进行分析

 2.4.1 虚拟机栈和本地方法栈溢出

2.4.3 运行时常量池溢出

2.4.4 方法区溢出

2.4.5 本机直接内存溢出


对象访问在 Java 语言中无处不在,是最普通的程序行为,但即使是最简单的访问,也会却涉及 Java 栈、 Java 堆、方法区这三个最重要内存区域之间的关联关系,如下面的这句代码:

Object obj = new Object();
1
我们知道在Java栈中保存的是对象的引用,在Java堆中才是具体new出来的对象实体,根据具体类型以及虚拟机实现的对象内存布局( Object Memory Layout)的不同,这块内存的长度是不固定的。 
另外,在 Java 堆中还必须包含能查找到此对象类型数据(如对象类型、父类、 实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。

既然java栈中的是对象的引用,那么我们如何使用对象那,主流的访问方式有两种:使用句柄和直接指针。

(1)使用句柄:

如果使用句柄访问方式, Java 堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和类型数据各自的具体地址信息,如图:

(2)直接指针

如果使用直接指针访问方式, Java 堆对象的布局中就必须考虑如何放置访问类型数据的相关信息, reference 中直接存储的就是对象地址,如图:

这两种对象的访问方式各有优势,使用句柄访问方式的最大好处就是 reference 中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中的实例数据指针,而 reference 本身不需要被修改。

使用直接指针访问方式的最大好处就是速度更快,它节省了一次指针定位的时间开销,由于对象的访问在 Java 中非常频繁,因此这类开销积少成多后也是一项非常可观的执行成本。

上图是一张Java运行时的内存分布图,可知虚拟机内存都有发生OutOfMemoryError(下文称 OOM)异常的可能,作为一个合格的Java开发人员,我们应该做到的是:

(1)第一,通过代码验证 Java 虚拟机规范中描述的各个运行时区域储存的内容; 
(2)第二,遇到内存溢出的时候,应该可以找打具体的位置,并进行合理的解决;

下边就聊一下 OOM:

一、Java 堆溢出

我们知道Java 堆用于储存对象实例,我们只要不断地创建对象,并且保证 GC Roots 到对象之间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量到达最大堆的容量限制后产生内存溢出异常(OOM)。
 

2.4.1 Java堆溢出

代码2-1,java堆内存溢出异常测试,HeapOOM.java

package cn.chapter2;

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

public class HeapOOM {
	
	static class OOMObject{
	}
	
	public static void main(String[] args){
		List<OOMObject> list = new ArrayList<OOMObject>();
		
		while(true){
			list.add(new OOMObject());
		}
	}
}
/**
 * Vm args:-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
 * 堆的最小值参数-Xms,堆的最大值参数-Xmx
 * -XX:+HeapDumpOnOutOfMemoryError表示让虚拟机在出现内存异常时Dump出当前的内存堆转储快照
 * -XX:HeapDumpPath:为快照文件位置
 */

设置参数:

 

 解释一下参数的意思:

1.Java -verbose:gc 中参数-verbose:gc 表示输出虚拟机中GC的详细情况.

使用后输出如下:

[Full GC 168K->97K(1984K), 0.0253873 secs]

解读如下:

箭头前后的数据168K和97K分别表示垃圾收集GC前后所有存活对象使用的内存容量,说明有168K-97K=71K的对象容量被回收,括号内的数据1984K为堆内存的总容量,收集所需要的时间是0.0253873秒(这个时间在每次执行的时候会有所不同)

Note:GC会暂用CPU时间片,有可能造成应用程序在某个时刻极短的停顿.

2.-Xms :设置堆的最小值, –Xmx :设置堆的最大值  

   在这里将他们都设置为20M,可以避免堆自动扩展。

3.通过参数 –XX:+HeapDumpOnOutOfMemoryError 可以让虚拟机在出现内存溢出异常时Dump出当前的内存堆转储快照以便事后分析。

运行程序

这个时候就可以在工程的根目录下找到对应的文件了,然后我们就可以使用MAT工具Dump出来的堆转储快照进行分析。

其中,框起来的文件之前没有,运行后会出现,若是没有自动出现,刷新一下项目,右键项目,点击refresh,就会出现这个文件。

进行分析

使用 MyEclipse Memory Analyzer Tool插件进行分析(需要先安装插件)

2.具体分析参照如下链接:

http://www.ibm.com/developerworks/cn/opensource/os-cn-ecl-ma/index.html?ca=drs-

http://essen.iteye.com/blog/1825314

http://tivan.iteye.com/blog/1487855

 2.4.1 虚拟机栈和本地方法栈溢出

关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:

  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
  • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。

将实验范围限制于单线程中的操作 

代码清单2-2 虚拟机栈和本地方法栈OOM测试(JavaVMStackSOF.java)

/**
 * VM Args:-Xss128k
 * @author huangh
 */
package cn.chapter2;

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;
		}
	}
}

运行结果:

如果测试时不限与单线程,暂时没搞懂,待续。。。

2.4.3 运行时常量池溢出

/**
 * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
 * @author huangh
 */
package cn.chapter2;

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

public class RuntimeConstantPoolOOM {
	
	public static void main(String[] args) {
		//使用List保持着常量池引用,避免Full GC回收常量池行为
		List<String> list = new ArrayList<String>();
		//10M 的PermSize在integer范围内足够产生OOM了
		int i = 0;
		while(true){
			list.add(String.valueOf(i++).intern());
		}
	}
}

运行结果:

等了很久,和书上运行结果不一致

2.4.4 方法区溢出

2.4.5 本机直接内存溢出

/**
 * VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
 * @author huangh
 */
package cn.chapter2;

import java.lang.reflect.Field;
import sun.misc.Unsafe;

public class DirectMemoryOOM {
	
	private static final int _1MB = 1024*1024;
	
	public static void main(String[] args) throws Exception{
		Field unsafeField = Unsafe.class.getDeclaredFields()[0];
		unsafeField.setAccessible(true);
		Unsafe unsafe = (Unsafe) unsafeField.get(_1MB);
		while(true){
			unsafe.allocateMemory(_1MB);
		}
	}
}

运行结果:

猜你喜欢

转载自blog.csdn.net/Quantum_Dog/article/details/86737596