Java小强个人技术博客站点    手机版
当前位置: 首页 >> 理论 >> Java内存报错简单分类与分析

Java内存报错简单分类与分析

95340 理论 | 2015-8-3

首先看下java的内存结构,请看下图


Java内存报错简单分类与分析


在运行时的内存区域有5个部分,Method Area(方法区),Java stack(java 虚拟机栈),Native MethodStack(本地方法栈),Heap(堆),Program Counter Regster(程序计数器)。从图中看出方法区和堆用黄色标记,和其他三个区域的不同点就是,方法区和堆是线程共享的,所有的运行在jvm上的程序都能访问这两个区域,堆,方法区和虚拟机的生命周期一样,随着虚拟机的启动而存在,而栈和程序计数器是依赖用户线程的启动和结束而建立和销毁。

Program Counter Regster(程序计数器):

每一个用户线程对应一个程序计数器,用来指示当前线程所执行字节码的行号。由程序计数器给文字码解释器提供吓一条要执行的字节码的的位置。根据jvm规范,在这个区域中不会抛出OutOfMemoryError的内存异常。

Java stack(java 虚拟机栈):

这个区域是最容易出现内存异常的区域,每一个线程对应生成一个线程栈,线程每执行一个方法的时候,都会创建一个栈帧,用来存放方法的局部变量表,操作树栈,动态连接,方法入口,这和C#是不一样的,在C#CLR中没有栈帧的概念,都是在线程栈中通过压栈和出栈的方式进行数据的保存。jvm规范对这个区域定义了两种内存异常,OutOfMemoryError,StackOverflowError。

Native MethodStack(本地方法栈):

和虚拟机栈一样,不同的是处理的对象不一样,虚拟机栈处理java的字节码,而本地栈则是处理的Native方法。其他方面一致。

Heap(堆):

前面说了堆是所有线程都能访问的,随着虚拟机的启动而存在,这块区域很大,因为所有的线程都在这个区域保存实例化的对象,因为每一个类型中,每个接口实现类需要的内存不一样,一个方法内的多个分支需要的内存也不尽相同,我们只有在运行的时候才能知道要创建多少对象,需要分配多大的地址空间。GC关注的正是这样的部分内容,所以很多时候也将堆称为GC堆。堆中肯定不会抛出StackOverflowError类型的异常,所以只有OutOfMemoryError相关类型的异常。

Method Area(方法区):

用于存放已被虚拟机加载的类信息,常量,静态方法,即使编译后的代码。同样只能抛出OutOfMemoryError相关类型的异常。


程序:

package test;
import java.util.ArrayList;
import java.util.List;
public class T {
	private static byte[] bt = new byte[1024*1024];
	public static void main(String[] args){
		List<Object> list = new ArrayList<Object>();
        while (true) {
        	list.add(bt);
        }
	}
}

报错:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:2760)
	at java.util.Arrays.copyOf(Arrays.java:2734)
	at java.util.ArrayList.ensureCapacity(ArrayList.java:167)
	at java.util.ArrayList.add(ArrayList.java:351)
	at test.T.main(T.java:8)

分析:

OutOfMemoryError说明是堆中出现的异常,heap space说明由于新建对象无法获得更多的内存而引发。

从实际开发角度来看,多数是因为代码编写BUG导致。当然不排除由于Jvm参数设置不当。

相关参数:

-Xms(堆的最小值),-Xmx(堆的最大值)


程序:

package test;
public class T {
	public static void main(String[] args){
		Recursion recursion = new Recursion();
        try {
            recursion.recursionself();
        } catch (Throwable e) {
            System.out.println("current value :" + recursion.currentValue);
            e.printStackTrace();
        }
	}
}
class Recursion {
    public int currentValue = 0;
    public void recursionself() {
        currentValue += 1;
        recursionself();
    }
}

报错:

current value :11433
Exception in thread "main" java.lang.StackOverflowError
	at test.Recursion.recursionself(T.java:19)
	at test.Recursion.recursionself(T.java:20)
	at test.Recursion.recursionself(T.java:20)

分析:

栈内存容量设置过小,也可能是代码中线程或方法产生了死循环或者过长的递归调用。

单线程的环境下,无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法再分配的时候,虚拟机抛出StackOverflowError异常。

相关参数:

-Xss参数 栈内存容量大小


设置Tomcat相关参数后启动Tomcat

报错:

Caused by: java.lang.OutOfMemoryError: PermGen space

分析:

程序加载的类过多时,而没有足够的空间。Web容器容易发生此类错误。

相关参数:

-XX:PermSize和-XX:MaxPermSize


程序:

package test;
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class T {
	private static final int _1MB = 1024 * 1024;
	public static void main(String[] args) throws IllegalArgumentException,
			IllegalAccessException {
		Field unsafeField = Unsafe.class.getDeclaredFields()[0];
		unsafeField.setAccessible(true);
		Unsafe unsafe = (Unsafe) unsafeField.get(null);
		while (true) {
			unsafe.allocateMemory(_1MB);
		}
	}
}

报错:

Exception in thread "main" java.lang.OutOfMemoryError
	at sun.misc.Unsafe.allocateMemory(Native Method)
	at test.T.main(T.java:12)
或:
java.lang.OutOfMemoryError: Direct buffer memory

分析:

如果你在直接或间接使用了ByteBuffer中的allocateDirect方法的时候,而不做clear的时候就会出现类似的问题。

常规的引用程序IO输出存在一个内核态与用户态的转换过程,也就是对应直接内存与非直接内存,如果常规的应用程序你要将一个文件的内容输出到客户端需要通过OS的直接内存转换拷贝到程序的非直接内存(也就是heap中),然后再输出到直接内存由操作系统发送出去,而直接内存就是由OS和应用程序共同管理的,而非直接内存可以直接由应用程序自己控制的内存,jvm垃圾回收不会回收掉直接内存这部分的内存,所以要注意。

Java虚拟机可以通过参数-XX:MaxDirectMemorySize设定本机直接内存可用大小,MaxDirectMemorySize没显式配置的时候,NIO direct memory可申请的空间的上限就是-Xmx减去一个survivor space的预留大小。

相关参数:

-XX:MaxDirectMemorySize


推荐您阅读更多有关于“ ByteBuffer Java内存 OutOfMemoryError StackOverflowError ”的文章

上一篇:JS图片上传预览 Android上的问题 下一篇:Java HashMap的工作原理(2)

猜你喜欢

发表评论: