Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • ☕ Understanding the JVM
    • Part 2 - 自动内存管理

      • Chapter 2 - Java 内存区域与内存溢出异常
      • Chapter 3.1-3.2 - 概述 && 对象已死
      • Chapter 3.3-3.4 - 垃圾收集算法 && HotSpot 的算法实现细节
      • Chapter 3.5 - 经典垃圾收集器
      • Chapter 3.6 - 低延迟垃圾收集器
    • Part 3 - 虚拟机执行子系统

      • Chapter 6 - 类文件结构
      • Chapter 7 - 虚拟机类加载机制
      • Chapter 8.1-8.2 - 运行时栈帧结构
      • Chapter 8.3 - 函数调用
      • Chapter 8.5 - 基于栈的字节码解释执行引擎
      • Chapter 9.2 - Tomcat: 正统的类加载器架构
    • Part 4 - 程序编译与代码优化

      • Chapter 10 - 前端编译与优化
      • Chapter 11.2 - 即时编译器
      • Chapter 11.3-11.4 - 提前编译器 && 编译器优化技术
    • Part 5 - 高效并发

      • Chapter 12.3 - Java 内存模型
      • Chapter 12.4-12.5 - Java 线程与协程
      • Chapter 13 - 线程安全与锁优化

Chapter 8.1-8.2 - 运行时栈帧结构

Created by : Mr Dk.

2020 / 01 / 30 21:32 🧨🧧

Ningbo, Zhejiang, China


8.1 概述

关于 Java 虚拟机与物理机

  • 物理机的执行引擎直接建立在 CPU、缓存、指令集和 OS 层面上
  • 虚拟机的执行引擎由软件自行实现,不受体系结构限制

JVM 在执行字节码时,分为:

  • 解释执行 (由解释器执行)
  • 编译执行 (通过即时编译器产生本地代码)

8.2 运行时栈帧结构

JVM 以函数 (方法) 作为最基本的执行单元。栈帧 (Stack Frame) 是支持虚拟机进行函数调用和函数执行背后的数据结构,位于运行时数据区的 虚拟机栈 中,包含函数的:

  • 局部变量表
  • 操作数栈
  • 动态链接
  • 函数返回地址

栈帧需要分配的内存在编译时就已经确定了,不会受到程序运行时变量数据的影响。在活动线程中,只有位于栈顶的函数是正在运行的,只有位于栈顶的栈帧才是生效的,被称为 当前函数 和 当前栈帧。执行引擎运行的字节码指令只对当前栈帧进行操作。

8.2.1 局部变量表 (Local Variables Table)

存放 函数参数 和 函数内部定义的局部变量。在编译出 Class 文件时,函数的 Code 属性中就已经确定了局部变量表的最大容量。局部变量表的容量以 变量槽 (Variable Slot) 为最小单位。每个变量槽可以存放:

  • boolean
  • byte
  • char
  • short
  • int
  • float
  • reference
    • 对象引用
    • JVM 能根据引用找到对象在 Java Heap 中数据的起始地址
    • JVM 能根据引用找到对象所属的类在方法区中存储的类信息
  • returnAddress

JVM 明确规定 long 和 double 需要占用两个连续的变量槽。局部变量表建立在线程堆栈中,属于线程私有数据,没有线程安全问题。JVM 通过索引定位的方式使用局部变量表,当一个函数被调用时,JVM 使用局部变量表来完成参数传递。如果执行的是实例方法,局部变量表第 0 位默认用于传递函数所属对象实例的引用,即隐含参数 this;其余参数按照参数表顺序排列,占用 1 开始的局部变量槽。参数表分配完毕后,再按函数体内部变量定义的顺序和作用于分配其余变量槽。

为节省栈帧使用的内存空间,局部变量表中的变量槽是可以重用的。若当前字节码 PC 的值已经超出了变量作用域,那么这个变量槽就可以给其它变量使用。

关于手动将对象设置为 null 值是否有利于 GC,这种操作在极特殊情形下有用:

  • 对象占用内存大
  • 栈帧长时间无法被回收
  • 函数调用次数达不到 JIT 的编译条件

在实际情况中,即时编译是 JVM 执行代码的主要方式,赋 null 的操作在经过即时编译优化之后会被消除,因而没有意义。

与类变量在准备阶段和初始化阶段的二度赋值不同,局部变量没有赋初始值是绝对不能使用的。编译器会检查到这一点。

8.2.2 操作数栈 (Operand Stack)

与局部变量表一样,操作数栈的最大深度在编译时被写入 Code 属性中。32-bit 数据类型的栈容量为 1;64-bit 数据类型的栈容量为 2。Javac 编译器的数据流分析保证了函数执行的任何时候,操作数栈的深度都不会超过最大深度。编译器保证操作数栈中的元素与字节码指令序列严格匹配,在类的校验阶段还会再次验证这一点。

两个栈帧在概念上是独立的,但在实现上,JVM 会使两个栈帧重叠。下面栈帧的操作数栈与上面栈帧的局部变量表重叠:

  • 节省空间
  • 函数调用时直接共用数据,无须进行额外的参数复制传递

8.2.3 动态链接

每个栈帧都包含一个指向常量池中该栈帧所属函数的引用。字节码中的 函数调用指令 以常量池中指向函数的符号引用作为参数

  • 符号引用在类加载阶段或第一次使用时转化为直接引用 - 静态解析
  • 符号引用在每一次运行期间都转化为直接引用 - 动态链接

8.2.4 方法返回地址

  • 正常调用完成
    • 可能会有返回值传递给上层的函数调用者
  • 异常调用完成
    • 函数执行过程中遇到了异常
    • 本函数的异常表中没有搜索到匹配的异常处理器,方法退出
    • 不会给调用者提供任何返回值

函数退出后,必须返回最初方法被调用时的位置,继续执行。函数退出的过程实际上等同于把当前栈帧出栈。

Edit this page on GitHub
Prev
Chapter 7 - 虚拟机类加载机制
Next
Chapter 8.3 - 函数调用