1.程序计数器
程序计数器(Program Counter Register)是一块较小的内存空间,它可以看做是
当前线程所执行的字节码的行号指示器。
为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各条线程之间计数器互不影响,独立存储,我们称这类内存区域为“线程私有”的内存。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
2.Java虚拟机栈
Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于
存储局部变量表、操作数栈、动态链接、方法出口等信息。
其中
局部变量表存放了编译器可知的各种基本数据类型(boolean,byte,char,short,int,float,long,double)、对象引用(reference类型,它不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)和returnAddress类型(指向了一条字节码指令的地址)
3.本地方法栈
本地方法栈(Native Method Stack)与虚拟机栈所发挥的作用是非常相似的,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。
有的虚拟机(比如Sun HotSpot虚拟机)直接就把本地方法栈和虚拟机栈合二为一。
4.Java堆
Java堆是被所有编程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是
存放对象实例。
5.方法区
方法区(Method Area)与Java堆一样,是各个编程共享的内存区域,它用于
存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
6.运行时常量池
运行时常量池(Runtime Constant Pool)是方法区的一部分,用于存放
编译期生成的各种字面量和符号引用。
一、对象的创建
虚拟机遇到一条new指令时,首先将去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那必须先执行相应的类加载过程。
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。(指针碰撞)(空闲列表)
二、对象的内存布局
在HotSpot虚拟机中,对象在内存存储的布局可以分为3块区域:对象头、实例数据(Instance Data)、对象填充(Padding)。
对象头包含两部分信息,第一部分
用于存储对象自身的运行时数据,如(哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等)
第二部分
是类型指针,即对象指向它的类数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
(如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据)
实例数据部分是对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。
分对齐填充并不是必然存在的,它仅仅起这占位符的作用。由于HotSpot VM的自动内存管理系统要求对象起始地址
必须是8字节的整数倍。
三、对象的访问定位
Java程序需要通过栈上的reference数据来操作堆上的具体对象。目前主流的访问方式有
使用句柄和
直接指针两种。
1)如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的信息就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。
2)如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。
OutOfMemoryError异常实战《深入理解java虚拟机》----P50