一个看别人文章看久了依然什么都记不住的人决定自己看官方文档(意外的效果还不错
文档地址: Java SE 23 规范,
由于中英文阅读习惯差异,本文会间歇以行内代码
的形式插入原文句子,并且为防止断句错误,会使用 "" 来标记英文原文中作为同一个主体的部分。
本文主要记录第二章《The Structure of the Java Virtual Machine(Java虚拟机的结构)》的 2.5 小节 Run-Time Data Areas 运行时数据区的阅读理解。
关键章节记录【摘自第一章 1.2 虚拟机】:
Chapter 2 :概述 Java 虚拟机架构
Chapter 3:介绍"用 Java 编写的代码如何编译到 Java 虚拟机的指令集中"
Chapter 4 :指定类文件格式class file format
。二进制,与硬件操作系统无关,表示已编译的类和接口
Chapter 5 :指定虚拟机启动start-up
,及类与接口的加载loading
、链接linking
、初始化initialization
Chapter 6 :指定虚拟机的指令集instruction set of the JVM
,按操作码助记符的字母顺序呈现指令instructions in alphabetical order of opcode mnemonics
Chapter 7:前述助记符表
Chapter 17:线程和锁。专家组制定的 Java 内存模型和线程规范。
在第二版 Java 虚拟机规范的 Chapter 8 ,介绍了虚拟机线程与共享主内存的低级交互操作low-levwl actions that explained the interaction of JVM threads with a shared main memory
Chapter 2:Java 虚拟机的结构
链接:docs.oracle.com/javase/spec...
2.1 到 2.4 都是基础的类和数据类型的描述,已经相对熟悉,此处略过。
2.5 开始是 Run-Time Data Areas 运行时数据区,本文主要记录对象。
2.5 Run-Time Areas 运行时数据区
定义:程序执行期间各种运行时数据区域be used during execution of a program
。
有些数据区域是在虚拟机启动时创建,并且在虚拟机终止时销毁。
其他数据区域按线程显示per thread
。
线程数据区per thread
在线程thread
创建时创建,在线程终止时销毁
总结为线程绑定区(
2.5.1 The pc Register PC 寄存器(全中文:程序计数器寄存器)
总结为存放下一步方法指令的地方,当方法是本地 native 的,则寄存内容是 undefined。
Java 虚拟机支持多线程同时运行support many threads of execution at once
。
每个 Java 虚拟机线程都有自己的 pc (程序计数器program counter
)寄存器register
。
在任何时候,每个虚拟机线程都在执行单个方法的代码the code of a single method
,即该线程的当前方法current method
。
如果该方法不是本地native
方法,则 pc 寄存器包含当前正在执行的 Java 虚拟机指令的地址。
如果线程当前执行的方法时本地native
方法,则 pc 寄存器的值是 undefined。
虚拟机的 pc 寄存器足够宽wide
,可以在特定平台保存returnAddress
或本地指针native pointer
。
2.5.2 JVM Stacks 虚拟机栈
每个虚拟机线程都有一个私有的虚拟机栈,与线程同时创建。该栈存储"帧"。
Java 虚拟机的栈类似 C 等传统语言的堆栈:保存局部变量和部分结果,并在方法调用和返回中发挥作用。
虚拟机栈除了推送push
和弹出pop
外从不直接操作,但可能会分配帧。虚拟机栈的内存不必是连续的。
SE23 版本的规范既允许虚拟机栈具有固定大小,也允许根据计算之需要动态扩展与收缩。如果其大小恒定,则可以再创建该栈时单独选择每个虚拟机栈的大小。
可控制初始栈大小,可控制最大值和最小值。
应该对应的就是 JVM 配置里那些 Max/Min 了
常见的相关异常:
StackOverflowError:线程中计算所需的堆栈大于被允许的栈值
OutOfMemoryError:设置了动态扩展栈但服务器已经没有足够的内存、新线程还没创建就死于内存不足
2.5.3 Heap 堆
虚拟机的所有线程共享一个堆has a heap that is shared among all JVM threads
。
堆时运行时数据区,从中分配所有类实例和数组的内存。
堆在虚拟机启动时创建,对象的堆存储由垃圾回收器(GC)回收原文是"自动存储管理系统"
,对象永远不会显式解除分配。虚拟机不假定特定类型的 GC,且可以根据实现者的系统要求选择存储管理技术。
堆既可以是固定大小,也可以根据计算需要扩展。如果不需要更大的堆,也可以收缩堆。堆内存不需要连续。
常见的相关异常:
OutOfMemoryError:计算所需的堆大于自动存储管理系统所能提供的堆
2.5.4 Method Area 方法区
方法区被所有 JVM 线程共享。
其类似于常规语言的编译代码的存储区域,或类似于操作系统进程中的"文本"段The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process
。
存储每个类的结构per-class structures
,比如运行时常量池run-time constant pool
、字段field
和方法数据method data
;
同时存储"方法和构造函数"的代码code for methods and constructors
,包括"类和接口的初始化以及实例初始化"中使用的特殊方法including the special methods useds in class and interface initialization and in instance initialization
。
方法区是在虚拟机启动时创建的,逻辑上是堆的一部分,可以选择不对其垃圾回收或压缩Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it
。
simple implementations 在此应当理解为对方法区的实现,也就是从用户开发实现的角度,可以令其不被回收或压缩。
SE23 的规范既不强制要求方法区域的位置,也不规定"管理已编译代码"的策略。
与前面的堆和栈一样,方法区既可以固定大小,也可以按需动态扩展;如无需更大,则可以缩小,且不需要是连续的。参数是-Xss
。
常见的相关异常:
OutOfMemoryError:方法区中的内存无法满足分配请求时
2.5.5 Run-Time Constant Pool 运行时常量池
是每个类/接口运行时于constant_pool
表中展现的形式,它们存在于类文件中。
包含多种变量,如编译时已知的数字文本numeric literals known at compile-time
、必须在运行时解析的方法和字段引用method and field references that must be resolved at run-time
。
运行时常量池的功能类似于传统编程语言的符号表symbol table
,不过包含的数据范围比典型的符号表更广。
每个运行时常量池都是从虚拟机的方法区分配的,类/接口的运行时常量池是在 Java 虚拟机创建类或接口时构造。
常见的相关异常:
OutOfMemoryError:创建类或接口时,当运行时常量池的构造所需内存多于虚拟机的方法区可用内存
2.5.6 Native Method Stacks 本地方法栈
可以使用常规栈conventional stacks
(俗称"C 栈"C stacks
)来支持本地方法(用 Java 之外的语言编写的方法)。
下面两句英文原文捋不太清楚,扔给 AI 才知道该怎么断句。。
Native method stacks may also be used by the implementation of an interpreter for the Java Virtual Machine's instruction set in a language such as C.
那些使用 C 或其他语言来实现 JVM 指令集的解释器,会用到本地方法栈。
Java Virtual Machine implementations that cannot load native methods and that do not themselves rely on conventional stacks need not supply native method stacks.
如果虚拟机的实现不支持加载本地方法,且其内部机制不依赖于传统的栈结构,那么这样的 JVM 实现就不需要提供本地方法栈。
这部分无论英文原文,还是译文,都提到了 supply native method stacks 提供本地方法栈,第一眼下意识会以为需要在代码里声明什么数据块。但其实这些"提供"行为,都是在 JVM 层面完成的,作为开发者,能做的其实是调用本地方法 ,本地方法可以自定义,只要加上前缀private native int fun1()
。
本地方法栈也和前面的方法区一样,可以设置其大小,参数也是-Xss
。
常见的相关异常:
StackOverflowError:计算所需的本地方法栈大于允许的栈
OutOfMemoryError:动态扩展本地方法栈但无足够内存可用,或已无可供新建线程的内存
总结
相关异常汇总
- StackOverflowError:虚拟机栈、本地方法栈
- OutOfMemoryError:虚拟机栈、本地方法栈、堆、方法区、运行时常量池
配置实例:
假设一个Java应用
MyApp.jar
,希望设置元空间初始大小为256MB,最大大小为1GB,每个线程的堆栈大小为512KB:java -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=1g -Xms512m -Xmx1024m -Xss512k -jar MyApp.jar
各个参数与Java虚拟机(JVM)的不同内存区域的关系如下:
-XX:MetaspaceSize=256m
关联区域:方法区(在JDK 8及以后为元空间MetaSpace)。
说明:此参数设置元空间的初始大小。元空间是方法区在JDK 8及以后版本的实现,用于存储类加载信息、常量、静态变量、即时编译器编译后的代码等数据。当元空间的使用量达到这个初始大小时,可能会触发垃圾收集(GC)以卸载不再需要的类并释放空间。
-XX:MaxMetaspaceSize=1g
关联区域:方法区(在JDK 8及以后为元空间MetaSpace)。
说明:此参数设置元空间的最大可分配空间。当元空间的使用量超过此限制时,JVM将抛出OutOfMemoryError: Metaspace异常。这个参数有助于防止因为类加载信息过多而导致的内存溢出。
-Xss512k
关联区域:虚拟机栈和本地方法栈。
说明:此参数设置每个线程的栈大小(包括Java虚拟机栈和本地方法栈)。栈空间主要用于存储方法调用和本地变量。如果栈空间过小,可能导致栈溢出错误(StackOverflowError);如果栈空间过大,会占用大量的内存资源,导致系统性能下降。因此,合理设置栈大小可以避免栈溢出错误和节约内存资源。
-Xms512m
关联区域:堆
说明:初始堆大小。设置JVM初始堆内存为512MB。这个参数指定了JVM启动时分配的初始堆内存大小。
-Xmx1024m
关联区域:堆
说明:最大堆大小。设置JVM最大堆内存为1024MB(即1GB)。这个参数指定了JVM在运行过程中可以分配的最大堆内存大小。
运行时常量池: 是方法区的一部分,用于存储编译生成的各种字面量和符号引用。在类加载后,这些常量会进入方法区的运行时常量池中。虽然上述参数配置中没有直接设置运行时常量池的大小,但元空间的大小(通过-XX:MetaspaceSize和-XX:MaxMetaspaceSize设置)会间接影响运行时常量池的可用空间。