JAVA内存区域与内存溢出异常
JAVA与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来。
1、JVM内部图解
2、JAVA运行数据区域组件
JAVA虚拟机在执行JAVA程序的过程中会把它所管理的内存分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁时间,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。JAVA虚拟机所管理的内存包括以下几个运行时数据区域:JAVA栈,堆,方法区,PC寄存器,本地方法栈
3、概述运行数据区域
将从数据区域的所有者,生命周期,异常,作用几个方面分析
PC寄存器:是线程私有的,它的创建与销毁依赖线程的启动和结束,是在JVM里唯一不会抛出OOM(OutOfMemoryError)异常的区域,作用是当前线程所执行的字节码行号指示器。
堆:是一个JVM实例所有线程共享的,它的创建与销毁依赖JVM的启动和结束,会抛出OutOfMemoryError异常,作用是保存对象和数组。
方法区:是一个JVM实例所有线程共享的,它的创建与销毁依赖JVM的启动和结束,会抛出OutOfMemoryError异常,作用是保存常量,静态变量,类的信息,编译器编译后的代码等。
常量池:是方法区的一部分,会抛出OutOfMemoryError异常,用于保存编译器生成的各种字面量和符号引用。
JAVA栈:是线程私有的,它的创建与销毁依赖线程的启动和结束,会抛出StackOverFlowError和OutOfMemoryError异常,作用是保存局部变量,操作栈,动态链接,方法出口等信息。
本地方法栈:是线程私有的,它的创建与销毁依赖线程的启动和结束,会抛出StackOverFlowError和OutOfMemoryError异常,作用和JAVA栈类似,只不过它是为执行本地方法服务的。
4、对象访问
对象访问在Java语言中无处不在,即使最简单的访问,也会涉及到JAVAh栈,堆,方法区这三个最重要内存区域的关联关系:例:Object obj = new Object();
Object obj会在JAVA栈中以reference(引用类型)类型数据出现,new Object()保存在堆中,还需要在方法区查到此对象类型数据(对象类型,父类,实现的接口,方法等)的地址信息。
由于reference类型在JAVA虚拟机规范里面只规定了指向对象的引用,并没有定义这个引用应该通过哪种方式去定位,以及访问到JAVA堆中的对象的具体位置。因此不同虚拟机实现对象的访问方式会有所不同,主流的访问方式有两种:使用句柄和直接指针。
A:使用句柄的方法访问,JAVA堆中将会划分出一块内存来作为句柄池,reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据和对象类型数据各自的具体地址信息,如下图所示:
B:直接指针访问方式,reference存放的是对象地址,如下图所示:
两种方式的比较:每种方式都有其利弊,根据我们的实际需要选择
A:最大的好处在于reference存储稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不被修改。
B:最大好处在于速度更快,节省了一次指针定位的时间开销。
5、JAVA栈和本地方法栈抛出异常
OutOfMemoryError:是虚拟机在拓展栈时无法申请到足够的内存空间
StackOverflowError:是线程请求的栈深度大于虚拟机允许的最大深度
6、详解运行数据区域
JAVA栈:JAVA栈描述的方法执行的内存模型:每个方法被执行的时候都会创建一个栈帧用于存放局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用直至完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。局部变量表的存放的是在编译期可知的基本数据类型和引用数据类型,它的大小是编译期完成分配的,long和double占两个局部变量空间。栈遵循:先进后出的原则。JAVA栈结构如下图所示:
堆:堆是JAVA虚拟机所管理的内存中最大的一块 ,处于物理上不连续的内存空间中,只要是逻辑上是连续的即可,它是垃圾收集器管理的主要区域。
堆可以分为:老年代区和新生代区(Eden,From Survivor,To Survivor)
堆的结构如下图所示:
PC寄存器:是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支,循环,跳转,异常处理,线程恢复等基础功能都需要依赖这个计数器来完成。对于非本地方法,这个区域记录的是正在执行的VM原语的地址,如果正在执行的是Natvie方法,这个区域则为空(undefined)。
方法区:方法区中存放了每个Class的结构信息,包括常量池、字段描述、方法描述等等。VM Space描述中对这个区域的限制非常宽松,除了和Java堆一样不需要连续的内存,也可以选择固定大小或者可扩展外,甚至可以选择不实现垃圾收集。相对来说,垃圾收集行为在这个区域是相对比较少发生的,但并不是某些描述那样永久代不会发生GC(至少对当前主流的商业JVM实现来说是如此),这里的GC主要是对常量池的回收和对类的卸载,虽然回收的“成绩”一般也比较差强人意,尤其是类卸载,条件相当苛刻。
常量池:Class文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量表(constant_pool table),用于存放编译期已可知的常量,这部分内容将在类加载后进入方法区(永久代)存放。但是Java语言并不要求常量一定只有编译期预置入Class的常量表的内容才能进入方法区常量池,运行期间也可将新内容放入常量池(最典型的String.intern()方法)。