《深入理解Java虚拟机》学习小记一之自动内存管理机制

2/22/2017来源:ASP.NET技巧人气:1333

                                              java内存区域与内存溢出异常

一、概要

我们可以带着以下几个问题去学习自动内存管理机制,罗列如下:

什么操作可能导致内存溢出? 有哪些种类的内存溢出? 都是在内存的哪些区域溢出? 垃圾收集有哪些原则? 有哪些垃圾收集算法及其实现? 新生代和老年代的回收策略如何? 各种内存相关的JVM参数是什么意思?

本文章主要总结问题1、问题2和问题3

二、运行时数据区域

Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域,如下图所示

其中虚拟机栈、本地方法栈和程序技术器是线程私有的,方法区和堆是线程共享的.

2.1程序计数器

作用:当前线程所执行的字节码的行号指示器

字节码解释器工作时通过改变它的值来选取下一条需要执行的字节码指令 分支、循环、跳转、异常处理和线程恢复都依赖于它

2.2虚拟机栈

栈的作用:栈用于存储局部变量表、操作数栈、动态链接和方法出口等信息.

其中局部变量表用于存放8种基本数据类型(boolean,byte,char,short,int,float,long,double)和reference类型.

reference类型:

指向对象起始地址的引用指针

指向一个代表对象的句柄

指向一条字节码指令的地址

可抛出两种异常状况

线程请求的栈深度大于虚拟机所允许的栈深度,抛出StackOverflowError异常

当扩展时无法申请到足够的内存时会抛出OutOfMemoryError异常

2.3本地方法栈

与虚拟机栈的作用非常相似.其区别是虚拟机栈执行Java方法服务,而本地方法栈则为虚拟机使用到的Native方法服务

同时也会抛出StackOverflowError和OutOfMemoryError异常

2.4堆

堆的作用:分配所有的对象实例和数组。可以抛出OutOfMemoryError异常。

2.5方法区

方法区的作用:用于存储已被虚拟机加载的类信息(Class)、常量(final修饰)、静态变量(static)和即时编译器编译后的代码(code)

可以抛出OutOfMemoryError异常

2.6运行时常量池

属于方法区的一部分,用于存放编译期生成的各种字面量和符号引用(在以后介绍Class结构会讲到),在类加载后存放到方法区的运行时常量池中。可抛出OutOfMemoryError异常

三、对象访问

主流的两种访问方式:使用句柄和直接指针。(HotSpot虚拟机就是使用直接指针的访问方式)

使用句柄访问

使用直接指针访问

优缺点比较

四、OutOfMemoryError异常

在Java虚拟机规范的描述中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError异常的可能.

下面通过若干实例来验证异常发生的场景.以下代码的开头都注释了执行时所需要设置的虚拟机启动参数,这些参数对实验结果有直接影响,请调试代码的时候不要忽略掉.

4.1Java堆溢出

堆里放的是new出来的对象,所以这部分很简单不断的new对象就可以了,但是为了防止对象new出来之后被GC,所以把对象new出来的对象放到一个List中去即可。为了有更好的效果,可以在运行前,调整堆的参数。

import java.util.ArrayList; import java.util.List; /** * VM Args: -Xms20m -Xms20m - XX:+HeapDumpOnOutOfMemoryError * @author Administrator * */ 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()); } } }

运行结果

java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid3688.hPRof ... Heap dump file created [364376345 bytes in 15.891 secs]

4.2虚拟机栈溢出

在单线程的堆中我们不断的让一个成员变量自增,容纳这个变量的单元无法承受这个变量了,就抛出StackOverflowError了。 可以开尽量多的线程,并在每个线程里调用native的方法,就自然会抛出 OutOfMemoryError了。 /** * VM Args: - Xss64k * @author Administrator * */ 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; } } } 运行结果 Stack length:914 Exception in thread "main" java.lang.StackOverflowError       at JavaVMStackSOF.stackLeak(   JavaVMStackSOF.java:9 )       at JavaVMStackSOF.stackLeak(   JavaVMStackSOF.java:10 )       at JavaVMStackSOF.stackLeak(   JavaVMStackSOF.java:10 )

4.3 方法区溢出

可采用增强Class加载的方式。基本思路是运行时产生大量的类去填满方法区,直到溢出。 借助第三方类库CGLib 直接操作字节码运行,生成大量的动态类。 import java.lang.reflect.Method; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodProxy; /** * VM Args:- XX:PermSize=10m -XX:MaxPermSize=10m * @author Administrator * */ 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() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return proxy.invoke(obj, args); } }); enhancer.create(); } } static class OOMObject{ } } 运行结果 java.lang.OutOfMemoryError: PermGen space at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631) at java.lang.ClassLoader.defineClass(ClassLoader.java:615) ... 8 more

4.4 运行时常量池溢出

可采用String类的intern方法。 import java.util.ArrayList; import java.util.List; /** * VM Args:- XX:PermSize=10m -XX:MaxPermSize=10m * @author Administrator * */ public class RuntimeConstantPoolOOM { public static void main(String[] args) { List<String> list = new ArrayList<String>(); int i = 0; while (true ){ list.add(String. valueOf(i++).intern()); } } } 运行结果 Exception in thread "main" java.lang.OutOfMemoryError: PermGen space       at java.lang.String.intern(   Native Method  )       at RuntimeConstantPoolOOM.main(   RuntimeConstantPoolOOM.java:14   )

五、小结

主要介绍虚拟机里面的内存是如何划分的,哪部分区域、什么样的代码和操作可能导致内存溢出异常。

---------------------------------全文完------------------------------

摘自《深入理解Java虚拟机》