一、 运行数据区域划分
各个数据区域功能如下:
1. 程序计数器:
较小的一块内存空间,可以看做是当前线程所执行的字节码的行号指示器,每条线程都有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,这类内存区域称为“线程私有”的内存。
如果线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;
如果正在执行的是Native方法,这个计数器值则为空;
此内存区域是唯一一个在JAVA虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2. 栈:
线程私有,生命周期与线程相同,栈描述的是Java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表:
存放编译期可知的各种基本数据类型(boolean,int,float,double,short,long,char,byte),对象引用(reference)
局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,方法运行期间不会改变局部变量表的大小。
Java虚拟机规范中对该区域规定了两种异常情况:
a)如线程请求的深度大于虚拟机所允许的深度,抛出*Error异常。
b)虚拟机栈动态扩展无法申请到足够的内存时,抛出OutOfMemoryError异常。
3. 本地方法栈:
与虚拟机栈的区别在于本地方法栈是虚拟机使用到的Native方法,虚拟机规范对本地方法栈中的方法是用语言、使用方式与数据结构没强制规定,因此虚拟机可以*实现,如Sun HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一。
4. Java堆
所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区的唯一目的就是存放对象实例,所有的对象实例及数组都在堆上分配。
还可以分为新生代及老年代(再细致点可分为Eden空间,From Survior空间,To Survior空间),用于垃圾收集。
Java虚拟机规范中对该区域规定了OutOfMemoryError异常:如果堆中没有内存完成实例分配,并且堆无法再扩展则抛出OutOfMemoryError异常。
5. 方法区
所有线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
Java虚拟机对这个区域的限制非常宽松,处理和Java对一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集。
Java虚拟机规范中对该区域规定了OutOfMemoryError异常: 如果方法区的内存空间不能满足内存分配请求,那Java虚拟机将抛出一个OutOfMemoryError异常。
6. 运行时常量池
方法区的一部分,用于存放编译期生成的各种字面常量和符号引用,这部分内容在类加载后存放到方法区的常量池中。
Java虚拟机规范中对该区域规定了OutOfMemoryError异常: 当常量池无法申请到内存时抛出OutOfMemoryError异常。
7. 直接内存
直接内存并不是虚拟机运行时数据区域的一部分,也非Java虚拟规范中定义的内存区域,但这部分内存也被频繁使用,并且可能导致OutOfMemoryError异常出现。Java虚拟机需要根据实际内存的大小来设置-Xmx等参数信息,如果忽略了直接内存,使得各个内存区域的总和大于物理内存限制,从而导致动态扩展时抛出OutOfMemoryError异常。
二、堆与栈
堆和栈是程序运行的关键,其间的关系有必要理清楚,两者如下图所示:
1. 堆:
所有线程共享,堆中只负责存储对象信息。
2. 栈:
在Java中每个线程都会有一个相应的线程栈与之对应(因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈),栈是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关的信息,包括局部变量、程序运行状态、方法返回值等;
3. 总结:
栈是运行时的单位,而堆是存储的单位。
三、JAVA对象的大小
在Java中,一个空Object对象的大小是8byte,这个大小只是保存堆中一个没有任何属性的对象的大小。看下面语句:
Object a = new Object();
这样在程序中创建了一个对象a,它所占的空间为:4byte+8byte。4byte是栈中保存引用所需的空间,8byte是Java堆中对象的信息所占的空间。
因为所有的Java非基本类型的对象都需要默认继承Object对象,因此不论什么样的Java对象,其大小都必须是大于8byte。
再如创建如下一个对象:
Class NewObject {
int count;
boolean flag;
Object ob;
}
其大小为:空对象大小(8byte)+int大小(4byte)+Boolean大小(1byte)+空Object引用的大小 (4byte)=17byte。
但Java在对对象内存分配时都是以8的整数倍来分,因此大于17byte的最接近8的整数倍的是24,因此此对象的大小为24byte。