首先看下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 ”的文章
Java小强
未曾清贫难成人,不经打击老天真。
自古英雄出炼狱,从来富贵入凡尘。
发表评论: