当前位置: 首页 > news >正文

JVM_栈详解一

1、栈的存储单位

     **栈中存储什么?**, 每个线程都有自己的栈,栈中的数据都是以栈帧(Stack Frame)的格式存在。在这个线程上正在执行的每个方法都各自对应一个栈帧(Stack Frame)。 栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息。

2、栈运行原理

      JVM直接对栈的操作只有两个,也就是栈帧的入栈和出栈,遵循先进后出/后进先出原则。在一个活动线程中,一个时间点上,只会有一个活动的栈帧,即只有当前正在执行的方法的栈帧(栈顶栈帧)是有效的,这个栈帧被称为当前栈帧(Current Frame),与当前栈帧对应的方法就是当前方法(Current Method),定义这个方法的类就是当前类(Current Class)。执行引擎运行的所有字节码指令只针对当前栈帧进行操作。如果在该方法中调用了其他方法,对应的新的栈帧就会被创建出来,放在栈的顶端,成为新的当前帧。

     注意事项:不同线程中所包含的栈帧是不允许存在相互引用的,即不可能在一个栈帧之中引用另外一个线程的栈帧如果当前方法调用了其他方法,方法返回之际,当前栈帧会传回此方法的执行结果给前一个栈帧,接着,虚拟机会丢弃当前栈帧,使得前一个栈帧重新成为当前栈帧。java方法有两种返回函数的方式,一种是正常的函数返回,使用return指令,另一种是抛出异常。不管是使用哪种方式,都会导致栈帧被弹出。

3、栈帧的内部结构

每个栈中存储着:

    局部变量表(Local Variables)
    操作数栈(Oprand Stack)(或表达式栈)
    动态链接(Dynamic Linking)(或指向运行时常量池的方法引用)
    方法返回地址(Return Address)(或方法正常退出或者异常退出的定义)
    一些附加信息

4、局部变量表

   介绍 
    局部变量表也被称之为局部变量数组或本地变量表
    定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类本数据类型、对象引用(reference),以及ReturnAddress类型
    由于局部变量表是建立在线程的栈上,是线程的私有数据,因此不存在数据安全问题
    局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maxmum variables数据项中。在方法运行期间是不会改变局部变量表大小的。

5、关于Slot的理解

    局部变量表,最基本的存储单元是Slot(变量槽)
    参数值的存放总是在局部变量数组的index0开始,到数组长度-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处,其余的参数按照参数顺序继续排列。

    在栈帧中,与性能调优关系最为密切的部分就是局部变量表。在方法执行时,虚拟机使用局部变量表完成方法的传递。
    局部变量表中变量也是重要的垃圾回收根节点,只要被局部变量表中直接或间接引用的对象都不会被回收。

6、操作数栈

介绍
    每一个独立的栈帧中除了包含局部变量表之外,还包含一个后进先出(Last-in-first-out)的操作数栈,也可以称之为表达式栈(Expression Stack)
    操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)/出栈(pop)
    某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。
    比如:执行复制、交换、求和等操作

 图解:将8和15出栈,执行求和操作后再将结果进栈操作。

 概念
    操作数栈,主要用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间
    操作数栈就是jvm执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的
    每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的code属性中,为max_stack的值
    栈中的任何一个元素都是可以任意的java数据类型
    32bit的类型占用一个栈单位深度
    64bit的类型占用两个栈单位深度
    操作数栈并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈(push)和出栈(pop)操作来完成一次数据访问
    代码追踪

7、栈顶缓存技术   

背景
    由于操作数是存储在内存中的,因此频繁的执行内存读/写操作必然会影响执行速度。为了解决这个问题,HopSpot JVM的设计者们提出了栈顶缓存(Tos,Top-of-Stack Cashing)技术,将栈顶元素全部缓存在物理cpu的寄存器中,以此降低对内存的读/写次数,提升执行引擎的执行效率

8、 动态链接

    如果被调用的方法在编译期无法被确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接

9、符号引用

我们用命令javap -v Math.class 获取可读的字节码信息,如下,我们类信息中的所有符号都放在了常量池中,如下:
 

public class com.suibibk.jvm.Mathminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER
Constant pool:#1 = Class              #2             // com/suibibk/jvm/Math#2 = Utf8               com/suibibk/jvm/Math#3 = Class              #4             // java/lang/Object#4 = Utf8               java/lang/Object#5 = Utf8               <init>#6 = Utf8               ()V#7 = Utf8               Code#8 = Methodref          #3.#9          // java/lang/Object."<init>":()V#9 = NameAndType        #5:#6          // "<init>":()V#10 = Utf8               LineNumberTable#11 = Utf8               LocalVariableTable#12 = Utf8               this#13 = Utf8               Lcom/suibibk/jvm/Math;#14 = Utf8               compute#15 = Utf8               ()I#16 = Utf8               a#17 = Utf8               I#18 = Utf8               b#19 = Utf8               c#20 = Utf8               main#21 = Utf8               ([Ljava/lang/String;)V#22 = Methodref          #1.#9          // com/suibibk/jvm/Math."<init>":()V#23 = Methodref          #1.#24         // com/suibibk/jvm/Math.compute:()I#24 = NameAndType        #14:#15        // compute:()I#25 = Utf8               args#26 = Utf8               [Ljava/lang/String;#27 = Utf8               math#28 = Utf8               SourceFile#29 = Utf8               Math.java
{public com.suibibk.jvm.Math();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #8                  // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:Start  Length  Slot  Name   Signature0       5     0  this   Lcom/suibibk/jvm/Math;public int compute();descriptor: ()Iflags: ACC_PUBLICCode:stack=2, locals=4, args_size=10: iconst_11: istore_12: iconst_23: istore_24: iload_15: iload_26: iadd7: bipush        109: imul10: istore_311: iload_312: ireturnLineNumberTable:line 5: 0line 6: 2line 7: 4line 8: 11LocalVariableTable:Start  Length  Slot  Name   Signature0      13     0  this   Lcom/suibibk/jvm/Math;2      11     1     a   I4       9     2     b   I11       2     3     c   Ipublic static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: new           #1                  // class com/suibibk/jvm/Math3: dup4: invokespecial #22                 // Method "<init>":()V7: astore_18: aload_19: invokevirtual #23                 // Method compute:()I12: pop13: returnLineNumberTable:line 11: 0line 12: 8line 13: 13LocalVariableTable:Start  Length  Slot  Name   Signature0      14     0  args   [Ljava/lang/String;8       6     1  math   Lcom/suibibk/jvm/Math;
}



常量池中的就都是符号引用,那方法怎么找到。符号引用转直接引用,我们再看一下我们的代码

public class Math {private static Math math = new Math();public int compute() {int a = 1;int b = 2;int c = (a+b)*10;return c;}public static void main(String[] args) {Math math = new Math();math.compute();}
}


   在JVM执行到map.compute()时,也就是对于上面的指令
     9: invokevirtual #23                 // Method compute:()I
    时,怎么根据符号引用找到compute()的执行指令?
    我们通过#23去常量池中找到对应的符号
    #23 = Methodref          #1.#24         // com/suibibk/jvm/Math.compute:()I
    可以知道时方法引用,其实#23对应也有两个符号#1和#24,这两个符号分别如下
    #1 = Class              #2             // com/suibibk/jvm/Math
    #24 = NameAndType        #14:#15        // compute:()I
    然后#24对应的时#14和#15
    #14 = Utf8               compute
    #15 = Utf8               ()I
    所以#23对应的符号引用就是com/suibibk/jvm/Math.compute:()I
    然后JVM会找到该符号引用对应的直接引用,放入栈帧的动态链接中。

10、方法返回地址(return address)

存放调用该方法的pc寄存器的值。一个方法的结束,有两种方式:正常执行完成
出现未处理的异常,非正常退出
无论通过哪种方式退出,在方法退出后都返回到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。

当一个方法开始执行后,只有两种方式可以退出这个方法:
执行引擎遇到任意一个方法返回的字节码指令(return),会有返回值传递给上层的方法调用者,简称正常完成出口;
一个方法在正常调用完成之后,究竟需要使用哪一个返回指令,还需要根据方法返回值的实际数据类型而定。
在字节码指令中,返回指令包含ireturn(当返回值是boolean,byte,char,short和int类型时使用),lreturn(Long类型),freturn(Float类型),dreturn(Double类型),areturn。另外还有一个return指令声明为void的方法,实例初始化方法,类和接口的初始化方法使用。

在方法执行过程中遇到异常(Exception),并且这个异常没有在方法内进行处理,也就是只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,简称异常完成出口。
方法执行过程中,抛出异常时的异常处理,存储在一个异常处理表,方便在发生异常的时候找到处理异常的代码

Exception table:
from to target type
4	 16	  19   any
19	 21	  19   any

本质上,方法的退出就是当前栈帧出栈的过程。此时,需要恢复上层方法的局部变量表、操作数栈、将返回值压入调用者栈帧的操作数栈、设置PC寄存器值等,让调用者方法继续执行下去。
正常完成出口和异常完成出口的区别在于:通过异常完成出口退出的不会给他的上层调用者产生任何的返回值。

11、一些附加信息

栈帧中还允许携带与Java虚拟机实现相关的一些附加信息。例如:对程序调试提供支持的信息。


12、栈的相关问题

举例栈溢出的情况?(StackOverflowError)
通过 -Xss设置栈的大小

调整栈大小,就能保证不出现溢出么?
不能保证不溢出

分配的栈内存越大越好么?
不是,一定时间内降低了OOM概率,但是会挤占其它的线程空间,因为整个空间是有限的。

垃圾回收是否涉及到虚拟机栈?
不会

方法中定义的局部变量是否线程安全?
具体问题具体分析。如果对象是在内部产生,并在内部消亡,没有返回到外部,那么它就是线程安全的,反之则是线程不安全的。

相关文章:

JVM_栈详解一

1、栈的存储单位 **栈中存储什么&#xff1f;**&#xff0c; 每个线程都有自己的栈&#xff0c;栈中的数据都是以栈帧&#xff08;Stack Frame&#xff09;的格式存在。在这个线程上正在执行的每个方法都各自对应一个栈帧&#xff08;Stack Frame&#xff09;。 栈帧是一个内存…...

Linux 金仓数据库安装和使用

文章目录 Linux 金仓数据库安装和简单使用 一、下载二、安装三、启动法1. 通用启动方式法2. 系统服务启动方式 四、测试五、DB管理工具1. 启动DB管理工具2. DB管理工具的常用功能 六、卸载 Linux 金仓数据库安装和简单使用 一、下载 打开官网 https://www.kingbase.com.cn/xzz…...

STM32笔记(串口IAP升级)

一、IAP简介 IAP&#xff08;In Application Programming&#xff09;即在应用编程&#xff0c; IAP 是用户自己的程序在运行过程中对 User Flash 的部分区域进行烧写&#xff0c;目的是为了在产品发布后可以方便地通过预留的通信口对产 品中的固件程序进行更新升级。 通常实…...

C++网络编程:select IO多路复用及TCP服务器开发

C网络编程&#xff1a;使用select实现IO多路复用 一、什么是 IO 多路复用&#xff1f;二、IO多路复用器 select三、相关接口3.1、fd_set 结构体3.2、宏和函数 四、select 实现 TCP 服务器五、总结 一、什么是 IO 多路复用&#xff1f; 在网络编程中&#xff0c;最容易想到的并…...

部署 L2JMobius 天堂2芙蕾雅版本

首先下载所需要的服务器端 “L2J_Mobius.zip” 和芙蕾雅客户端&#xff08;三个压缩文件&#xff09;&#xff0c; 我的网盘下载&#xff1a;https://pan.baidu.com/s/1XdlcCFPvXnzfwFoVK7Sn7Q?pwdavd4 所有文件都在“芙蕾雅”目录下&#xff0c;也可以加入企鹅交流裙 87470…...

C#开发合集

用C#轻松搞定m3u8视频下载与合并 嘿&#xff0c;程序员们&#xff01;今天咱们来聊聊如何用C#写个小程序&#xff0c;轻松下载和合并m3u8视频文件。没错&#xff0c;就是那种分段的流媒体视频。准备好了吗&#xff1f;让我们开始吧&#xff01; 准备工作 在动手之前&#xf…...

鸿蒙面试 --- 性能优化

性能优化可以从三个方面入手 感知流畅、渲染性能、运行性能 感知流畅 在应用开发中&#xff0c;动画可以为用户界面增添生动、流畅的交互效果&#xff0c;提升用户对应用的好感度。然而&#xff0c;滥用动画也会导致应用性能下降&#xff0c;消耗过多的系统资源&#xff0c;…...

React的基础知识:Context

1. Context 在 React 中&#xff0c;Context 提供了一种通过组件树传递数据的方式&#xff0c;无需手动在每个层级传递 props。这在处理一些全局应用状态时非常有用&#xff0c;比如用户认证、主题、语言偏好等。 如何使用 Context 创建 Context&#xff1a;首先&#xff0c;…...

微知-lspci访问到指定的PCIe设备的几种方式?(lspci -s bus;lspci -d devices)

通过bdf号查看 -s &#xff08;bus&#xff09; lspci -s 03:00.0通过vendor id或者device id等设备查看 -d &#xff08;device&#xff09; lspci -d 15b3: #这里是vendor号&#xff0c;所以在前面 lspci -d :1021 #这里是设备号&#xff0c;所以要:在前vendorid和deviceid…...

【Kubernetes 集群核心概念:Pod】pod生命周期介绍【五】

5.1 Pod生命周期 Pod的生命周期指的是从Pod创建到终止的整个过程。它分为以下两种常见情况&#xff1a; 长期运行Pod&#xff1a; 例如运行HTTP服务的Pod&#xff0c;它在正常情况下会一直运行&#xff0c;但可以手动删除或终止。短期运行Pod&#xff1a; 例如执行计算任务的…...

c++的虚继承说明、案例、代码

虚继承的基本概念 在 C 中&#xff0c;虚继承主要用于解决多继承时可能出现的菱形继承问题。菱形继承是指一个类有两个&#xff08;或更多&#xff09;子类&#xff0c;而这两个子类又同时继承自一个共同的基类&#xff0c;当这些子类又被另一个类继承时&#xff0c;就形成了菱…...

小米PC电脑手机互联互通,小米妙享,小米电脑管家,老款小米笔记本怎么使用,其他品牌笔记本怎么使用,一分钟教会你

说在前面 之前我们体验过妙享中心&#xff0c;里面就有互联互通的全部能力&#xff0c;现在有了小米电脑管家&#xff0c;老款的笔记本竟然用不了&#xff0c;也可以理解&#xff0c;毕竟老款笔记本做系统研发的时候没有预留适配的文件补丁&#xff0c;至于其他品牌的winPC小米…...

介绍SSD硬盘

SSD硬盘&#xff08;固态硬盘&#xff0c;Solid State Drive&#xff09;是一种利用闪存技术存储数据的存储设备&#xff0c;与传统的机械硬盘&#xff08;HDD&#xff09;不同&#xff0c;SSD没有任何活动部件&#xff0c;因此其性能和耐用性较为优越。以下是SSD硬盘的一些主要…...

CMAKE常用命令详解

NDK List基本用法 Get–获取列表中指定索引的元素 list(Get list_name index output_var)解释 list_name: 要操作集合的名称index: 要取得的元素下标output_var: 保存从集合中取得元素的结果 栗子 list(GET mylist 0 first_element) # 获取第一个元素APPEND–在列表末尾…...

Vue3的通灵之术Teleport

前言 近期Vue3更新了一些新的内容&#xff0c;我都还没有一个一个仔细去看&#xff0c;但是还是有必要去解读一下新内容的。就先从Teleport 开始吧。 官方对 Teleport 的解释是&#xff1a;<Teleport> 是一个内置组件&#xff0c;它可以将一个组件内部的一部分模板“传…...

ue5第三人称闯关游戏学习(一)

视频资料38 - Compilers and Editors_哔哩哔哩_bilibili 上一个第一人称射击项目做完 接下来要更深入学习。 引入资产与C来创建第三人称闯关游戏 这次要引入的资产有两个分别是 Unreal Learning Kit&#xff1a;Game和stylized character kit: casual 01 不过有个比较麻…...

IIC 随机写+多次写 可以控制写几次

verilog module icc_tx#(parameter SIZE 2 , //用来控制写多少次 比如地址是0000 一个地址只能存放8bit数据 超出指针就会到下一个地址0001parameter CLK_DIV 50_000_000 ,parameter SPEED 100_000 ,parameter LED 50 )( input wire c…...

controller中的参数注解@Param @RequestParam和@RequestBody的不同

现在controller中有个方法&#xff1a;&#xff08;LoginUserRequest是一个用户类对象&#xff09; PostMapping("/test/phone")public Result validPhone(LoginUserRequest loginUserRequest) {return Result.success(loginUserRequest);}现在讨论Param("login…...

手搓人工智能-最优化算法(1)最速梯度下降法,及推导过程

“Men pass away, but their deeds abide.” 人终有一死&#xff0c;但是他们的业绩将永存。 ——奥古斯坦-路易柯西 目录 前言 简单函数求极值 复杂函数梯度法求极值 泰勒展开 梯度&#xff0c;Nabla算子 Cauchy-Schwarz不等式 梯度下降算法 算法流程 梯度下降法…...

多目标优化算法——多目标粒子群优化算法(MOPSO)

Handling Multiple Objectives With Particle Swarm Optimization&#xff08;多目标粒子群优化算法&#xff09; 一、摘要&#xff1a; 本文提出了一种将帕累托优势引入粒子群优化算法的方法&#xff0c;使该算法能够处理具有多个目标函数的问题。与目前其他将粒子群算法扩展…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

【ROS】Nav2源码之nav2_behavior_tree-行为树节点列表

1、行为树节点分类 在 Nav2(Navigation2)的行为树框架中,行为树节点插件按照功能分为 Action(动作节点)、Condition(条件节点)、Control(控制节点) 和 Decorator(装饰节点) 四类。 1.1 动作节点 Action 执行具体的机器人操作或任务,直接与硬件、传感器或外部系统…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个生活电费的缴纳和查询小程序

一、项目初始化与配置 1. 创建项目 ohpm init harmony/utility-payment-app 2. 配置权限 // module.json5 {"requestPermissions": [{"name": "ohos.permission.INTERNET"},{"name": "ohos.permission.GET_NETWORK_INFO"…...

全志A40i android7.1 调试信息打印串口由uart0改为uart3

一&#xff0c;概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本&#xff1a;2014.07&#xff1b; Kernel版本&#xff1a;Linux-3.10&#xff1b; 二&#xff0c;Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01)&#xff0c;并让boo…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

Go 并发编程基础:通道(Channel)的使用

在 Go 中&#xff0c;Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式&#xff0c;用于在多个 Goroutine 之间传递数据&#xff0c;从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...

Vite中定义@软链接

在webpack中可以直接通过符号表示src路径&#xff0c;但是vite中默认不可以。 如何实现&#xff1a; vite中提供了resolve.alias&#xff1a;通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...

脑机新手指南(七):OpenBCI_GUI:从环境搭建到数据可视化(上)

一、OpenBCI_GUI 项目概述 &#xff08;一&#xff09;项目背景与目标 OpenBCI 是一个开源的脑电信号采集硬件平台&#xff0c;其配套的 OpenBCI_GUI 则是专为该硬件设计的图形化界面工具。对于研究人员、开发者和学生而言&#xff0c;首次接触 OpenBCI 设备时&#xff0c;往…...