一、运行时数据区
Java虚拟机在执行Java程序的过程中会把虚拟机所管理的内存划分为若干个不同的数据区域。
1.1 程序计数器
1.2 Java虚拟机栈(1)可理解为当前线程所执行的字节码(.class文件)行号(地址)指示器。通过改变计数器的值选取下一条需要执行的字节码指令。
(2)一个处理器的一个核在任何一个确定的时刻都只会执行一条线程中的指令,为了线程切换后能恢复到正确的执行位置,每条线程都需要有一个独立的程序计数器。
(3)此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemory Error 情况的区域。
(1)描述Java方法执行的内存模型。
(2)每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息;方法的执行——栈帧在虚拟机栈中从入栈到出栈。
(3)局部变量表存放了编译期可知8种基本数据类型、对象引用和returnAddress类型 。
(4)在进入一个方法时需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
(5)Java虚拟机对该区域规定了两种异常:StackOverflowError异常 (线程请求的栈深度大于虚拟机所允许的深度 ),OutOfMemoryError 异常(虚拟机栈可以动态扩展 时无法申请到足够的内存 )
1.3 本地方法栈
与虚拟机栈类似,只不过本地方法栈是为虚拟机执行使用到的Native方法服务的,会抛出相同的异常。
1.4 Java堆
(1)对大多数应用而言,Java堆是Java虚拟机所管理的内存中最大的一块 。
(2)此内存区域的唯一目的就是存放对象实例 ,几乎所有的对象实例以及数组都在这里分配内存(编译优化可能导致栈上分配)。
(3)Java堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆” 。
(4)Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可 ,一般可通过通过-Xmx和-Xms控制扩展。
(5)会抛出OutOfMemoryError异常(在堆中没有内存完成实例分配,并且堆也无法再扩展时 )。
1.5 方法区
(1)存储已被虚拟机加载的类信息、 常量、 静态变量、 即时编译器编译后的代码等数据。
(2)会抛出OutOfMemoryError异常 (方法区无法满足内存分配需求时 )。
(3)运行时常量池:存放编译期生成的各种字面量和符号引用,并非预置入Class文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量放入池中 。当常量池无法再申请到内存时会抛出OutOfMemoryError异常 。
1.6 直接内存
(1)并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。
(2)NIO类引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存 ,能在一些场景中显著提高性能。
(3)各个内存区域总和大于物理内存限制会导致动态扩展时出现OutOfMemoryError异常。
二、HotSpot虚拟机对象
下面讨论HotSpot虚拟机在Java堆中对象分配、 布局和访问的全过程。
2.1 对象的创建
(1)指针碰撞:内存是绝对规整的,所有用过的内存都放在一边,空闲的内存放在另一边,中间放着一个指针作为分界点的指示器,所分配内存就仅仅是把那个指针向空闲空间那边挪动一段与对象大小相等的距离。
(2)空闲列表:虚拟机维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
(3)选择哪种分配方式由Java堆是否规整决定,而Java堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
(4)对象初始化后把对象引用入栈,继续下一条指令。
2.2 对象在内存中的布局
(1)对象在内存中存储的布局可以分为3块区域:对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。
(2)对象头包括两部分信息(HotSpot),大小正好是8字节的倍数(1倍或者2倍):
(3)实例数据第一部分称为"Mark Word",用于存储对象自身的运行时数据,如哈希码 、GC分代年龄等。32位或64位(与虚拟机位数一致)。
第二部分是类型指针:即对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。
第三部分:若对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据 。
实例数据部分是 对象真正存储的有效信息,也是在程序代码中所定义的各种类型的字段内容。 无论是从父类继承下来的,还是在子类中定义的,都需要记录起来。
(4)对其填充
并不是必然存在的,也没有特别的含义,它仅仅起着占位符的作用。Htospot 对象的大小必须是8字节的整数倍 。
2.3 对象的访问定位
如何通过引用访问对象依赖于虚拟机实现,目前主流的访问方式有使用句柄和直接指针两种 。
(1)使用句柄访问对象
在Java堆中划分出一块内存作为句柄池,引用中存储对象的句柄地址,句柄中包含了对象实例数据与类型数据各自的具体地址信息 。
图片来源于:《深入理解Java虚拟机》
(2)使用指针访问对象(Hotspot)
引用中存储对象的直接地址,Java堆中存放对象类型数据。
图片来源于:《深入理解Java虚拟机》