深入理解JVM - 运行时数据区域

1. 运行时数据区域

注意JVM运行时数据区域与Java内存模型的区别

  • JVM 运行时数据区域:JVM 所管理的内存划分
  • Java内存模型:屏蔽底层硬件和操作系统的区别,在语言级抽象java的内存访问,使得在不同的环境中java一致的内存访问效果

image

2. 程序计数器 Program Counter Register

当前线程所执行的字节码的行号指示器
JVM概念模型中,字节码解释器通过改变PC选取下一条要执行的指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个指示器完成。
线程为OS调度的基本单位,Java的线程模型使用的是内核指称的一对一模型,每个线程都需要有自己的PC,因此PC是线程私有的内存。
若当前线程正在执行Java方法,则PC为正在执行的虚拟机字节码指令地址;若正在执行本地方法(Native),则值未定义(Undefined)。
唯一一个未在《JVM规范》中指定OutOfMemoryError的内存区域

JVM概念模型:有点类似Java内存模型,都是高度的抽象,JVM概念模型则是JVM的抽象,而具体实现不一定根据这个抽象模型实现,但是具有相同的效果/功能。

3. Java 虚拟机栈 Java Virtual Machine Stack

线程私有,生命周期与线程相同
虚拟机栈描述Java方法执行的线程内存模型:一个方法的执行和退出对应一个栈帧(Stack Frame)的入栈和出栈。
栈帧存放着局部变量表操作数栈动态链接方法出口等信息。
通常C/C++中描述的堆内存、栈内存中的栈内存就是这里的虚拟机栈,或者是虚拟机栈中的局部变量表部分。

局部变量表
存放了编译期可知的各种Java虚拟机基本数据类型(8种)、对象引用(对象指针或句柄指针)和returnAddress(指向一条字节码指令的地址)。
这些数据在局部变量表的存储空间以局部变量槽(Slot)表示,64位的long和double占用两个变量槽,其余数据类型占用两个。
局部变量表所需内存空间在编译期完成分配,当进入一个方法时,需要在栈帧中分配多大的局部变量空间是完全确定的。
虚拟机具体使用多大的内存(32、64位?)实现一个slot取决于具体的实现。

栈帧
栈帧中的局部变量表大小编译期确定,那么操作数栈栈是否能完全确定?栈帧需要分配的具体大小是不能确定的吗?按照字节码看,操作数栈需要的空间大小应该也是能在编译期确定的呀?

异常
线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError
如果Java虚拟机栈容量可以动态扩展(取决于具体VM实现,HotSpot不能),当栈扩展时无法申请到足够内存会抛出 OutOfMemoryError
即便HotSpot的虚拟机栈无法动态扩展,但是当申请一个线程的栈内存时就没有没有足够内存也会抛出 OutOfMemoryError

4. 本地方法栈 Native Stack

和虚拟机栈的作用基本相同,为方法服务,不过本地方法栈为本地(Native)方法服务。
《JVM》为对此内存的实现有限制,HotSpot 将虚拟机栈和本地方法栈合二为一实现。
异常
同虚拟机栈,在栈深度溢出和栈扩展失败时分别抛出 StackOverflowErrorOutOfMemoryError

5. Java 堆 Java Heap

虚VM所管理内存最大的一块,线程共享,VM启动时创建,唯一目的是创建对象实例,几乎所有实例对象都在这里分配
即时编译技术的进步,尤其逃逸分析技术的日渐强大,栈上分配、标量替换等优化手段,导致对象可能不在堆中分配。优化后类似C#的struct?
物理上课不连续,但逻辑上必须连续。
课实现为可扩展的、也可实现为固定大小的,主流VM均实现为可扩展的。

异常
若在Java堆中无法完成实例分配,且堆也无法扩展时,VM将抛出 OutOfMemoryError

6. 方法区 Method Area

线程共享、存储已被VM分配的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据
永久代 Permanent Generation
方法区在JDK8前的常用称呼,主要是在之前 HotSpot 使用永久代实现方法区,将垃圾收集器的分代设计扩展至方法区,省去单独的内存管理设计。其他虚拟机没有这个称呼。
JDK7 HotSpot将原永久代的字符串常量、静态变量移出。
JDK8 Hotspot 废弃永久代,使用元空间(metaspace)实现,将JDK7中永久代剩余内容(主要是类型信息)移到元空间。

《JVM规范》对方法区限制很宽松,不需要连续物理内存、可选择固定大小或可扩展、可不实现垃圾收集。
这部分内存回收主要针对常量和类型的卸载。

异常
当方法区无法满足新的内存分配需求,将抛出 OutOfMemoryError

7. 运行时常量池 Runtime Constabnt Pool

方法区一部分。
class文件中除了类版本、字段、方法、接口等描述信息,还有常量池表(Constant Pool Table),存放编译期生成的各种字面量与符号引用,这部分在类加载后放到方法区的运行时常量池中
一般来说,除了保存class文件中的符号引用,也会将由符号引用翻译出来的直接引用也存储在运行时常量池中。

运行时常量池和class常量池的一个区别是动态性,不要求一定是编译时产生,也可在运行时放入,如 String#intern。

异常
方法区一部分,当常量池无法申请到足够内存抛出 OutOfMemoryError

8. 直接内存 Direct Memory

非运行时数据区域一部分,非《JVM规范》定义区域。
DK1.4引入NIO,可使用Native函数库直接分配堆外内存,通过存储在Java堆中的DirectByteBuffer对象引用这块内存,避免Java堆和Native堆中来回复制数据
异常
不受Java堆大小限制,但受本地内存限制,申请内存时无足够内存也会抛出 OutOfMemoryError

9. 扩展概念

  1. CPU的PC
  2. Java内存模型
  3. 基于栈和基于寄存器的虚拟机
  4. 堆和栈的区别
  5. java对象内存布局、引用
  6. 异常级别、全都可以被捕获
  7. String 常量池
  8. 符号引用与直接引用
  9. Java 堆和 Native 堆
posted @ 2022-04-13 21:32  YangDanMua  阅读(47)  评论(0编辑  收藏  举报