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

【JVM 03-JVM内存结构之-虚拟机栈】

虚拟机栈 笔记记录

  • 1. 定义
    • 1.1 演示栈帧
  • 2. 特点
  • 3. 线程运行诊断
    • 3.1 案例1 cpu占用过多&解决
    • 3.2 案例2 程序运行很长时间没有结果
  • 4. 拓展知识&问题辨析
    • 4.1 栈的内存越大越好嘛?(不是)
    • 4.2 方法内的局部变量是否线程安全?(是线程安全的)
      • 4.2.1 局部变量全在方法中
      • 4.2.2 局部变量可能逃离方法
    • 4.3 可能抛出的异常?
    • 4.4 什么情况下会发生栈内存溢出?
    • 4.5 如何设置栈的大小?
    • 4.6 补充栈帧的内部结构
      • 重要的两个(局部变量表&操作数栈)
        • 4.6.1 局部变量表
        • 4.6.2 操作数栈

学习资料来源-b站黑马JVM& 尚硅谷JVM精讲与GC调优

1. 定义

这里是引用

  • 虚拟机栈:线程运行需要的内存空间。栈中放的是多个栈帧。
  • 栈帧:每个方法运行时需要的内存。(比如参数,局部变量,返回地址等。)
  • 每个线程只能有1个活动栈帧,对应着当前正在执行的那个方法。
  • Java虚拟机栈(Java Virtual Machine Stack),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(Stack Frame),对应着一次次的Java方法调用。是线程私有的,生命周期和线程一致。
  • 虚拟机栈中放的一个个栈帧。
    这里是引用

1.1 演示栈帧

在这里插入图片描述

    public static void main(String[] args) {method1();}private static void method1() {method2(1,2);}private static int method2(int a, int b) {int c=a+b;return c;}

2. 特点

  • 栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器。
  • 垃圾回收是否涉及栈内存?(不会,一次次方法执行后弹出栈自动被回收掉。)不存在GC ; 但是会存在OOM。

3. 线程运行诊断

3.1 案例1 cpu占用过多&解决

在这里插入图片描述

  1. 首先通过top命令定位哪个进程对cpu占用过高
  2. 根据pid 32655 我们只直到这个进程占用过高,但是我们需要知道那个线程导致的。使用ps H -eo pid,tid,%cpu | grep 32655 (用ps命令进一步定位哪个线程占用cpu过高)
    对Linux忘记的话借用deepseek回顾一下。在这里插入图片描述
    在这里插入图片描述
  3. 使用jdk提供的工具,jstack 进程id
    在这里插入图片描述
    我们已经知道是线程32655的导致的,可以直接根据线程编号,找到对应的输出信息。但是注意,32655是10进制的,但是jstack输出的信息是16进制的,所以先换算一下。
    在这里插入图片描述
    这里得到7F99,找到对应的代码。根据线程id7f99找到对应的包下的java代码,第8行。
    在这里插入图片描述
  • 发现原来是代码写了while(true) 死循环,导致CPU高。
  • 当然这里只是一个举例,实际情况要看线上的具体问题和代码。
    在这里插入图片描述

3.2 案例2 程序运行很长时间没有结果

这里是引用
一直滑倒最后看到一个死锁问题。这也是为啥不输出的原因。
在这里插入图片描述
再看具体的Java代码,其实也是很好理解。如果不理解也没关系,重温锁的知识或者死锁的产生等即可。
在这里插入图片描述

4. 拓展知识&问题辨析

4.1 栈的内存越大越好嘛?(不是)

栈越大 内存分配的栈越少,栈越少 线程越少。所以栈别太大,越小线程越多,也不能太小,太小栈溢出。【一般采用系统默认就好】

4.2 方法内的局部变量是否线程安全?(是线程安全的)

4.2.1 局部变量全在方法中

不会有线程安全问题,每个局部变量都在各自的栈帧中,线程不共享。

static void m1(){int x=0;for (int i=0;i<1000;i++){x++;}System.out.println(x);}

4.2.2 局部变量可能逃离方法

总结就是具体问题具体分析,要注意局部变量逃离本方法的问题。

//线程安全public static void m1() {StringBuilder sb = new StringBuilder();sb.append(1);sb.append(2);sb.append(3);System.out.println(sb);}//线程不安全,StringBuilder作为参数传递进来,就意味着有可能有其他线程能访问到它。就不再是线程私有的了。public static void m2(StringBuilder sb) {sb.append(1);sb.append(2);sb.append(3);System.out.println(sb);}//线程不安全,返回值也是StringBuilder,所以其他线程能访问到它。public static StringBuilder  m3() {StringBuilder sb = new StringBuilder();sb.append(1);sb.append(2);sb.append(3);return sb;}

4.3 可能抛出的异常?

StackOverFlowError?OutOfMemoryError?

  • Java 虚拟机规范允许Java栈的大小是动态的或者是固定不变的。 如果采用固定大小的Java虚拟机栈,那每一个线程的Java虚拟机栈容量可以在线程创建的时候独立选定。如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量,Java虚拟机将会抛出一个
    StackOverflowError 异常。
  • 如果Java虚拟机栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的虚拟机栈,那Java虚拟机将会抛出—个
    OutOfMemoryError 异常。

4.4 什么情况下会发生栈内存溢出?

一、局部数组过大。当函数内部的数组过大时,有可能导致堆栈溢出。(栈帧过大)
二、递归调用层次太多。递归函数在运行时会执行压栈操作,当压栈次数太多时,也会导致堆栈溢出。(栈帧过多)

对于第1种情况可能会发生在对象转换JSON的时候,如Dept中有Emp,Emp中有Dept,无线循环,可以使用
@JsonIgnore
private Dept dept; 忽略即可。

在这里插入图片描述

4.5 如何设置栈的大小?

在这里插入图片描述

-Xss size (即:-XX:ThreadStackSize)
-Xss256k或者-XX:ThreadStackSize=256k都可以
我们看栈大小window下可能会显示0
在这里插入图片描述

  • 设置10124K
    添加VM options在这里插入图片描述在这里插入图片描述

4.6 补充栈帧的内部结构

这里是引用

重要的两个(局部变量表&操作数栈)

可以IDEA插件中下载jclasslib插件来看代码

4.6.1 局部变量表

局部变量表(local variables)
● 局部变量表也被称之为局部变量数组或本地变量表。
● 定义为一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各类基本数据类型(8种)、对象引用(reference),以及returnAddress类型
● 局部变量表所需的容量大小是在编译期确定下来的,并保存在方法的Code属性的maximum local variables数据项中。在方法运行期间是不会改变局部变量表的大小的。
● 方法嵌套调用的次数由栈的大小决定。一般来说,栈越大,方法嵌套调用次数越多。对一个函数而言,它的参数和局部变量越多,使得局部变量表膨胀,它的栈帧就越大,以满足方法调用所需传递的信息增大的需求。进而函数调用就会占用更多的栈空间,导致其嵌套调用次数就会减少。
● 局部变量表中的变量只在当前方法调用中有效。在方法执行时,虚拟机通过使用局部变量表完成参数值到参数变量列表的传递过程。当方法调用结束后,随着方法栈帧的销毁,局部变量表也会随之销毁。
在这里插入图片描述

4.6.2 操作数栈

操作数栈(Operand Stack)
● 我们说Java虚拟机的解释引擎是基于栈的执行引擎,其中的栈指的就是操作数栈。
● 每一个独立的栈帧中除了包含局部变量表以外,还包含一个后进先出(Last-In-First-Out)的操作数栈,也可以称之为表达式栈(Expression Stack)。
● 操作数栈就是JVM执行引擎的一个工作区,当一个方法刚开始执行的时候,一个新的栈帧也会随之被创建出来,这个方法的操作数栈是空的。
● 每一个操作数栈都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期就定义好了,保存在方法的Code属性中,为max_stack的值。
栈中的任何一个元素都是可以任意的Java数据类型。
32bit的类型占用一个栈单位深度
64bit的类型占用两个栈单位深度
● 操作数栈,在方法执行过程中,根据字节码指令,并非采用访问索引的方式来进行数据访问的,而是只能通过标准的入栈(push)和出栈(pop)操作,往栈中写入数据或提取数据来完成一次数据访问。
某些字节码指令将值压入操作数栈,其余的字节码指令将操作数取出栈。使用它们后再把结果压入栈。比如:执行复制、交换、求和等操作
如果被调用的方法带有返回值的话,其返回值将会被压入当前栈帧的操作数栈中,并更新PC寄存器中下一条需要执行的字节码指令。

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

这里做一下简单的介绍,具体对于栈的深度,以及有过slot的复用,每个类型占多少大小等后续复习继续更新吧。

相关文章:

  • 解释一下NGINX的反向代理和正向代理的区别?
  • Nginx中root与alias的区别及用法
  • 如何使用WordPress区块(以及如何创建自定义区块)?
  • Lavavel学习笔记(Eloquent ORM/Swoole 定时任务)
  • 界面组件DevExpress WPF中文教程:Grid - 行和卡片
  • JVM监控工具
  • ceph osd 磁盘分区对齐
  • UE4游戏查找本地角色数据的方法-SDK
  • 科学养生:解锁现代健康生活新方式
  • 软考中级软件设计师——数据结构篇
  • C++学习之打车软件—JNI终端编程业务④https协议session开发
  • Vue 3 实现 Excel 表格解析的完整指南
  • 【python实用小脚本-79】[HR转型]Excel难民到数据工程师|用Python实现CSV秒转JSON(附HRIS系统对接方案)
  • React从基础入门到高级实战:React 基础入门 - 列表渲染与条件渲染
  • 物联网 温湿度上传onenet
  • GO语言学习(九)
  • 如何在Mac 上使用Python Matplotlib
  • 网络抓包命令tcpdump及分析工具wireshark使用
  • AI架构师的新工具箱:ChatGPT、Copilot、AutoML、模型服务平台
  • Java常用数据结构底层实现原理及应用场景
  • 企业网站策划书ppt/如何申请一个网站域名
  • 国家认可的赚钱游戏/电脑优化工具
  • 上海由多少家网站建设公司/百度快照优化seo
  • 广州建设网站服务/长沙市云网站建设
  • 响应式网站是做多大尺寸/公司建网站多少钱
  • 承德北京网站建设/新品牌推广方案