JVM内存结构解读

参考书籍及部分图片来源:《深入理解Java虚拟机:第三版》

一、JVM内存结构概述

image-20200717172801422

1. 程序计数器(Program Counter Register)

​ 每个线程都有一个独立的程序计数器,占用空间非常小,它记录了线程正在执行的字节码指令的地址,字节码解释器通过修改它的值来选取下一条要执行的指令,因此被称为程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。注意如果正在执行的是本地(Native)方法,这个计数器值则应为空(Undefined)。因为native方法由C/C++实现 ,并没有被编译为字节码指令。

2. Java虚拟机栈(Java Virtual Machine Stack)

​ 栈也是线程私有的,生命周期与线程相同,栈描述了Java方法执行时的内存模型。JVM会在每个方法执行的时候在栈中创建一个栈帧,用来存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法被调用直至执行完毕的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。

​ 局部变量表存放了各种编译期可知的基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress类型(指向了一条字节码指令的地址)。

​ 这些数据类型在局部变量表中的存储空间以局部变量槽(Slot)来表示,其中64位长度的long和double类型的数据会占用两个变量槽,其余的数据类型只占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。

​ 当异常线程请求的栈深度大于虚拟机所允许的深度时抛出 StackOverflowError,虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存时会抛出OutOfMemoryError(HotSpot虚拟机栈容量无法动态扩展)。

3. 本地方法栈(Native Method Stacks)

​ 和Java虚拟机栈的作用相同,不同的是里面存储的是native方法,也就是非java语言实现的方法。native方法会调用本地库接口(JNI),所以我们只能看到一个由native修饰的方法名。native方法实现纯java无法实现的功能,通常与操作系统及硬件有关。

4. Java堆(Java Heap)

​ Java堆是JVM管理内存中最大的一块,是被所有线程共享的一块区域,它的唯一作用就是存储对象的实例,几乎所有的对象是李都会在这里分配内存。因此。Java堆又被称作GC堆,因为垃圾回收器回收的就是对象实例,也就是说堆是由垃圾回收器管理的区域。

​ Java堆中也是有区域分配的,如新生代、老年代、永久代亦或元空间,每个区域中存储存活时间不同的实例。大部分实例创建后都是在新生代中,经研究发现,大部分实例生命周期短,活不过新生代,因此新生代是垃圾回收器主要的回收区域。

​ 如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError 错误。

5. 方法区(Method Area)

​ 方法区也是线程共享的区域,它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。虽然《Java虚拟机规范》中把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫作“非堆”(Non-Heap),目的是与Java堆区分开来。

​ 当方法区无法满足内存分配需求时,将抛出OutOfMemoryError错误。

6. 运行时常量池(Runtime Constant Pool)

​ 运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池表(Constant Pool Table),用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

二、Java虚拟机栈结构详解

​ 栈由栈帧组成,存储了局部变量表、操作数栈、动态连接、和方法返回地址等信息,每一个方法从调用开始到结束的过程都对应着一个栈帧从入栈到出栈的过程。

image-20200718192115326

1. 局部变量表(Local Variables Table)

​ 局部变量表是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量,以变量槽(Variable Slot)为最小单位,除了long与double会占据两个变量槽外其它基本数据类型(包括引用)均占用一个变量槽,变量槽的具体大小由硬件、操作系统、虚拟机所决定。

​ JVM通过索引来使用局部变量表,索引从0开始到变量槽的数量,对于占据两个变量槽的long和double,JVM不允许单独访问其中一个,必须一起访问。

​ 局部变量表第0个索引默认存储该方法所在对象实例的引用,故我们可以通过this访问到这个参数。

​ 为了节省空间,变量槽是可以重用的,即在方法区内部也有部分变量的作用域不会覆盖整个方法,如 for(int i = 0; ;)等,当循环结束后变量i所在的变量槽就有可能被重用。

image-20200718200534979

​ 如图我们可以看到bytes变量作用域极小,当我们触发gc垃圾回收的时候bytes变量(引用)应该已失效,其对应的byte实例应该会被回收,但是事实并没有回收,为社么呢?看下图:

image-20200718201115072

​ 我们可以看到这次唯一的变化就是再次声明了一个变量i,但是结果就是byte实例被回收了,为什么?因为在图一中,bytes变量虽然看似已经失效,但是该变量槽依旧存在,而在图二中,我们再次声明了一个变量i,变量i重用了已失效的bytes变量所在的变量槽,导致bytes变量彻底失效,所以byte实例就被回收了。

​ 关于局部变量表还有一个要注意的地方就是局部变量是没有默认值的,不像类变量有赋予系统初始值的过程,所以局部变量必须赋初值。

2. 操作数栈(Operand Stack)

​ 操作数栈的性质与局部变量表类似,不过它是一个栈结构,其最大深度在编译时就确定 了,除double和long外其它基本数据类型占用的栈容量为1,double和long为2 。

​ 操作数栈用于临时存储字节码指令在运行时所需要用到的数据,当一个方法刚开始执行的时候,操作数栈是空的,字节码指令会向其中压栈或出栈数据,比如在进行加法计算的时候,加法指令会将操作数栈顶的两个元素取出计算,然后将结果重新入栈。

​ 从JVM内存结构上来看栈帧是相互独立的,但实际上相邻的栈帧(有调用关系)之间是有共享区域的,这样在方法调用时就可以直接通过操作栈来进行数值传递了。如图:

image-20200718214613170

3. 动态连接(Dynamic Linking)

​ 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接。Class文件的常量池中存有大量的符号引用,字节码中的方法调用指令就以常量池里指向方法的符号引用作为参数。这些符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为直接引用,这种转化被称为静态解析。另外一部分将在每一次运行期间都转化为直接引用,这部分就称为动态连接。

4. 方法返回地址

​ 一个方法执行后有两种退出方式,一种是正常执行完退出,另一种是出现未处理的异常导致非正常退出,无论哪种退出方式,都要回该方法被调用的位置。如果是正常退出,则会返回程序计数器的值;而方法异常退出时,返回地址是要通过异常处理器表来确定的,栈帧中就一般不会保存这部分信息。

三、Java堆与方法区

1. 堆与方法区的概念

​ Java堆存储对象实例,方法区存储已被虚拟机加载的类信息(模板)、常量、静态变量等信息,还包括编译器编译后的代码。

​ 对于HotSpot虚拟机来说,很多人喜欢把方法区称作永久代,因为当时的HotSpot虚拟机设计团队选择把收集器的分代设计扩展至方法区,或者说使用永久代来实现方法区,也就是说当时的方法区和堆是连在一起的(物理上连在一起),这样使得HotSpot的垃圾收集器能够像管理Java堆一样管理这部分内存,省去专门为方法区编写内存管理代码的工作,但是其它虚拟机是没有永久代这个概念的。

图片来源:https://juejin.im/post/5df5fde36fb9a0162c486c71

​ 在Java8 中HotSpot虚拟机取消了永久代,取而代之的是元空间(MetaSpace),那么永久代与元空间有什么区别呢?永久代是堆的一部分,和新生代、老年代地址连续,但是元空间用的是本地内存,不受堆的限制。还有从Java7 开始字符串常量池、静态变量就从方法区移动到了堆中。

图片来源:https://juejin.im/post/5df5fde36fb9a0162c486c71

关于方法区的更多内容请参考:JVM方法区的变革

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

华仔哦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值