[JVM]java虚拟机运行时数据区域–程序计数器、虚拟机栈和本地方法栈

2022年9月11日12:17:16

目录

前言:

一. 运行时数据区域

1.1 程序计数器(线程私有)

1.2 Java虚拟机栈(线程私有)

什么叫做操作数栈?

什么是局部变量表?

什么是动态链接?

虚方法和非虚方法?

虚拟机中的方法调用指令?

什么是方法出口?

1.3 本地方法栈


前言:

JVM很重要,内存模型还是很需要知道的。

再次熟悉一遍内容。

内容来自《深入理解java虚拟机》

一. 运行时数据区域

注意共享方法区和堆。

但是线程私有的程序计数器,虚拟机栈,本地方法栈

1.1 程序计数器(线程私有)

程序计数器是一块很小的内存空间,可以当做当前程序所执行的字节码的行号指示器。

java底层程序读的是class文件。

这是一种概念模型,真的情况可能各种虚拟机使用优化的高效的方法区执行。

字节码的指示器的工作时候就是通过改变这个计数器的值,来选取下一条要执行的字节码的命令,分支,循环等等基础功能都需要这个计数器。

java虚拟机的多线程是通过线程轮流切换并分配处理器的执行时间的方式来实现的。

一个处理器,一个内核,就只会执行一个线程中的指令。

为了线程切换之后能够恢复到正确的位置,各个线程之间互不影响,独立存储,我们称这类内存区域为 线程私有的内存。

如果正在执行的是java方法,那么这个计数器记录的是虚拟机字节码的执行的指令的地址。

如果是native方法,那么这个计数器为空。

1.2 Java虚拟机栈(线程私有)

生命周期与线程相同,是线程私有的。

每个方法在执行的过程都会创建一个栈帧(stack frame)

用来存储:局部变量表,操作数栈,动态链接,方法出口等信息。

每一个方法在调用到执行完成的过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

什么叫做操作数栈?

这也是为什么java性能低的原因,因为基于栈的指令集系统做的平台无关性,但是也降低了性能

编译期就已经确定,存在方法code属性中,栈的特点后进先出,存放表达式。

因为就我们熟知的X86 和ARM指令集,对数据的操作都是基于寄存器,比如对两个数进行加法操作,那么会把两个数送到

两个寄存器中,在执行加法操作。

基于栈的设计模式,是将数放在栈中,我们需要使用的时候,将栈顶的数据出栈,并执行相应的操作。

举例来说,在JVM中 执行 a = b + c 的字节码执行过程中操作数栈以及局部变量表的变化如下图所示。

局部变量表中存储着a、b、c 三个局部变量,首先将b和c分别入栈

将栈顶的两个数出栈执行加法操作,并将结果保存至栈顶,之后将栈顶的数出栈赋值给a

什么是局部变量表?

存放编译期间可知的各种基本数据类型,对象的引用。

局部变量空间,对于long和double占用两个Slot,其余的基本类型占用一个。

局部变量表的所需要的内存空间,会在编译期间完成分配。

因为运行过程都走的是方法,那么进入一个方法的时候,这个方法需要在帧中分配多大的局部变量空间是完全确定的。在方法运行期间不会改变局部变量表的大小。

什么是动态链接?

https://www.cnblogs.com/Timeouting-Study/p/12511969.html

Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以指向常量池的引用作为参数

  • 部分符号引用在类加载阶段(解析)的时候就转化为直接引用,这种转化为静态链接
  • 部分符号引用在运行期间转化为直接引用,这种转化为动态链接

方法的调用:绑定(跟上面的动态链接是一体的)

方法绑定机制为:早期绑定和晚期绑定。

绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生过一次早期绑定

早期绑定就是指被调用的目标方法如果在编译期可知,且运行期保持不变时,即可将这个方法与所属的类型进行绑定,这样一来,由于明确了被调用的目标方法究竟是哪一个,因此也就可以使用静态链接的方式将符号引用转换为直接引用。.

晚期绑定:被调用方法在编译时不能被确定下来

每一个栈帧内部都包含有一个指向运行时常量池 中该栈帧方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。例如invokednamic指令

在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用(Symbolic Reference)保存在class文件的常量池。

常量池保存在哪?在class文件中。然后运行时候在方法区里面。

这个博主写的很清晰:

https://www.cnblogs.com/Timeouting-Study/p/12511969.html

比如:描述一个方法调用了另外的其他方法时,就是通过常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接调用

即:符号引用动态的运行时候转成直接引用。

Q:为什么需要常量池?

A:字节码文件中需要很多数据的支持,但数据很大,不能直接保存到字节码文件中,所以常量池的作用就是为了提供一些符号和常量,便于指令的识别。

虚方法和非虚方法?

非虚方法:如果方法在编译器就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法称为非虚方法

                   静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法

其他方法就是虚方法:(子类对象的多态性的前提):1.类的继承关系       2.方法可重写

虚拟机中的方法调用指令?

普通调用指令:

  1. invokestatic: 调用静态方法,解析阶段确定唯-方法版本:
  2. invokespecial:调用<init>方法、 私有及父类方法,解析阶段确定唯一方法版本
  3. invokevirtual: 调用所有虚方法
  4. invokeinterface: 调用接口方法

动态调用指令:

       5.前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持由用户确定方法版本。其中            invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。

什么是方法出口?

当一个方法执行完毕之后,要返回之前调用它的地方,因此在栈帧中必须保存一个方法返回地址。

但是出现异常会不会返回地址

补充:

局部变量,在方法内部声明,当该方法运行完时,内存即被释放。
成员变量,只要该对象还在,哪怕某一个方法运行完了,还是存在。
从系统的角度来说,声明局部变量有利于内存空间的更高效利用(方法运行完即回收)。
成员变量可用于各个方法间进行数据共享。

1.3 本地方法栈

大体上都类似于虚拟机栈
不同点:栈执行的java方法服务
本地方法栈执行的是Native方法(不一定是用java开发的)服务

参考与更多需要了解的:

JVM运行时数据区域

https://www.cnblogs.com/Gang-Bryant/p/10705582.html

字节码的运行过程

https://blog.csdn.net/a15089415104/article/details/83245568

对于操作数栈(转载了下面大佬的):

https://www.cnblogs.com/kesan/p/11368934.html

  • 作者:pmdream
  • 原文链接:https://blog.csdn.net/pmdream/article/details/105480411
    更新时间:2022年9月11日12:17:16 ,共 2891 字。