JVM中栈的frames详解

47次阅读

共计 2779 个字符,预计需要花费 7 分钟才能阅读完成。

简介

咱们晓得 JVM 运行时数据区域专门有一个叫做 Stack Area 的区域,专门用来负责线程的执行调用。那么 JVM 中的栈到底是怎么工作的呢?快来一起看看吧。

JVM 中的栈

小师妹:F 师兄,JVM 为每个线程的运行都调配了一个栈,这个栈到底是怎么工作的呢?

小师妹,咱们先看下 JVM 的整体运行架构图:

咱们能够看到运行时数据区域分为 5 大部分。

堆区是存储共享对象的中央,而栈区是存储线程公有对象的中央。

因为是栈的构造,所以这个区域总是 LIFO(Last in first out)。咱们思考一个办法的执行,当办法执行的时候,就会在 Stack Area 中创立一个 block,这个 block 中持有对本地对象和其余对象的援用。一旦办法执行结束,则这个 block 就会出栈,供其余办法拜访。

Frame

JVM 中的 stack area 是由一个个的 Frame 组成的。

Frame 次要用来存储数据和局部后果,以及执行动静链接,办法的返回值和调度异样。

每次调用办法时都会创立一个新 Frame。当 Frame 的办法调用实现时,无论该办法是失常完结还是异样完结(它引发未捕捉的异样),这个 frame 都会被销毁。

Frame 是从 JVM 中的 stack area 中调配的。

每个 frame 都由三局部组成,别离是本人的 local variables 数组,本人的 operand stack,以及对以后办法的 run-time constant pool 的援用。

在线程的执行过程中,任何一个时刻都只有一个 frame 处于活动状态。这个 frame 被称为 current frame,它的办法被称为 current 办法,定义以后办法的类是以后类。

如果 frame 中的办法调用另一个办法或该 frame 的办法完结,那么这个 frame 将不再是 current frame。

每次调用新的办法,都会创立一个新的 frame,并将控制权转移到调用新的办法生成的框架。

在办法返回时,以后 frame 将其办法调用的后果(如果有的话)传回上一个 frame,并完结以后 frame。

请留神,由线程创立的 frame 只能有该线程拜访,并且不能被任何其余线程援用。

Local Variables 本地变量

每个 frame 都蕴含一个称为其本地局部变量的变量数组。frame 的局部变量数组的长度是在编译的时候确定的。

单个局部变量能够保留以下类型的值:boolean, byte, char, short, int, float, reference, 或者 returnAddress。

如果对于 long 或 double 类型的值须要应用一对局部变量来存储。

局部变量因为存储在数组中,所以间接通过数字的索引来定位和拜访。

留神,这个数组的索引值是从 0 开始,到数组长度 - 1 完结。

单个局部变量间接通过索引来拜访就够了,那么对于占用两个间断局部变量的 long 或者 double 类型来说,怎么拜访呢?

比如说一个 long 类型占用数组中的 n 和 n + 1 两个变量,那么咱们能够通过索引 n 值来拜访这个 long 类型,而不是通过 n + 1 来拜访。

留神,在 JVM 中,并不一定要求这个 n 是偶数。

那么这些局部变量有什么用呢?

Java 虚拟机应用局部变量在办法调用时传递参数。

咱们晓得在 java 中有两种办法,一种是类办法,一种是实例办法。

在类办法调用中,所有参数都从局部变量 0 开始在间断的局部变量中传递。

在实例办法调用中,局部变量 0 始终指向的是该实例对象,也就是 this。也就是说实在的参数是从局部变量 1 开始存储的。

Operand Stacks

在每个 frame 外部,又蕴含了一个 LIFO 的栈,这个栈叫做 Operand Stack。

刚开始创立的时候,这个 Operand Stack 是空的。而后 JVM 将 local variables 中的常量或者值加载到 Operand Stack 中去。

而后 Java 虚拟机指令从操作数堆栈中获取操作数,对其进行操作,而后将后果压回操作数堆栈。

比如说,当初的 Operand Stack 中曾经有两个值,1 和 2。

这个时候 JVM 要执行一个 iadd 指令,将 1 和 2 相加。那么就会先将 stack 中的 1 和 2 两个数取出,相加后,将后果 3 再压入 stack。

最终 stack 中保留的是 iadd 的后果 3。

留神,在 Local Variables 本地变量中咱们提到,如果是 long 或者 double 类型的话,须要两个本地变量来存储。而在 Operand Stack 中,一个值能够示意任何 Java 虚拟机类型的值。也就是说 long 和 double 在 Operand Stack 中,应用一个值就能够示意了。

Operand Stack 中的任何操作都必须要确保其类型匹配。像之前提到的 iadd 指令是对两个 int 进行相加,如果这个时候你的 Operand Stacks 中存储的是 long 值,那么 iadd 指令是会失败的。

在任何工夫点,操作数堆栈都具备关联的深度,其中 long 或 double 类型的值对该深度奉献两个单位,而任何其余类型的值则奉献一个单位深度。

Dynamic Linking 动静链接

什么是动静链接呢?

咱们晓得在 class 文件中除了蕴含类的版本、字段、办法、接口
等形容信息外,还有一项信息就是常量池 (constant pool table),用于寄存编译器生成的各种字面量(Literal) 和符号援用(Symbolic References)。

所谓字面量就是常说的常量,能够有三种形式,别离是:文本字符串,八种根本类型和 final 类型的常量。

而符号援用是指用符号来形容所援用的指标。

符号援用和间接援用有什么区别呢?咱们举个例子。

比方咱们定义了 String name=”jack”, 其中 jack 是一个字面量,会在字符串常量池(String Pool)中保留一份。

如果咱们存储的时候,存的是 name,那么这个就是符号援用。

如果咱们存储的是 jack 在字符串常量池中地址,那么这个就是间接援用。

从下面的介绍咱们能够晓得,为了实现最终的程序失常运行,所有的符号援用都须要转换成为间接援用能力失常执行。

而这个转换的过程,就叫做动静链接。

动静链接将这些符号办法援用转换为具体的办法援用,依据须要加载类以解析尚未定义的符号,并将变量拜访转换为与这些变量的运行时地位关联的存储构造中的适当偏移量。

办法执行结束

办法执行结束有两种模式,一种是失常执行结束,一种是执行过程中抛出了异样。

失常执行结束的办法能够值返回给调用方。

这种状况下 frame 的作用就是复原调用程序的状态,包含其局部变量和操作数堆栈,并适当减少调用程序的程序计数器以跳过办法调用指令。

如果办法中抛出了异样,那么该办法将不会有值返回给调用方。

本文已收录于:http://www.flydean.com/jvm-thread-stack-frames/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

正文完
 0