本章主要内容是 运行时数据区、HotSpot虚拟机对象、对象访问定位等。

通过上图,我们可以看到 运行时数据区 包括:程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区等。
其中,在 程序计数器、Java虚拟机栈、本地方法栈 这三个区域中每个线程有一个专属的区域,线程和线程之间互不干扰,独立存储,这样的内存区域被称为 线程私有 内存。而 Java堆、方法区 是 所有线程共享的内存区域。
这里我们有一个问题:
我们为啥要记录这个?
Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间来实现的。所以,在任何一个时间点上,一个处理器(或者是多核处理器上的一个内核)只会执行一个线程中的字节码指令。假设 T1、T2 两个线程轮流在一个处理器中执行。当 T1 切换到 T2 再由 T2 切换回 T1 的时候,字节码解释器需要知道当时 T1 执行到哪里了,程序计数器此时就派上用场了,因为 Ta 记录了当时 T1 线程当时(T1 切换到 T2 的时候)的状态。
我们经常会听到别人说 堆内存(Heap)和 栈内存(Stack)。这里的栈内存(Stack)指的就是 Java 虚拟机栈,再准确点来说,应该是 Java 虚拟机栈中的局部变量表。
线程私有 的内存
与 Java虚拟机栈 的作用类似。它们之间的区别在于:
虚拟机栈是为了 虚拟机执行 Java方法(字节码) 而服务的,而本地方法栈是为了 虚拟机执行 Native方法 而服务的。
虚拟机规范并未对本地方法栈的实现有什么强制规定,各虚拟机可以自由的实现 Ta。甚至像 Sun HotSpot 将虚拟机栈和本地方法栈合二为一。
与虚拟机栈相同,此区域也会抛出 StackOverflowError 和 OOM。
被所有线程共享的一块儿内存区域。
该区域是在虚拟机启动的时候创建。
唯一的目的就是用来存放对象实例,几乎所有对象实例都在这里分配内存。
这里我们想一个问题:
是所有的对象实例都会在堆上分配内存吗?
答案是:并不是。虽然 Java虚拟机规范 中的描述是:所有的对象实例以及数组都要在堆上分配。但随着 JIT编译器 的发展与逃逸分析技术的逐渐成熟,栈上分配、标量优化技术 将会导致一些微妙的变化,所有对象都分配在堆上,也变的不那么“绝对”了。
Java 虚拟机规范的规定,堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的就行,和磁盘空间一样。
如果堆中没有内存空间分配给对象实例了,并且也无法再扩展时,将会抛出去 OOM 异常。
对于 方法区 与 永生代 的关系,大家可以移步到我的另外一篇Blog(JVM HotSpot 之 内存结构演进过程)去进行了解。
对象的大小在类加载完成的时候就可以确定了。
为对象分配内存的方式,主要有以下两种:
| 名称 | 描述 |
|---|---|
| 指针碰撞(Dump the Pointer) | 如果堆中的内存是 绝对规整的,所有用过的内存都放在一边,空闲的内存放在另外一边,中间放一个指针作为分界点的指示器,分配内存的时候就是把指针向空闲空间的方向移动一段儿与对象大小相等的距离。 |
| 空闲列表(Free List) | 如果堆中的内存 不是规整 的(已使用的内存和空闲的内存交错),此时虚拟机维护一个列表,用来记录哪些内存块是可用的(空闲的),在给对象分配内存的时候从列表中找到一块儿足够大的空间划分给该对象实例,并更新列表上的记录。 |
对象的内存布局,分为三块区域:对象头(Header)、实例数据(Instance Data)、对齐填充(Paddding)。对象的大小必须是8字节的整数倍,当 实例数据(Instance Data) 部分没有对齐的时候,就需要 对齐填充(Paddding) 来补全。
Java 程序是通过 栈 中的 reference 数据来操作堆中的具体对象。目前主流有两种方式:句柄 和 指针。

通过上图,我们可以总结以下两点:

通过上图,我们可以总结以下两点:
上一篇:《深入理解JAVA虚拟机(第2版)》—— 学习笔记1
下一篇:《深入理解JAVA虚拟机(第2版)》—— 学习笔记3