一.运行时数据区域
Java虚拟机将其管理的内存划分为不同的数据区域,他们各自有不同的用途,创建及销毁时间。
总体情况如下:
1.1程序计数器
每当我们创建一个线程时,便会相对应的使其拥有一个程序计数器。程序计数器的主要功能是作为当前线程的行号指示器。字节码指示器会因为程序计数器的值的不同而执行不同的代码。各个线程的程序计数器互不影响,独立储存。
在实际运行中,如果当前线程执行的是java方法,程序计数器记录的是正在执行的虚拟机字节码指令的地址,如果当前线程执行的是本地Native方法,程序计数器的值为空。
此区域是唯一没有规定OutOfMemoryError异常的区域!
1.2虚拟机栈
此区域会抛出两种异常:
1.StackOverflowError : 当对栈的访问深度超出栈本身的深度时抛出,即栈已空还在get。
2.OutOfMemoryError :虚拟机栈的大小是支持扩展的,当虚拟机栈需要扩展而无法分配内存时抛出此异常。
1.3本地方法栈
1.StackOverflowError : 当对栈的访问深度超出栈本身的深度时抛出,即栈已空还在get。
2.OutOfMemoryError :栈无法分配内存时抛出此异常。
1.4堆
堆是所有线程共享的用于存放所有对象实例(包含数组)的一片很大的内存区域。此区域在逻辑上处于连续的内存空间中,而可以不处于连续的物理空间中。
堆可以稍微细分为 1新生代(Eden空间,From Survivor空间,To Survivor空间) 2老年代。其中,可能还会存在一些线程私有的分配缓冲区(TLAB-----Thread Local Allocation Buffer),分配缓冲去在下文中会仔细介绍。
抛出OutOfMemorryError异常 :当堆中没有内存完成实例对象的分配,与此同时又没有内存供其扩展时抛出异常。
1.5方法区
方法区时所有线程共享的用于储存已被虚拟机加载的类信息,常量,静态变量等数据。方法区的一部分叫做运行时常量池,数据进入运行时常量池有两种途径:1.class文件中含有一部分信息叫常量池(存放编译器生成的字面量和符号引用),在类被加载后会放入运行时常量池。2非编译器也可以进入运行时常量池,比如String的intern()方法。在HotSpot虚拟机中,方法去更多的被叫做永久带,这是为了同堆一起进行垃圾收集。但是,这造成了很多的内存溢出问题。
二.以HotSpot虚拟机为例探索内存区域
2.1普通对象的创建
普通对象的创建过程分为5个步骤:
2.1.1.进行类加载检查
2.1.2分配内存
内存分配有两种方式
分配内存时会出现并发问题,A线程想在某一区域分配内存还未完成时,B线程也想使用此区域的内存。解决办法有两种。
2.1.3分配的内存空间初始化为0值
2.1.4进行必要设置
2.1.5按程序员意愿进行初始化
2.2内存布局
2.2.1对象头
2.2.2实例数据
2.2.3对齐填充
2.3访问定位
2.3.1句柄访问
2.3.2直接指针访问
三.内存溢出实例
3.1堆溢出
/** * VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError * @author zzm */ public class HeapOOM { static class OOMObject { } public static void main(String[] args) { List<OOMObject> list = new ArrayList<OOMObject>(); while (true) { list.add(new OOMObject()); } } }
3.2虚拟机栈溢出/本地方法栈溢出
/** * VM Args:-Xss128k * @author zzm */ 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; } } }不停创建线程并为其分配各自的虚拟机栈会抛出OutOfMemorryError异常, 别运行这段代码,会死机 !!!!
/** * VM Args:-Xss2M (这时候不妨设大些) * @author zzm */ 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) throws Throwable { JavaVMStackOOM oom = new JavaVMStackOOM(); oom.stackLeakByThread(); } }
3.3方法区(包含运行时常量池)
/** * VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M * @author zzm */ public class RuntimeConstantPoolOOM { public static void main(String[] args) { // 使用List保持着常量池引用,避免Full GC回收常量池行为 List<String> list = new ArrayList<String>(); // 10MB的PermSize在integer范围内足够产生OOM了 int i = 0; while (true) { list.add(String.valueOf(i++).intern()); } } }也可以生成大量的动态类区填满方法区使其抛出异常。在当下的主流框架如Spring,Hibernate中会使用CGLib这类字节码技术对类进行增强,增强的类越多,就需要更大的方法区保证动态类生成的Class可以载入内存。
/** * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M * @author zzm */ 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() { public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invokeSuper(obj, args); } }); enhancer.create(); } } static class OOMObject { } }
最后,本片文章是我阅读《深入理解Java虚拟机-----JVM高级特性于最佳实践》总结而来,溢出异常的代码也是来自于书中的源码。初学java,出错实乃再平常不过之事,万望各位提出我的错误,在此谢过诸位了。