一、运行时数据区域
Java虚拟机在执行java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的事件,有的区域随着虚拟机进程的启动而存在,有些区域则依赖用户线程的启动和结束而建立和销毁。Java虚拟机所管理的内存将会包括以下几个运行时数据区域。

1.1 程序计数器
线程私有
。可以看作是当前线程所执行字节码的行号指示器。如果线程所执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码的地址;如果正在执行的是Native方法,这个计数器值为空。此区域是唯一一个没有规定任何和OutOfMemoryError情况的区域。
Java多线程是通过轮流切换并分配处理器执行时间的方式I来实现的,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器,各个线程之间互不影响,独立存储。
1.2 Java虚拟机栈
线程私有
。生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应这一个栈帧在虚拟机栈中入栈到出栈的过程。
Java虚拟机规范中,对这个区域规定了两种异常情况:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈可动态扩展,扩展时无法申请到足够内存,就会抛出OutOfMemeryError 异常。
1.3 本地方法栈
与虚拟机栈发挥的作用非常相似,只不过是为虚拟机使用到的Native方法服务的。虚拟机规范并没有对本地方法使用的语言、使用方式、数据结构强制规定,虚拟机自由实现,有的虚拟机将本地方法栈 和 虚拟机栈合二为一。
1.4 Java堆
线程共享
。虚拟机启动时创建,存放对象实例。Java堆是垃圾收集器管理的主要区域。从内存回收角度看,由于现在收集器基本都采用 分代收集算法,因此Java堆可以细分为:新生代和老年代;再细致点有Eden空间、From Survivor 空间、 To Survivor 空间等。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemeryError异常。
1.5 方法区
线程共享
。存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。这块区域的内存回收目标主要是针对常量池的回收和对类型的卸载。当方法区无法满足内存分配需要时,将抛出OutOfMemoryError异常。
1.6 运行时常量池
是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池,用于存放在编译器生成的各种字面量和符号引用,这部分内存将在类加载后进入到方法区的运行时常量池中存放。
二、对象的创建
虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过,如果没有,那必须先执行相应的类加载过程。类加载通过后,接下来虚拟机将为新生对象分配内存,对象所需要的内存大小在类加载完后便可完全确定。完成内存分配后,虚拟机需要将分配到的内存空间都初始化为零值,这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用。接下来虚拟机要为对象进行必要的设置,例如这个对象属于哪个类的实例,如何才能找到类的元数据信息,对象的哈希值、对象的GC分代年龄信息等,这些信息存放在对象头之中。紧接着执行<init>方法,执行对象的构造器方法。
三、对象的内存分布
HotSpot虚拟机中,对象在内存中存储的布局可分为3块区域:对象头、实例数据 和 对其填充。对象头一部分用于存储对象自身的运行时数据,如哈希码、GC分代年龄等;另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个确定这个对象是哪个类的实例,当然并不是所有虚拟机实现都必须在对象数据上保留类型指针,换句话说,查找对象的元数据并不一定要经过对象本身。 HotSpot VM 要求对象起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍,因此在对象没有对齐的时候,需要通过对齐填充来补全。
四、对象的访问定位
引用类型通过何种方式定位、访问堆中的对象的具体位置,目前主流的访问方式有 使用句柄
和 直接指针
两种。
如果使用句柄访问的话,那么Java堆中将会划分出一块内存来作为句柄池,reference中存储的是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息。(优势:reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中农的实例数据指针,而reference本身不需要修改)
如果使用直接指针访问,那么Java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息,而reference中存储的直接就是对象地址。(优势:速度更快,节省了一次指针定位的时间开销。)
网友评论