<JVM上篇:内存与垃圾回收篇>03-程序计数器 | 虚拟机栈(三)

简介: <JVM上篇:内存与垃圾回收篇>03-程序计数器 | 虚拟机栈

4.3.1. 关于 Slot 的理解


局部变量表,最基本的存储单元是 Slot(变量槽)


参数值的存放总是在局部变量数组的 index 0 开始,到数组长度-1 的索引结束。


局部变量表中存放编译期可知的各种基本数据类型(8 种),引用类型(reference),returnAddress 类型的变量。


在局部变量表里,32 位以内的类型只占用一个 slot(包括 returnAddress 类型),64 位的类型(long 和 double)占用两个 slot。


byte、short、char 在存储前被转换为 int,boolean 也被转换为 int,0 表示 false,非 0 表示 true。

long和double则占据两个Slot

JVM 会为局部变量表中的每一个 Slot 都分配一个访问索引,通过这个索引即可成功访问到局部变量表中指定的局部变量值


当一个实例方法被调用的时候,它的方法参数和方法体内部定义的局部变量将会按照顺序被复制到局部变量表中的每一个 slot 上


如果需要访问局部变量表中一个 64bit 的局部变量值时,只需要使用前一个索引即可。(比如:访问 long 或 double 类型变量)


如果当前帧是由构造方法或者实例方法创建的,那么该对象引用 this 将会存放在 index 为 0 的 slot 处,其余的参数按照参数表顺序继续排列。


229d2c5ec47176808059829e278deffd.png


方法与局部变量表对照图:


665c1e5b9f08ae4248c241a4f1ffa33e.png


4.3.2. Slot 的重复利用


栈帧中的局部变量表中的槽位是可以重用的,如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变就很有可能会复用过期局部变量的槽位,从而达到节省资源的目的。


Slot重复利用示意图:


301e1fb116fd07705b804205b89398d4.png


4.3.3. 静态变量与局部变量的对比


参数表分配完毕之后,再根据方法体内定义的变量的顺序和作用域分配。


我们知道类变量表有两次初始化的机会,第一次是在“准备阶段”,执行系统初始化,对类变量设置零值,另一次则是在“初始化”阶段,赋予程序员在代码中定义的初始值。


和类变量初始化不同的是,局部变量表不存在系统初始化的过程,这意味着一旦定义了局部变量则必须人为的初始化,否则无法使用。

public void test(){
    int i;
    System. out. println(i);//System.out.println(num);//错误信息:Variable 'num' might not have been initialized
}

这样的代码是错误的,没有赋值不能够使用。


**补充:**变量的分类:


按照数据类型分:① 基本数据类型 ② 引用数据类型

按照在类中声明的位置分:

成员变量:在使用前,都经历过默认初始化赋值.

类变量(静态变量): linking的prepare阶段:给类变量默认赋值 —> initial阶段:给类变量显式赋值即静态代码块赋值

实例变量:随着对象的创建,会在堆空间中分配实例变量空间,并进行默认赋值

局部变量:在使用前,必须要进行显式赋值的!否则,编译不通过


4.3.4. 补充说明


在栈帧中,与性能调优关系最为密切的部分就是前面提到的局部变量表。在方法执行时,虚拟机使用局部变量表完成方法的传递。


局部变量表中的变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。


4.4. 操作数栈(Operand Stack)


每一个独立的栈帧除了包含局部变量表以外,还包含一个后进先出(Last-In-First-Out)的 操作数栈,也可以称之为表达式栈(Expression Stack)


操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)


某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈

比如:执行复制、交换、求和等操作

2a6db6fbdcbf204c7df774272cbcf615.png

代码举例

public void testAddOperation(){
    byte i = 15;
    int j = 8;
    int k = i + j;
}

字节码指令信息

public void testAddOperation();
    Code:
    0: bipush 15
    2: istore_1
    3: bipush 8
    5: istore_2
    6:iload_1
    7:iload_2
    8:iadd
    9:istore_3
    10:return

操作数栈,主要用于保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间。


操作数栈就是 JVM 执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。


每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的 Code 属性中,为 max_stack 的值。


栈中的任何一个元素都是可以任意的 Java 数据类型


32bit 的类型占用一个栈单位深度

64bit 的类型占用两个栈单位深度

操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈和出栈操作来完成一次数据访问


如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新 PC 寄存器中下一条需要执行的字节码指令。


操作数栈中元素的数据类型必须与字节码指令的序列严格匹配,这由编译器在编译器期间进行验证,同时在类加载过程中的类检验阶段的数据流分析阶段要再次验证。


另外,我们说 Java 虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。


**备注:**操作数栈和局部变量表的底层都是数组,所以对于double和long类型数据需要两个单位存储.


4.5. 代码追踪

public void testAddOperation() {
    byte i = 15;
    int j = 8;
    int k = i + j;
}


使用 javap 命令反编译 class 文件: javap -v 类名.class

public void testAddoperation();     
  Code: 
     0 bipush 15
     2 istore_1
     3 bipush 8
     5 istore_2
     6 iload_1
     7 iload_2
     8 iadd
     9 istore_3
    10 return

e9261231648baa24763fc3793d784f20.png

914e23e832f563ae376c15e3da4769b4.png

3055e8553a6d4384ea44d4b29043b0a6.png

49026433c7ec6e427f51de42600bb720.png

c35e3ce1222e0f107c5641faa8082e9f.png

acbaba8894ebb7bb64f45d0ec5840282.png

4f2281d29c6db52a247bec4d2dce0910.png

92fa27284aa5e058b5e7c86dc44267ac.png

涉及操作数栈的字节码指令执行分析:

5f41973d7536c3b9d7f57dc4c14ca699.png


  • istore 指令会导致出栈 并且写入局部变量表 ipush:放入操作数栈
  • istore和istore一样,只不过istore只有0到3(其实是四个不同的指令,操作数隐式指定),再往后就得用istore了,因为还需要显式指出槽位,所以要占两个字节.
相关文章
|
3月前
|
缓存 Prometheus 监控
Elasticsearch集群JVM调优设置合适的堆内存大小
Elasticsearch集群JVM调优设置合适的堆内存大小
697 1
|
2天前
|
存储 IDE Java
java设置栈内存大小
在Java应用中合理设置栈内存大小是确保程序稳定性和性能的重要措施。通过JVM参数 `-Xss`,可以灵活调整栈内存大小,以适应不同的应用场景。本文介绍了设置栈内存大小的方法、应用场景和注意事项,希望能帮助开发者更好地管理Java应用的内存资源。
16 1
|
8天前
|
存储 算法 Java
JVM: 内存、类与垃圾
分代收集算法将内存分为新生代和老年代,分别使用不同的垃圾回收算法。新生代对象使用复制算法,老年代对象使用标记-清除或标记-整理算法。
18 3
|
2月前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
2月前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
102 5
|
3月前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
101 1
|
3月前
|
Java
JVM内存参数
-Xmx[]:堆空间最大内存 -Xms[]:堆空间最小内存,一般设置成跟堆空间最大内存一样的 -Xmn[]:新生代的最大内存 -xx[use 垃圾回收器名称]:指定垃圾回收器 -xss:设置单个线程栈大小 一般设堆空间为最大可用物理地址的百分之80
|
3月前
|
Java
JVM运行时数据区(内存结构)
1)虚拟机栈:每次调用方法都会在虚拟机栈中产生一个栈帧,每个栈帧中都有方法的参数、局部变量、方法出口等信息,方法执行完毕后释放栈帧 (2)本地方法栈:为native修饰的本地方法提供的空间,在HotSpot中与虚拟机合二为一 (3)程序计数器:保存指令执行的地址,方便线程切回后能继续执行代码
40 3
|
3月前
|
存储 缓存 监控
Elasticsearch集群JVM调优堆外内存
Elasticsearch集群JVM调优堆外内存
75 1
|
3月前
|
存储
栈内存
栈内存归属于单个线程,也就是每创建一个线程都会分配一块栈内存,而栈中存储的东西只有本线程可见,属于线程私有。 栈的生命周期与线程一致,一旦线程结束,栈内存也就被回收。 栈中存放的内容主要包括:8大基本类型 + 对象的引用 + 实例的方法
41 1

热门文章

最新文章