JVM规范系列之运行时数据区域
Posted 码神手记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JVM规范系列之运行时数据区域相关的知识,希望对你有一定的参考价值。
码神手记-资深攻城狮的私房笔记,微信公众平台/知乎/头条同步。
动动小手,点个关注!
JVM规范系列之运行时数据区域
JVM规范定义了程序运行期间使用的多种数据区域,即运行时数据区域。我们经常遇到的一些线上故障,大多与内存相关,掌握JVM运行时数据区域的设计原理,对排查故障十分重要。
我们可以从以下几个维度来理解不同的JVM的运行时数据区域
1. 作用域与生命周期
2. 保存什么数据
3. 容量,固定大小或动态伸缩两种策略
4. 相关的异常,会出什么异常跟容量策略有关
在对JVM规范进行深入研究后,我将核心内容抽象浓缩到下图当中。
JVM运行时数据区域-核心知识图谱
私信发送关键字“JVM”,自动获取完整版JVM知识体系图
01
程序计数寄存器(PC Register)
JVM的PC寄存器是对物理CPU寄存器的一种抽象模拟。
JVM支持同时执行多个线程,每个线程都有自己的PC寄存器,即寄存器是每个线程的私有数据区域。生命周期与线程相同,在线程创建的时候被创建,在线程销毁的时候被销毁。
JVM的PC寄存器足够宽,可以保存一个returnAddress类型的值或与平台相关的一个本地指针。
PC寄存器是JVM中唯一不会抛出OutOfMemoryError的内存区域。
02
栈(Stacks)
栈是每个JVM线程私有的数据区域,其生命周期与线程相同,在线程创建的同时被创建,线程销毁的时候被销毁。栈中存储着帧(frames),即栈帧,关于栈帧在后续文章中会有介绍,在这里不再展开叙述。
JVM栈类似于一些传统的栈(比如C语言的),通过栈帧保存着方法调用过程中的一些局部变量以及返回数据的一部分结果。除了pop和push栈帧操作,JVM栈从来不会被直接操作,帧可以从堆中分配。栈的内存并不需要是连续的。
怎么理解传统的栈?
指的是方法调用栈,由栈帧构成的栈。程序的执行过程是连续的函数调用,每一次调用都对应一个栈帧,符合后入先出(LIFO)的顺序。栈帧中保存着函数调用的局部状态,通过栈帧实现了参数、方法返回值的传递。
JVM规范允许栈有固定的大小,或者根据需要动态扩缩容。如果某个JVM实现中的栈是固定大小的,那么可以在创建栈的时候指定大小,如果是动态扩缩容的,则可以在创建栈的时候指定最大最小值。
相关异常
1. 如果线程计算需要的栈内存比被允许的大小还要大,则JVM会抛出StackOverflowError。
2. 如果JVM栈是可以动态扩张的,并且已经尝试扩张,但是因为可用内存不足而影响了扩张。或者可用内存不足以创建一个新线程的初始栈,JVM会抛出OutOfMemoryError。
03
堆(Heap)
堆被所有线程共享,为所有类实例和数组分配内存,生命周期与JVM相同,在虚拟机启动时被创建,在虚拟机退出时销毁。
堆存储是被自动存储管理系统(即垃圾收集器,Garbage Collector)回收的,对象永远不会被显式地释放。JVM并不指定特定的自动存储管理系统,存储管理技术可以由实现者根据需求进行选择。
堆的大小可以是固定的,也可以根据计算的需要进行扩展,如果不需要更大的堆,还可以收缩。和栈一样,堆的内存也不需要是连续的。
在JVM实现中,可以让程序员或用户控制堆的初始大小,如果堆是可以动态扩展或收缩,则还可以控制堆的最大和最小大小。
相关异常:
1. 如果计算需要的堆比自动存储管理系统提供的还要多,JVM会抛出OutOfMemoryError。
04
方法区(Method Area)
方法区被所有线程共享,生命周期与JVM相同,在虚拟机启动时被创建,在虚拟机退出时销毁。
它类似于传统语言编译后代码的存储区域,也类似于操作系统进程中的文本段(程序代码)。方法区中存储的是每个类的结构,比如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法。
虽然在逻辑上方法区是堆(Heap)的一部分,但是在比较简单的JVM实现中可以选择不进行垃圾收集或压缩。JVM规范不强制规定方法区的位置或者管理编译后代码的策略。方法区的大小可以是固定的,也可以按需扩缩容。方法区的内存也不需要是连续的。
在JVM实现中,可以让程序员或用户控制方法区的初始大小,如果堆是可以动态扩展或收缩,则还可以控制堆的最大和最小大小。
相关异常:
1. 可用内存不足以初始化方法区或扩容超出了最大允许值时,JVM会抛出OutOfMemoryError。
05
运行时常量池(Run-Time Constant Pool)
运行时常量池是类文件常量池表中的每一个类或接口在运行时的表现形式。它包含了几种类型的常量,从编译时已知的数值字面值到必须在运行时才会被解析的方法和字段引用。运行时常量池所提供的功能与传统编程语言的符号表(编译原理中的一个概念)类似,不过它包含的数据范围比典型的符号表更广。
每一个运行时常量池都是从方法区中分配的,在虚拟机创建类或接口时被创建。
相关异常:
1. 在创建类或接口时,如果构建运行时常量池需要的内存超出了JVM方法区目前的可用内存,JVM将会抛出OutOfMemoryError。
——百度百科
06
本地方法栈(Native Method Stacks)
本地方法栈是线程私有的,生命周期与线程相同,在线程创建时被创建,在线程退出时被销毁。
在JVM实现中,可以使用传统的栈(通俗地称为”C栈“,C是Conventional)来支持本地方法(用Java语言以外的语言编写的方法)。本地方法栈可以被例如C语言中的JVM指令集解释器所使用。当JVM虚拟机的实现不能加载本地方法并且它本身不依赖传统栈时,则不需要提供本地方法栈。如果提供了本地方法栈,通常要在每个线程创建时为每个线程分配本地方法栈。
JVM规范允许本地方法栈具有固定大小,或者按需动态扩缩容。如果JVM实现中的本地方法栈是固定大小的,可以在创建时选择每个本地方法栈的大小。
在JVM实现中,可以让程序员或者用户控制本地方法栈的初始大小,当大小可变时,也可以控制最小和最大值。
相关异常
1. 如果线程计算所需的本地方法内存超出允许的范围,JVM抛出StackOverflowError。
2. 如果某个JVM实现中的本地方法栈支持动态扩容,并且正在进行扩容,但可用内存不足。或者可用内存不足以创建一个新线程的初始方法栈,JVM会抛出OutOfMemoryError。
07
结语
以上就是JVM运行时数据区域的所有内容。以HotSpot为例的虚拟机,在进行实现时虽然会有自己个性化的实现方式,比如将本地方法栈与栈融合到了一起、JDK1.8之后将方法区改为MetaSpace-元数据区,但万变不离其宗。掌握底层原理规范,能让你更容易定位遇到的问题,建议根据开头的知识图谱,反复阅读消化。
扫描二维码关注
码神手记
以上是关于JVM规范系列之运行时数据区域的主要内容,如果未能解决你的问题,请参考以下文章