基于
java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
Java VM 内存模型分为线程私有和共享数据区两大类。
线程私有的包括:
- 程序计数器
- 虚拟机栈
- 本地方法栈
共享数据区(所有线程共享的区域)包括:
- GC 堆
- 方法区
程序计数器
程序计数器指当前线程所执行的字节码的行号指示器。每个线程都有自己计数器,是私有内存空间,该区域是整个内存中较小的一块。
当线程正在执行一个Java方法时,PC计数器记录的是正在执行的虚拟机字节码的地址;当线程正在执行的一个Native方法时,PC计数器则为空(Undefined)。
此内存区域是唯一一个在Java虚拟机规范中没有规定任何 OutOfMemoryError 情况的区域。
虚拟机栈
Java 虚拟机栈的生命周期与线程相同,是Java方法执行的内存模型。每个方法(不包含native方法)执行的同时都会创建一个栈帧结构,方法执行过程,对应着虚拟机栈帧的入栈到出栈的过程。
栈帧(Stack Frame)结构
局部变量表 (locals大小,编译期确定),一组变量存储空间, 容量以 slot 为最小单位。其中64位长度的 long 和 double 类型的数据会占用2个局部变量空间,其余的数据类型只占用一个。
操作数栈(stack大小,编译期确定),操作数栈元素的数据类型必须与字节码指令序列严格匹配
对于以上代码:
需要执行 x + y
,所以栈深度为2。
x,y,z以及this需要4个局部变量表中的slot。
istore_0 –> this
istore_1 –> x
istore_2 –> y
istore_3 –> z
动态连接, 指向运行时常量池中该栈帧所属方法的引用,为了动态连接使用。
方法返回地址
正常退出,执行引擎遇到方法返回的字节码,将返回值传递给调用者
异常退出,遇到Exception,并且方法未捕捉异常,那么不会有任何返回值。
额外附加信息,虚拟机规范没有明确规定,由具体虚拟机实现。
Java虚拟机规范规定该区域有两种异常:
- *Error:当线程请求栈深度超出虚拟机栈所允许的深度时抛出
- OutOfMemoryError:当Java虚拟机进行动态扩展无法申请足够内存时抛出
本地方法栈
本地方法栈为虚拟机使用到的 Native 方法提供服务,而前面讲的虚拟机栈则为 Java 方法提供服务。
有些虚拟机的实现直接把本地方法栈和虚拟机栈合二为一,比如非常典型的 HotSpot 虚拟机。
异常(Exception):Java虚拟机规范规定该区域可抛出*Error 和 OutOfMemoryError。
GC 堆
方法区
存储类的元数据的区域。
- 栈帧中保存的对象的引用,指向堆区的对象。
- 对象头中保存类的元数据(方法区中)的引用
此处的描述是针对 JDK7 以前,JDK7 及以后静态变量存储在 Class 对象中。
毕竟 Class 对象在JVM虚拟机中只存在一个,满足静态变量的语义。
根据虚拟机的不同,方法区会有不同的实现:永久代(JDK7以前)以及MetaSpace。(不知道可不可以这么描述?)
异常(Exception):Java虚拟机规范规定该区域可抛出OutOfMemoryError。
运行时常量池
运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。这部分内容将在类加载后进入方法区的运行时常量池。
字面量:与Java语言层面的常量概念相近,包含文本字符串、声明为final的常量值等。
符号引用:编译语言层面的概念,包括以下3类:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
以上摘自周志明的深入理解Java虚拟机(第2版)
是以 JDK7 为宿主环境。
在 JDK7 以后 String.intern()
发生了些许变化。
由于字符串常量 保存在 String Literal Pool
(HotSpot实现为StringTable),此处保存的也仅仅是字符串对象的引用,真正的 String 类型的对象保存在堆中。
由 String.intern()
运行时动态产生的驻留字符串对象是不是就和运行时常量池没关系了呢?