第3章 运行时数据区概述及线程
第3章 运行时数据区概述及线程
以下内容是由尚硅谷的宋红康老师整理,在此先感谢尚硅谷的教学课程;在此仅作为个人学学习笔记进行分享;视频教程地址:宋红康JVM:https://www.bilibili.com/video/BV1PJ411n7xZ

1-网络知识总结
1-运行时数据区
1、前言
本节主要讲的是运行时数据区,也就是下图这部分,它是在类加载完成后的阶段
当我们通过前面的:类的加载 --> 验证 --> 准备 --> 解析 --> 初始化,这几个阶段完成后,就会用到执行引擎对我们的类进行使用,同时执行引擎将会使用到我们运行时数据区
也就是大厨做饭,我们把大厨后面的东西(切好的菜,刀,调料),比作是运行时数据区。而厨师可以类比于执行引擎,将通过准备的东西进行制作成精美的菜品
2、运行时数据区结构
2.1、运行时数据区与内存
内存
- 内存是非常重要的系统资源,是硬盘和CPU的中间仓库及桥梁,承载着操作系统和应用程序的实时运行JVM内存布局规定了Java在运行过程中内存申请、分配、管理的策略,保证了JVM的高效稳定运行。
- 不同的JVM对于内存的划分方式和管理机制存在着部分差异。结合JVM虚拟机规范,来探讨一下经典的JVM内存布局。
- 我们通过磁盘或者网络IO得到的数据,都需要先加载到内存中,然后CPU从内存中获取数据进行读取,也就是说内存充当了CPU和磁盘之间的桥梁
运行时数据区的完整图
2.2、线程的内存空间
线程的内存空间
Java虚拟机定义了若干种程序运行期间会使用到的运行时数据区:
- 其中有一些会随着虚拟机启动而创建,随着虚拟机退出而销毁。
- 另外一些则是与线程一一对应的,这些与线程对应的数据区域会随着线程开始和结束而创建和销毁。
灰色的为单独线程私有的,红色的为多个线程共享的。即:
- 线程独有:独立包括程序计数器、栈、本地方法栈
- 线程间共享:堆、堆外内存(永久代或元空间、代码缓存)
关于 Runtime 类的说明
每个JVM只有一个Runtime实例。即为运行时环境,相当于内存结构的中间的那个框框:运行时环境。
3、线程
3.1、JVM 线程
JVM 线程
- 线程是一个程序里的运行单元。JVM允许一个应用有多个线程并行的执行
- 在Hotspot JVM里,每个线程都与操作系统的本地线程直接映射
- 当一个Java线程准备好执行以后,此时一个操作系统的本地线程也同时创建。Java线程执行终止后,本地线程也会回收
- 操作系统负责将线程安排调度到任何一个可用的CPU上。一旦本地线程初始化成功,它就会调用Java线程中的run()方法
- 如果一个线程抛异常,并且该线程时进程中最后一个守护线程,那么进程将停止
3.2、JVM 系统线程
JVM系统线程
- 如果你使用jconsole或者是任何一个调试工具,都能看到在后台有许多线程在运行。
- 这些后台线程不包括调用public static void main(String [])的main线程以及所有这个main线程自己创建的线程。
这些主要的后台系统线程在Hotspot JVM里主要是以下几个:
- 虚拟机线程:这种线程的操作是需要JVM达到安全点才会出现。这些操作必须在不同的线程中发生的原因是他们都需要JVM达到安全点,这样堆才不会变化。这种线程的执行类型括"stop-the-world"的垃圾收集,线程栈收集,线程挂起以及偏向锁撤销
- 周期任务线程:这种线程是时间周期事件的体现(比如中断),他们一般用于周期性操作的调度执行
- GC线程:这种线程对在JVM里不同种类的垃圾收集行为提供了支持
- 编译线程:这种线程在运行时会将字节码编译成到本地代码
- 信号调度线程:这种线程接收信号并发送给JVM,在它内部通过调用适当的方法进行处理
2-个人知识总结
1-当我谈RuntimeArea

- 1)JVM有几块内存分区
- 2)每个分区分别做什么
- 3)当一个类被加载时,RuntimeArea做了什么?
2-厨房做菜类比RuntimeArea
在厨房做菜的例子中,我们可以将HotSpot JVM的运行时数据区各个结构比喻为不同的准备区域和操作台:
-
程序计数器(Program Counter):
- 类比为食谱书:程序计数器类似于食谱书上的页码,指示当前厨师正在执行的步骤。它是线程私有的,每个线程都有自己的食谱书。
-
Java虚拟机栈(JVM Stack):
- 类比为操作台:JVM栈就像是厨房里的操作台,用于处理食材和烹饪。每当厨师开始一个新的任务(方法调用),就会在操作台上放置一个新的准备空间(栈帧),直到任务完成。
-
堆(Heap):
- 类比为冰箱或食材库存:堆是用来存放对象实例的地方,就像是冰箱里存放食材的空间。厨师在做菜时需要从冰箱里取出食材(对象),使用完后再放回冰箱。
-
方法区(Metaspace):
- 类比为食材清单或菜谱库:方法区存储类的元数据信息,比如菜谱上的食材清单和烹饪指导。厨师在做菜时需要参考这些信息。
-
运行时常量池(Runtime Constant Pool):
- 类比为备忘录或特定食材清单:运行时常量池包含每个类的常量、字段和方法信息,就像特定菜肴的备忘录和所需食材清单。
-
本地方法栈(Native Method Stack):
-
类比为特定的烹饪工具和技术:本地方法栈可以比作厨房里用于特定食材或烹饪技术的特殊工具或设备。它与JVM栈(操作台)一样,但专门用于处理本地方法,这些方法通常由本地语言(如C或C++)编写,与Java代码直接交互。
-
当谈论HotSpot JVM的运行时数据区时,还有一个重要的部分是本地方法栈(Native Method Stack),它与Java虚拟机栈类似,但用于执行本地方法(Native Method)
在这个比喻中,本地方法栈充当了一个额外的准备区域,专门为处理需要底层本地资源或操作系统特定功能的任务而设计。它与JVM栈紧密合作,共同支持Java程序执行中对本地方法的调用和处理。 -
每个结构在厨房中的角色类比帮助理解HotSpot JVM在执行Java程序时如何管理内存和执行过程中所需的数据。
-
3-JVM中当一个类被加载时,RuntimeArea做了什么?
1-JVM中当一个类被加载时,RuntimeArea做了什么?
当一个类被加载到JVM时,Runtime Area(运行时数据区)会进行以下操作:
-
内存分配:为该类的静态变量分配内存空间,并初始化这些变量。静态变量通常存放在方法区(或者元空间),在加载阶段的准备步骤中完成内存分配和默认初始化。
-
类信息存储:将类的元信息存储在方法区(或者元空间)。这些信息包括类的结构信息(如类的名称、字段信息、方法信息)、运行时常量池、静态变量等。方法区(或者元空间)用于存储类的字节码、运行时常量池和符号引用等数据。
-
类加载器引用:将类加载器的引用存储在类的元信息中,用于唯一标识和定位类加载器,支持类的动态加载和链接。
-
初始化:执行类的初始化阶段,包括执行类构造器
<clinit>()方法(静态构造器),静态变量会被初始化为指定的初始值或者程序中指定的值。这个阶段是类加载过程的最后一步,确保类的静态初始化在多线程环境中正确执行。
总之,当一个类被加载到JVM时,Runtime Area负责分配内存空间、存储类的元信息和静态变量信息,并执行必要的初始化操作,以便类可以被正确使用和执行。
2-JVM中当一个类被加载时,RuntimeArea做了什么?
当一个类被加载到Java虚拟机(JVM)中时,Runtime Data Area(运行时数据区)的各个部分会按照一定的顺序参与类加载过程。下面是按照大致发生的顺序来说明每个区域的活动:
-
类加载准备阶段:
- 方法区(或元空间,自JDK 8起):在这个阶段,JVM会读取类文件,解析其中的类型信息、常量池、字段、方法描述等,并将这些元数据存储到方法区或元空间中。同时,为静态变量分配内存并初始化为默认值(基本类型为0/false,引用类型为null)。
-
类加载验证阶段:
- 此阶段主要由JVM负责执行,确保类文件的格式正确且符合Java语言规范,虽然它不直接对应于Runtime Data Area的特定区域,但验证过程确保了即将加载的类安全可靠,为后续的准备、解析、初始化等步骤打下基础。
-
类加载解析阶段:
- 方法区(或元空间):解析过程会将常量池中的符号引用转为直接引用,这些直接引用指向方法区中的类、字段、方法等实体。这一步骤进一步完善了类在方法区中的存储结构。
-
类加载初始化阶段:
- 程序计数器:在类的初始化阶段(如执行
<clinit>方法),程序计数器会跟踪执行字节码指令的位置。 - Java虚拟机栈:如果类的初始化涉及到任何方法(包括静态初始化块)的调用,相关的方法栈帧会被压入到当前线程的Java虚拟机栈中,用于存储局部变量、操作数栈等。
- 本地方法栈:如果有 native 方法在初始化过程中被调用,本地方法栈将用于支持这些方法的执行。
- 堆:类的实例(如果在初始化时创建)以及非final的静态变量的值(如果在
<clinit>中有显式赋值)存储在堆上。 - 方法区(或元空间):静态最终变量(
final static,且编译器可确定其值的)的值可能直接存储在方法区或常量池中,而不需要在堆上分配空间。
- 程序计数器:在类的初始化阶段(如执行
总结来说,当类被加载时,Runtime Data Area的各个区域协同工作,从方法区(或元空间)存储类的元数据开始,到程序计数器、Java虚拟机栈、本地方法栈参与类的初始化过程,再到堆上可能的实例和静态变量分配,整个流程确保了类被正确且安全地加载到JVM中。
3-JVM中当一个类被加载时,RuntimeArea做了什么?
当一个类被加载到Java虚拟机(JVM)中时,Runtime Data Area(运行时数据区)扮演着关键角色,参与了类加载的各个阶段,特别是类的存储和执行。以下是运行时数据区各部分在类加载过程中涉及的主要活动:
-
程序计数器(Program Counter Register):
- 在类加载过程中,程序计数器主要跟踪当前线程执行的字节码指令。对于类加载本身,它并不直接参与类数据的存储,但在类的静态初始化(
<clinit>方法)执行时,它会记录下一条待执行的指令位置。
- 在类加载过程中,程序计数器主要跟踪当前线程执行的字节码指令。对于类加载本身,它并不直接参与类数据的存储,但在类的静态初始化(
-
Java虚拟机栈(Java Virtual Machine Stack):
- 类加载器在加载类时,如果涉及到类的静态方法或构造器的调用,相应的栈帧会被压入到当前线程的Java虚拟机栈中。这些栈帧包含了方法的局部变量表、操作数栈等信息,用于类加载过程中的方法执行。
-
本地方法栈(Native Method Stack):
- 如果类加载过程中涉及到native方法的调用(例如通过JNI,Java Native Interface),那么本地方法栈将用于支持这些native方法的执行。
-
堆(Heap):
- 类的实例以及类的静态变量(除了被标记为
final和编译器能够静态确定值的static final变量,这些通常存储在常量池中)存储在堆上。类加载后,其对应的类对象(如Class对象)也会存储在堆中,包括类的元数据信息、静态字段等。
- 类的实例以及类的静态变量(除了被标记为
-
方法区(Method Area)/元空间(Metaspace):
- 在Java 8及之后的版本中,原本属于方法区的部分功能(如类的元数据、常量池等)被移至元空间。在类加载时,类的结构信息(如类名、访问修饰符、常量池、字段、方法信息等)会被加载到方法区或元空间中。此外,静态变量也在这一区域分配内存(尽管静态常量可能存储在常量池中)。
总结来说,当一个类被加载时,Runtime Data Area中的方法区(或元空间)用于存储类的结构信息和静态变量,堆用于存储类的对象实例和部分静态变量,而虚拟机栈和本地方法栈则在类的初始化和方法执行过程中发挥作用。程序计数器则跟踪当前执行的指令位置,间接参与类的初始化过程。
