jvm-虚拟机栈笔记【详细】
一:虚拟机栈的概述
1:虚拟机栈的概念:
Java虚拟机栈(Java virtual Machine stack) ,早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个的栈帧(stack Frame) (下文有详细介绍),对应着一次次的Java方法调用。
2:虚拟机栈的基本性质:
- 线程私有
- 生命周期和线程一致
- 主管Java程序的运行,它保存方法的局部变量(8种基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回。
3:虚拟机栈的优点和缺点:
- 优点:跨平台,指令集小,编译器容易实现
- 缺点: 性能下降,实现同样的功能需要更多的指令。
4:虚拟机栈构成的基本栈帧介绍:
- 栈帧是用于虚拟机执行是方法调用和方法执行是的数据结构,它是虚拟机栈的基本元素。每一个方法从调用到方法结束都对应着一个栈帧入栈、出栈的过程,最顶部的栈帧称为当前栈帧,栈帧所关联的方法为当前方法,定义这个方法的类称为当前类,该线程中,虚拟机有且只会对当前栈帧进行操作
- 栈帧的作用:存储数据,部分过程处理,处理动态链接,方法返回值和异常分派
- 栈帧的构造图
- 每一个栈帧包含的内容有局部变量表、操作数栈、动态链接、方法返回值和一些附加信息(下文会进行详细介绍),在编译代码时,栈帧需要多大的局部变量表和操作数栈都可以完全确定,并且写入到code属性中,如图:
5:虚拟机如果执行一个方法(在文章的最后会详细讲方法调用):
首先我们的方法被编译成了字节码,并生成了可执行的命令。通过程序计数器,虚拟机会一行一行的执行命令,直到进入一个新的方法入口,对应虚拟机栈也就是新的栈帧入栈,当前栈帧改变,又或者遇到返回指令或出现异常结束了方法,对应虚拟机也就是出栈。
二:局部变量表:
- 局部变量表定义为一个数字数组,用于存放 方法参数和局部变量。在class文件的方法表的code属性的max_locals指定了该方法所需局部变量表的最大容量
- 局部变量表的基本大卫为变量槽(slot,下文有详细介绍);虚拟机是通过索引定位的方式使用局部变量表
- 当调用方法是非static 方法时,局部变量表中第0位索引的 Slot 默认是用于传递方法所属对象实例的引用(reference),即 “this” 关键字指向的对象。分配完方法参数后,便会依次分配方法内部定义的局部变量。
- 为了节省栈帧空间,局部变量表中的 Slot 是可以重用的。因为即使是一个方法内,也是存在作用域的,当离开了某些变量的作用域之后,这些变量对应的 Slot 空间就可以交给其他变量使用。但是这种机制有时候会影响垃圾回收行为,原因很简单,当离开某个作用域时,如果没有新的变量值覆盖之前作用域内的变量(指reference)空间,那么当垃圾回收时,则该引用对应的java堆中的内存则不允许被回收,因为局部变量表中还存在该引用。所以问题在于虚拟机并没有主动清理局部变量表中离开作用域的变量值,而是采用新盖旧的方法被动清理。
main方法代码
public static void main(String[] args) {int a =2 ;{int b =3 ;b +=a ;}int c = 5 ;Son son = new Son();son.show();}
- 所以很明显,局部变量表的作用就是记录执行该方法时会使用到的变量值,它可以说这个方法的数据池,是我们方法中变量的化身,相当于把我们方法中所需要的变量整合成一个数组对象或集合对象,这个对象的名称就叫做局部变量表。
三:变量槽(variable slot)
一个变量槽大小一般为32位,其实《Java虚拟机规范》中并没有明确指出 一个变量槽应占用的内存空间大小,如在64位虚拟机中使用了64位的物理内存空间去实现一个变量槽,但是虚拟机仍要使用对齐和补白的手段 让变量槽在外观上看起来与32位虚拟机中的一致,也就是说64位的变量槽实际上只用到了前32位来存储数据,其余32位是不能使用的。对于boolean、byte、char、short、int、 float、reference 和returnAddress这8种数据类型,可以使用32位或更小的物理内存来存储,而64位的数据类型(double、long),Java虚拟机会以高位对齐的方式为其分配两个连续的变量槽空间(64位的虚拟机分配的也是两个变量槽)。
四:操作数栈
- 操作数栈(Operand Stack)也常被称为操作栈,它是一个后入先出(Last In First Out,LIFO)栈。同局部变量表一样,操作数栈的最大深度也在编译的时候被写入到Code属性的max_stacks数据项 之中。操作数栈的每一个元素都可以是包括long和double在内的任意Java数据类型。32位数据类型所占的栈容量为1,64位数据类型所占的栈容量为2。
- Java虚拟机的解释执行引擎被称为“基于栈的执行引擎”,里面的“栈”就是操作数栈。当一个方法刚刚开始执行的时候,这个方法的操作数栈是空的,在方法的执行过程中,会有各种字节码指令往操作数栈中写入和提取内容,也就是出栈和入栈操作。譬如在做算术运算的时候是通过将运算涉及的操作数栈压入栈顶后调用运算指令来进行的,又譬如在调用其他方法的时候是通过操作数栈来进行方法参数的传递。
- 举个例子,例如整数加法的字节码指令iadd,这条指令在运行的时候要求操作数栈中最接近栈顶的两个元素已经存入了两个int型的数值,当执行这个指令时,会把这两个int 值出栈并相加,然后将相加的结果重新入栈。
代码段:
int a=1;
int b=2;
int c=a+b;
对应的指令
iload_0 // 将局部变量表0号索引的值入操作数栈
iload_1 // 将局部变量表1号索引的值入操作数栈
iadd // 操作数栈去除前两位相加,放入栈顶
istore_2 // 操作数栈顶元素出栈,放入局部变量表2号索引
五:动态连接
每一个栈帧内部都包含一个指向当前方法所在类型的运行时常量池的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(dynamic Linking)。比如: invokedynamic指令
在java源文件被编译到字节码文件时,所有的变量和方法引用都为符号引用,保存在class文件的常量池中,比如:描述一个方法调用了另外的其他方法时,就是常量池中指向方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
这里补充一个方法的调用:
1:在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关。
- 静态链接:放一个字节码文件被装载进jvm内部时,如果被调用的目标方法在编译器可知且运行期保持不变时,这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接
- 动态链接:如果被调用的方法在编译期无法确定下来,也就是说,只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用转换过程具备动态性,因此也就被称之为动态链接
2:对应的方法绑定机制为:早期绑定(Early Binding)和晚期绑定(Late Binding)。绑定是一个字段、方法或者类在符号引用被替换为直接引用的过程,这仅仅发生一次。
- 早期绑定:指定调用的目标方法在编译期可知,且运行期保持不变,可将这个方法也所属类型进行绑定
- 晚期绑定:只被调用的目标方法在编译期无法被确定下来,只能够在程序运行期间根据实际的类型绑定相关的方法
3:虚方法与非虚方法:
- 非虚方法:如果方法在编译器就确定了具体的调用版本,这个版本在运行时是不可改变的,这样的方法称为非虚方法。静态方法,私有方法、final方法、实例构造器、父类方法都是非虚方法
- 虚方法:除了上述说的虚方法,其他都是虚方法(多态,父类引用指向子类对象)
4:虚拟机中提供了以下几条方法调用指令:
- 普通调用指令:
- invokestatic:调用静态方法,解析阶段确定唯一方法版本
- invokespecial:调用<init>方法、私有及父类方法,解析阶段确定唯一方法版本
- invokevirtual:调用所有虚方法
- invokeinterface:调用接口方法
- 动态调用指令:
- invokedynamic:动态解析出需要调用的方法,然后执行
- 直到Java8的Lambda表达式的出现,invokedynamic指令的生成,在Java中才有了直接的生成方式
- 前四条指令固化在虚拟机内部,方法调用执行不可认为干预,而invokedynamic指令则支持由用户确定方法版本。其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。
5:方法重写的本质
- 找到操作数栈栈顶的第一个元素所执行的对象的实际类型,记做C
- 如果在类型C中找到与常量中描述符合简单名称都相符的方法,则进行访问权限校验
- 如果通过则返回这个发放的直接引用,查找过程结束
- 如果不通过,则返回java.lang.IllegalAccessError 异常
- 否则按照继承关系从下往上一次对C的各个父类进行第二步的搜索和验证过程
- 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常
6:虚方法表:JVM采用在类的方法区建立一个虚方法表(virtual method table) (非虚方法不会出现在表中)来实现。使用索引表来代替查找。每个类中都有一个虚方法表,表中存放着各个方法的实际入口。虚方法表会在类加载的链接阶段被创建并开始初始化,类的变量初始值准备完成之后, JVM会把该类的方法表也初始化完毕。
六:方法返回地址
- 方法返回地址:存放调用该方法的PC寄存器的值
- 当一个方法开始执行以后,只有两种方法可以退出当前方法:
- 当执行遇到返回指令,会将返回值传递给上层的方法调用者,这种退出的方式称为正常完成出口(Normal Method Invocation Completion),一般来说,调用者的PC计数器可以作为返回地址。
- 当执行遇到异常,并且当前方法体内没有得到处理,就会导致方法退出,此时是没有返回值的,称为异常完成出口(Abrupt Method Invocation Completion),返回地址要通过异常处理器表来确定。
- 当方法返回时,可能进行3个操作:
- 恢复上层方法的局部变量表和操作数栈
- 把返回值压入调用者调用者栈帧的操作数栈
- 调整 PC 计数器的值以指向方法调用指令后面的一条指令
7:附加信息
sdd
sdasd:《Java虚拟机规范》允许虚拟机实现增加一些规范里没有描述的信息到栈帧之中,例如与调试、性能收集相关的信息,这部分信息完全取决于具体的虚拟机实现。
8:面试题:
举例栈溢出的情况
StackOverflowError 通过设置-XSS设置栈的大小:OOM。如果线程请求的栈深度大于虚拟机所允许的深度,会抛出StackOverflowErroer异常,如果虚拟机栈扩展是无法申请到足够的内存,就会抛出OutOfMenioryError
调整栈大小,就能保证不出现溢出吗?
不能,如果一个递归没有退出条件,就死翘翘了
分配栈的内存越大越好吗
不是,因为整个虚拟机是确定下来的内存,如果栈大的话其他就得小
垃圾回收是否涉及到虚拟机栈
不会的
方法 中定义的局部变量是否线程安全
具体问题具体分析 。如果只有一个线程才可以操作此数据,则必是线程安全的。如果有多个线程操作此数据,则此数据是共享数据。如果不考虑同步机制的话,会存在线程安全问题。
例子代码:
public class StringBuilderTest {int num = 10;//s1的声明方式是线程安全的public static void method1(){//StringBuilder:线程不安全StringBuilder s1 = new StringBuilder();s1.append("a");s1.append("b");//...}//sBuilder的操作过程:是线程不安全的 参数是StringBuilderpublic static void method2(StringBuilder sBuilder){sBuilder.append("a");sBuilder.append("b");//...}//s1的操作:是线程不安全的 返回了StringBuilderpublic static StringBuilder method3(){StringBuilder s1 = new StringBuilder();s1.append("a");s1.append("b");return s1;}//s1的操作:是线程安全的public static String method4(){StringBuilder s1 = new StringBuilder();s1.append("a");s1.append("b");return s1.toString();}public static void main(String[] args) {StringBuilder s = new StringBuilder();new Thread(() -> {s.append("a");s.append("b");}).start();method2(s);}
}
jvm-虚拟机栈笔记【详细】相关推荐
- java中的vm自变量_java中JVM虚拟机内存模型详细说明
java中JVM虚拟机内存模型详细说明 JVM的内部结构如下图: 一个优秀Java程序员,必须了解Java内存模型.GC工作原理,以及如何优化GC的性能.与GC进行有限的交互,有一些应用程序对性能要求 ...
- jvm虚拟机栈的作用
jvm虚拟机栈的作用 jvm虚拟机栈栈帧的组成 jvm虚拟机栈,也叫java栈,它由一个个的栈帧组成,而栈帖由以下几个部分组成 局部变量表-存储方法参数,内部使用的变量 操作数栈-在变量进行存储时,需 ...
- 深入理解JVM虚拟机读书笔记——内存模型与线程
注:本文参考自周志明老师的著作<深入理解Java虚拟机(第3版)>,相关电子书可以关注WX公众号,回复 001 获取. 1. Java内存模型 JMM概述: Java 内存模型指的是 JM ...
- jvm虚拟机学习笔记
什么是jvm 定义:java虚拟机,java二进制字节码运行的环境 好处 一次编译,到处运行 自动内存管理,垃圾回收功能 数组下标越界检查 多态(虚方法表) 比较jvm,jre,jdk jvm:只是一 ...
- JVM虚拟机(最详细的jvm总结)
我跟你讲一下我对于java的理解吧 java最大的特点就是平台无关性,一次编译,到处运行. Java源码首先被编译成字节码,再由不同平台的JVM进行解析,JAVA语言在不同的平台上运行时不需要进行重新 ...
- JVM 虚拟机栈详解
当Java虚拟机运行程序时.每当一个新的线程被创建时.Java 虚拟机都会分配一个虚拟机栈,Java虚拟机栈是以帧为单位来保存线程的运行状态.Java栈只会有两种操作:以帧为单位进行压栈跟出栈. 某个 ...
- 深入理解JVM虚拟机读书笔记——锁优化
注:本文参考自周志明老师的著作<深入理解Java虚拟机(第3版)>,相关电子书可以关注WX公众号:兴趣使然的草帽路飞,回复 001 获取. 1. Java语言中的线程安全 按照线程安全的& ...
- JVM虚拟机栈的栈帧结构中动态链接的理解
<深入理解Java虚拟机>书中原文写到: 每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接(Dynamic Linking).通过第 ...
- 深入理解JVM虚拟机读书笔记【第十二章】Java内存模型与线程
12.1 概述 12.2 硬件的效率与一致性 12.3 Java内存模型 12.3.1 主内存与工作内存 12.3.2 内存间交互操作 12.3.3 对于volatile型变量的特殊规则 12.3.4 ...
- 【JVM】运行时数据区介绍,程序计数器和虚拟机栈详解
JVM越来越是Java面试中的重头戏,今天来总结一下JVM运行时数据区的相关内容. 文章目录 JVM运行时数据区 JVM运行时数据区内部结构 程序计数器(PC寄存器) 程序计数器的介绍 PC寄存器的实 ...
最新文章
- vsftpd用户配置 No.2
- 【自动驾驶】4.分布式实时通信——DDS技术
- Beginning C# Objects 读书笔记(一)
- libsvm 数据预处理 模块化程序
- 危机永远回来,只是什么时候的问题
- Objective-C 之Block(2)
- leetcode209. 长度最小的子数组(暴力+滑动窗口)
- python的注释符_Python3 注释和运算符
- sqlmap源码阅读系列检查是否满足依赖
- Yii 多表关联relations,需要与with()方法联合使用
- 热烈庆祝排名进入5000
- Git 管理工具 SourceTree 的使用(上手简单,不熟悉git命令的开发者必用)
- 基于RS485通讯总线的ModbusRtu协议 上位机项目源代码
- 如何简单快速地调整图片大小
- 手机上的环境光传感器
- js从地址栏获取参数
- Oracle 根据汉字返回对应拼音的 函数
- 找不到.jack_您不了解Jack关于Firefox的知识!
- edcoder数据结构第1关:基于BF算法的病毒感染监测
- html图片左侧留白,HTML+CSS布局img图片元素出现空白的问题
热门文章
- 机器学习中的数学——Nesterov Momentum
- 一文搞懂如何兼容苹果HomeKit?智汀助你轻松打造智慧家庭
- 关于我用iVX沉浸式体验了一把0代码创建电影院购票小程序这件事
- 羽毛球 - 正手高球(杀球、吊球、高远球)
- Nginx 配置多个SSL域名指向不同端口
- 相片打印机原理_手机照片打印机的原理是什么 便携式照片打印机到底好不好...
- 国内外知名Android开发者GitHub、Blog
- 想做一个SSL代理的集群中,有很多问题需要解决,I am all grateful to you for your advice
- 捕获组合键 键盘组合键
- Mysql数据库版本高低引起的group by问题