JVM stacks

image
Oracle 官方文档

2.5.2. Java Virtual Machine Stacks
  Each Java Virtual Machine thread has a private Java Virtual Machine stack, created at the same time as the thread.A Java Virtual Machine stack stores frames (§2.6).A Java Virtual Machine stack is analogous类似的 to the stack of a conventional传统的 language such as C: it holds local variables and partial传统的 results, and plays a part in method invocation调用 and return.Because the Java Virtual Machine stack is never manipulated操作 directly except除…之外 to push and pop frames, frames may be heap allocated分配的.The memory for a Java Virtual Machine stack does not need to be contiguous连续的.

  In the First Edition版本 of The Java® Virtual Machine Specification规范, the Java Virtual Machine stack was known as被称为 the Java stack.

  This specification规范 permits允许 Java Virtual Machine stacks either to be of a fixed固定的 size or to dynamically动态的 expand扩大 and contract缩小 as required by根据需要 the computation计算.If the Java Virtual Machine stacks are of a fixed size, the size of each Java Virtual Machine stack may be chosen选择 independently独立地 when that stack is created.

  A Java Virtual Machine implementation实现 may provide the programmer程序员 or the user control over the initial初始 size of Java Virtual Machine stacks,as well as, in the case of dynamically expanding or contracting Java Virtual Machine stacks, control over the maximum and minimum sizes.

  The following exceptional异常的 conditions情况 are associated有关联的 with Java Virtual Machine stacks:

  • If the computation计算 in a thread requires a larger Java Virtual Machine stack than is permitted被允许, the Java Virtual Machine throws a StackOverflowError.
  • If Java Virtual Machine stacks can be dynamically expanded,

and expansion扩大 is attempted尝试 but insufficient memory内存不足 can be made available to effect实现 the expansion,or if insufficient memory can be made available to create the initial初始 Java Virtual Machine stack for a new thread,the Java Virtual Machine throws an OutOfMemoryError.

不同平台cpu架构不同,不能基于寄存器来设计java指令集。为了实现跨平台,java指令集根据LIFOlast-in-first-outstack设计 - 优点:跨平台、指令集小、编译器容易实现 - 缺点:性能下降、实现同样功能需要更多的指令

pc register一样,JVM Stack线程私有的,生命周期与线程相同。
JVM Stack 描述的是Java方法执行的线程内存模型:每个方法被执行的时候,JVM都会同步创建一个Stack Frame

Stack Frame用于存储

  • Local Variables局部变量表,影响stack frame大小
  • Operand Stacks操作数栈,影响stack frame大小
  • Dynamic Linking动态链接,指向运行时常量池的方法引用
  • Method Invocation Completion方法调用结束
    • Normal Method Invocation Completion方法调用正常结束
    • Abrupt Method Invocation Completion方法调用异常结束

jvm Stack 特点:

  • 每一个方法被调用直至执行完毕的过程,就对应着一个Stack FrameJVM Stack中从入栈出栈的过程。
    • 方法执行-入栈
    • 方法执行结束-出栈
  • jvm Stack 是一种快速有效的分配存储方式,访问速度仅次于The pc Register
  • jvm Stack 没有 GC

jvm Stack 可能出现的内存错误;jvm规范允许jvm Stack的大小固定不变或动态扩展

  • StackoverflowError:jvm Stack的大小固定,每个线程的jvm Stack大小在线程创建时独立设置,如果线程请求分配的jvm Stack大小超过jvm stack允许的最大容量,JVM将抛出StackOverflowError
  • OutOfMemmoryError
    • 动态扩展的jvm stack,尝试扩展时无法申请到足够的内存,JVM将抛出OutOfMemoryError
    • 创建新的线程时没有足够的内存去创建对应的jvm stack,JVM将抛出OutOfMemoryError

jvm Stack 是运行时的单位,而 Heap 是存储的单位
jvm Stack 解决程序的运行问题,即程序如何执行。参与方法的调用和返回,每个线程在创建时都会创建自己的jvm Stack,内部保存一个个stack frame,对应一次次的方法调用
Heap 解决数据存储问题,即数据怎么存放,存放到哪。new创建的对象实例都存放在Heap
方法嵌套调用的次数由jvm stack的大小决定

  • jvm stack越大,方法嵌套调用次数越多
  • 对一个函数来说,参数局部变量越多,local variables越大,则其stack frame越大,该函数调用会占用更多的jvm stack空间,导致嵌套调用次数减少
  • local variables中的局部变量只在当前方法调用中有效
    • 方法执行时,JVM通过使用local variables来完成参数值参数变量列表的传递。
    • 方法调用结束后,随着stack frame的出栈销毁,local variables也会随之销毁

Stack Frame

Oracle 官方文档
   A frame is used to store data and partial部分的 results, as well as to perform执行 dynamiclinking, return values for methods, and dispatch调遣 exceptions.

A new frame is created each time a method is invoked. A frame is destroyed when its method invocation调用 completes, whether或者…(或者) that completion is normal or abrupt (it throws an uncaught未捕获 exception). Frames are allocated分配 from the Java Virtual Machine stack (§2.5.2) of the thread creating the frame. Each frame has its own array of local variables (§2.6.1), its own operand stack (§2.6.2), and a reference to the runtime constant pool (§2.5.5) of the class of the current method.

A frame may be extended扩展 with additional附加的 implementation-specific具体实现 information, such as debugging information.

The sizes of the local variable array and the operand stack are determined确定的 at compile-time编译时期 and are supplied提供 along with随同…一起 the code for the method associated with与…有关 the frame (§4.7.3).Thus因此 the size of the frame data structure depends only on the implementation实现 of the Java Virtual Machine, and the memory for these structures can be allocated分配 simultaneously同时 on method invocation调用.

Only one唯一 frame, the frame for the executing method, is active at any point in a given thread of control. This frame is referred to被称为 as the current frame, and its method is known as被称为 the current method. The class in which the current method is defined定义 is the current class. Operations操作 on local variables and the operand stack are typically通常 with reference to关于 the current frame.

A frame ceases结束 to be current当前帧 if its method invokes another method or if its method completes. When a method is invoked, a new frame is created and becomes current当前帧 when control控制权 transfers转让 to the new method. On method return, the current frame passes沿某方向移动 back the result of its method invocation, if any如果有的话, to the previous先前的 frame. The current frame is then discarded丢弃 as the previous先前的 frame becomes the current当前帧 one.

Note that a frame created by a thread is local to that thread and cannot be referenced引用 by any other thread.

stack frame是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息
在一条活动线程中,一个时间点上只会有一个活动的栈帧栈顶栈帧,即当前正在执行的方法的栈帧是有效的,这个栈帧称为current当前栈帧
current frame 对应的方法是current method
定义current method的类是current class
Execution Engine执行引擎运行的所有字节码指令只针对current frame进行操作
a方法中调用b方法: b方法对应的stack frame会被创建并入栈,成为栈顶栈帧b方法对应的stack frame成为新的current
b方法正常执行完,b方法对应的stack frame出栈,则a方法对应的stack frame重新变为栈顶栈帧,成为新的current

JAVA方法两种返回函数方式stack frame出栈

  • 函数正常返回,使用return指令
  • 抛异常未用try-catch捕获处理

stack frame中与性能调优有关的主要是local variables

stack frame中允许携带与JVM实现有关的一些附加信息:对程序调试提供支持的信息

Local Variables

Oracle 官方文档
Each frame (§2.6) contains an array of variables known as its local variables.
The length of the local variable array of a frame is determined确定 at compile-time编译时期
and supplied提供 in the binary representation二进制表示法 of a class or interface along with the code for the method associated with与…有关 the frame (§4.7.3).

A single local variable can hold a value of type boolean, byte, char, short, int, float, reference, or returnAddress.
A pair of local variables can hold a value of type long or double.

Local variables are addressed by indexing.
The index of the first local variable is zero.
An integer is considered经过深思熟虑的 to be an index into the local variable array if and only if当且仅当 that integer is between zero and one less than the size of the local variable array.

A value of type long or type double occupies占用 two consecutive连续的 local variables.
Such a value may only be addressed using the lesser index.
For example, a value of type double stored in the local variable array at index n actually occupies占用 the local variables with indices索引 n and n+1;
however, the local variable at index n+1 cannot be loaded from. It can be stored into. However, doing so invalidates使无效 the contents内容 of local variable n.

The Java Virtual Machine does not require n to be even偶数.
In intuitive terms直观地说, values of types long and double need not be 64-bit aligned对齐 in the local variables array.
Implementors实现者 are free to decide决定 the appropriate合适的 way to represent表示 such values using the two local variables reserved保留 for the value.

The Java Virtual Machine uses local variables to pass parameters on method invocation.
On class method invocation, any parameters are passed in consecutive连续的 local variables starting from local variable 0.
On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language).
Any parameters are subsequently随后 passed in consecutive连续的 local variables starting from local variable 1.

Local Variables是一个Array,存储方法参数和定义在方法体内的局部变量,数据类型包括:编译期可知的各种JVM基本数据类型、reference、returnAddress JVM基本数据类型bytebooleanshortcharintfloatlongdouble
reference对象引用类型:并不等同于对象本身,可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置
returnAddress:指向一条字节码指令地址

Local Variables建立在线程上是线程的私有数据,因此不存在数据安全问题
Local Variables中的存储单位以Slot变量槽来表示。数据从Local Variables Array 的索引0位置开始存放,占用64bitlongdouble类型数据会占用两个Slot\;使用时,用其占用的第一个Slot的index,其余的数据只占用一个Slot

Local Variables所需的内存空间在编译期完成分配,并保存在方法Code属性的maximum local variables数据项中
当进入一个方法时,这个方法需要在Stack Frame中分配多大的Local Variables空间是完全确定的,
方法运行期间不会改变Local Variables的大小大小指Slot的数量
JVM真正使用多大的内存空间来实现一个Slot,由具体的JVM实现自行决定譬如按照一个Slot占用32bit、64bit,或者更多

当一个实例方法被调用时,方法参数和方法体内部定义的局部变量将会按照声明顺序放置到local variables array。如果current frame是由构造方法或者实例方法创建的,那么该对象引用this将会存放在索引为0的Slot,其余变量按照位置顺序继续排列。静态方法不存在对象引用this,其local variables不会保存this,所以静态方法中不能使用this

如果一个局部变量过了其作用域,那么在其作用域之后申明的新的局部变量就很可能会复用过期局部变量的slot,从而达到节省资源的目的

local variables中的变量也是重要的垃圾回收根结点,只要被local variables直接间接引用的对象都不会被回收

Operand Stacks

Oracle 官方文档
Each frame (§2.6) contains a LIFOlast-in-first-out stack known as its operand stack.
The maximum depth of the operand stack of a frame is determined确定 at compile-time编译时期 and is supplied提供 along with the code for the method associated with与…有关 the frame (§4.7.3).

Where it is clear明确的 by context上下文, we will sometimes refer称…(为) to the operand stack of the current frame as simply简单地 the operand stack.

The operand stack is empty when the frame that contains it is created.
The Java Virtual Machine supplies提供 instructions(计算机的)指令 to load constants常量 or values from local variables or fields字段 onto the operand stack.
Other Java Virtual Machine instructions(计算机的)指令 take operands from the operand stack, operate on them, and push the result back onto the operand stack.
The operand stack is also used to prepare把…预备好 parameters to be passed to methods and to receive method results.

For example, the iadd instruction (§iadd) adds two int values together.
It requires that the int values to be added be the top two values of the operand stack, pushed there by previous先前的 instructions.
Both of the int values are popped from the operand stack.They are added, and their sum is pushed back onto the operand stack.
Subcomputations子计算 may be nested嵌套 on the operand stack, resulting in导致 values that can be used by the encompassing涉及 computation计算.

Each entry on the operand stack can hold a value of any Java Virtual Machine type,including a value of type long or type double.

Values from the operand stack must be operated upon在……上 in ways appropriate to适用于 their types.
It is not possible, for example, to push two int values and subsequently随后 treat把…看作 them as a long or to push two float values and subsequently add them with an iadd instruction.
A small number of少数 Java Virtual Machine instructionsthe dup instructions (§dup) and swap (§swap)operate on run-time data areas as raw原始的 values without regard关注 to their specific具体的 types;
these instructions(计算机的)指令 are defined in such a way必须如此 that they cannot be used to modify修改 or break up individual单独的 values.
These restrictions限制规定 on operand stack manipulation操作 are enforced强制性的 through通过 class file verification验证 (§4.10).

At any point in time, an operand stack has an associated相关的 depth, where a value of type long or double contributes添加 two units单位 to the depth and a value of any other type contributes添加 one unit.

《深入理解Java虚拟机》
JVM的解释执行引擎被称为“基于栈operand stack的执行引擎execution engine

在概念模型中,两个不同的Stack frame作为不同方法的jvm stack元素,是完全相互独立的。
但是在大多虚拟机的实现里都会进行一些优化处理,令两个Stack frame出现一部分重叠。
让下面stack frame的部分operand stack与上面stack frame的部分local variables重叠在一起,这样做不仅节约了一些空间,
更重要的是在进行方法调用时就可以直接共用一部分数据,无须进行额外的参数复制传递
两个stack frame数据共享

operand stack 在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈push或出栈pop。某些字节码指令将值压入operand stack,其余的字节码指令将操作数取出operand stack,使用复制、交换、求和后把结果压入operand stack

如果被调用的方法带有返回值,其返回值将会被压入current stack frameoperand stack中,并更新The pc Register中下一条需要执行的字节码指令
operand stack中元素的数据类型必须与字节码指令严格匹配,这由编译器编译期间进行验证,同时在类加载过程中的类检验阶段数据流分析阶段要再次验证
operand stack主要保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
operand stackJVM execution engine的一个工作区,当一个方法刚开始执行的时候,一个新的stack frame也会随之被创建,这个方法的operand stack是空的(已创建)
每一个operand stack 都会拥有一个明确的栈深度用于存储数值,其所需的最大深度在编译期确定,保存在方法的Code属性中,为max_stack的值,与local variables大小无关
operand stack中任何一个元素都是任意的Java数据类型,与local variablesslot类似

  • 32bit的类型占用一个operand stack单位深度
  • 64bit的类型占用两个operand stack单位深度

operand stack使用数组实现, 不使用访问数组索引进行数据访问,只能通过标准的入栈push和出栈pop来完成数据访问

Dynamic Linking

Oracle 官方文档
Each frame (§2.6) contains a reference to the run-time constant pool (§2.5.5) for the type of the current method to support dynamic linking of the method code.The class file code for a method refers表示 to methods to be invoked and variables to be accessed访问 via通过 symbolic符号 references. Dynamic linking translates转换 these symbolic method references into concrete具体的 method references, loading classes as necessary to resolve解决 as-yet-undefined尚未定义 symbols, and translates variable accesses访问 into appropriate恰当的 offsets位置 in storage存储 structures associated with与…有关 the run-time location of these variables.

This late binding延迟绑定 of the methods and variables makes使 changes in other classes that a method uses less likely可能的 to break this code.

《深入理解Java虚拟机》每个Stack Frame都包含一个指向run-time constant pool运行时常量池中该Stack Frame所属方法的引用,持有这个引用是为了支持方法调用过程中的Dynamic Linking比如invokedynamic指令

Java源文件编译为字节码文件时,所有的变量method references方法引用都作为symbolic reference符号引用保存在class文件的常量池中。字节码中的方法调用指令class文件的常量池里指向方法的symbolic reference符号引用作为参数。这些symbolic reference符号引用一部分会在类加载阶段或者第一次使用的时候就被转化为concrete具体的 method references,这种转化被称为静态解析。另外一部分将在每一次运行期间都转化为concrete具体的 method references,这部分就称为Dynamic Linking

Dynamic Linking 通过symbolic reference符号引用指向run-time constant pool运行时常量池中的method references方法引用

方法的调用–多态

JVM 将symbolic reference符号引用 #3#2转换为直接引用与方法的绑定机制有关

  • static linking静态链接
    • 当一个字节码文件被装载进JVM内部时,如果被调用方法在编译期可知,且运行期保持不变时。将被调用方法的symbolic reference符号引用转换为直接引用的过程称为static linking静态链接
  • dynamic linking动态链接
    • 如果被调用方法在编译期无法被确定下来,也就是说只能够在程序运行期将调用方法的symbolic reference符号引用转换为直接引用。由于这种引用转换过程具备动态性因此也被称为dynamic linking动态链接

方法的绑定机制:是一个字段方法symbolic reference符号引用被替换为直接引用的过程,仅发生一次

  • early binding早期绑定:被调用的目标方法在编译期可知,且运行期保持不变即可将这个方法与所属的类型进行绑定,因此可以使用static linking静态链接的方式将symbolic reference符号引用转换为直接引用
  • late binding延迟绑定:如果被调用的方法在编译期无法被确定下来只能在程序运行期根据实际的类型,绑定相关的方法,这种绑定方式就是late binding延迟绑定

虚函数:Java中任何一个普通方法其实都具备虚函数的特征,相当于c++语言中的虚函数c++中需要使用关键字virtual来显示定义。如果java程序中不希望某个方法拥有虚函数的特征,使用关键字final修饰不能被重写,编译期确定,不再具备多态性

多态类继承,且重写方法

  • 子类对象的多态性前提:
    • 类的继承
    • 方法的重写
  • 面向对象的高级语言,尽管在语法风格上存在差异,但是都支持封装继承多态等面向对象特性
    • 封装
    • 继承
    • 多态
  • 具备多态性,就具备early bindinglate binding两种绑定方式,可以在编译期确定具体调用哪个方法

  • 虚方法
    • 具备多态性的方法
    • 除了静态方法私有方法final方法实例构造器父类方法
    • invokevirtual指令:调用所有虚方法final修饰的方法为非虚方法,也使用invokevirtual指令
    • invokeinterface指令:调用接口方法
  • 非虚方法
    • 不具备多态性的方法
    • invokestaticinvokespecial指令调用的方法称为非虚方法,其余的final修饰的方法为非虚方法称为虚方法
    • 方法在编译期确定具体的调用版本,这个版本在运行时不可变
    • 静态方法私有方法final方法实例构造器父类方法都是非虚方法
    • invokestatic指令:调用静态方法ClassLoaderSubSystem.Linking.Resolve阶段解析阶段确定唯一方法版本
    • invokespecial指令:调用<init>方法、私有方法父类方法ClassLoaderSubSystem.Linking.Resolve阶段解析阶段确定唯一方法版本
  • 动态调用指令
    • invokedynamic指令:动态解析出需要调用的方法,然后执行。支持由用户确定方法版本
    • invokevirtualinvokeinterfaceinvokestaticinvokespecial指令固化在JVM内部,方法的调用执行不可人为干预

方法重写的本质

  • 找到operand stack栈顶元素所执行的对象的实际类型,记作c当调用一个对象的方法时,会先把该方法的对象压入operand stack,通常为invokevirtual指令
  • 如果在类型c中找到与常量池描述符简单名称都相符的方法查找c中有没有该方法,则进行访问权限校验
    • 如果访问权限校验通过,则返回这个方法的直接引用,查找过程结束。
    • 如果访问权限校验不通过,则返回java.lang.IllegalAccessError异常。
  • 如果在类型c中没找到与常量池描述符简单名称都相符的方法查找c中有没有该方法,按照继承关系从下往上依次对c的各个父类进行第2步的搜索和验证
  • 如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常

java.lang.IllegalAccessError异常jar冲突可能会出现: 程序试图访问或修改一个属性或调用一个方法,当这个属性或方法没有权限访问,一般会引起编译器异常,这个错误如果发生在运行时,就说明一个类发生了不兼容的改变。

虚方法表

  • 在面向对象编程OOP,频繁的使用到动态分派,若每次动态分派的过程都要重新在类的方法元数据中搜索合适的目标,会影响执行效率
  • JVM采用在类的方法区建立一个virtual method table虚方法表,使用索引表来代替查找。非虚方法不会出现在表中。
  • 每个类中都有一个virtual method table虚方法表,存放着各个方法的实际入口
  • virtual method table虚方法表ClassLoaderSubSystem.Linking.resolve阶段将常量池内的符号引用转换为直接引用被创建并开始初始化,类的变量初始值准备完成后,JVM会把该类的方法表也初始化完毕

Method Invocation Completion

《深入理解Java虚拟机》当一个方法开始执行后,只有两种方式退出这个方法。

  • 第一种方式是执行引擎遇到任意一个方法返回的字节码指令,这时候可能会有返回值传递给上层的方法调用者调用当前方法的方法称为调用者或主调方法,方法是否有返回值以及返回值的类型将根据遇到何种方法返回的字节码指令来决定,这种退出方法的方式称为Normal Method Invocation Completionsub正常调用完成
  • 另一种退出方式是在方法执行的过程中遇到异常,且这个异常没有在方法体内得到妥善处理。无论是JVM内部产生的异常,还是代码中使用athrow字节码指令产生的异常,只要在本方法的异常表中没有搜索到匹配的异常处理器,就会导致方法退出,这种退出方法的方式称为Abrupt Method Invocation Completion异常调用完成。一个方法使用Abrupt Method Invocation Completion的方式退出,不会给他的上层调用者提供任何返回值。方法执行过程中抛出异常的异常处理器,存储在一个异常处理表,方便在发生异常的时候找到处理异常的代码。

无论采用何种退出方式,在方法退出后,必须返回到最初方法被调用时的位置,程序才能继续执行,方法返回时可能需要在stack frame种保存一些信息,用来帮助恢复他的上层主调方法的执行状态。一般来说,方法正常退出时,主调方法的pc register的值就可以作为返回地址即调用该方法的指令的下一条指令的地址stack frame中很可能会保存这个pc register value。而方法异常退出时,返回地址是要通过异常处理器表来确定的,stack frame一般不会保存这部分信息,异常退出不会给上层调用者生产任何的返回值。

方法退出的过程实际上等同于把当前stack frame 出栈,因此退出时可能基于jvm规范讨论,具体执行哪些操作由具体jvm实现来确定执行的操作有:恢复上层方法的local variablesoperand stack,把返回值如果有返回值压入调用者stack frameoperand stack,调整pc register的值以指向方法调用指令后面的一条指令等。

返回字节码指令

  • ireturn
    • boolean、byte、char、short、int
  • lreturn
    • long
  • freturn
    • float
  • dreturn
    • double
  • areturn
    • 引用类型
  • return
    • void 方法、实例初始化方法、类、接口的初始化方法

Normal Method Invocation Completion

Oracle 官方文档
A method invocation completes normally正常地 if that invocation does not cause造成 an exception (§2.10) to be thrown, either directly直接地 from the Java Virtual Machine or as a result of executing执行 an explicit明确的 throw statement语句. If the invocation of the current method completes normally, then a value may be returned to the invoking method. This occurs发生 when the invoked method executes执行 one of the return instructions(计算机的)指令 (§2.11.8), the choice选择 of which must be appropriate合适的 for the type of the value being returned (if any).

The current frame §2.6) is used in this case to restore恢复 the state of the invoker调用者, including its local variables and operand stack, with the program counter of the invoker调用者 appropriately适当地 incremented递增 to skip past跳过 the method invocation instruction(计算机的)指令. Execution执行 then continues normally正常地 in the invoking method’s frame with the returned value (if any) pushed onto the operand stack of that frame.

Abrupt Method Invocation Completion

Oracle 官方文档
A method invocation调用 completes abruptly意外地 if execution执行 of a Java Virtual Machine instruction(计算机的)指令 within the method causes the Java Virtual Machine to throw an exception (§2.10), and that exception is not handled within the method. Execution执行 of an athrow instruction (§athrow) also causes引起 an exception to be explicitly明确地 thrown and, if the exception is not caught抓住 by the current method, results in abrupt意外地 method invocation completion. A method invocation that completes abruptly意外地 never returns a value to its invoker调用者.

附加信息

《深入理解Java虚拟机》JVM规范运行jvm实现增加一些规范里没有描述的信息到stack frame中,例如与调试、性能收集相关的信息,这部分信息完全取决于具体jvm实现。

指令集架构

  • 基于栈式架构(JVM)
    • 设计和实现更简单,适用于资源受限的系统
    • 避开寄存器的分配难题:使用零地址指令方式分配
    • 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈,指令集更小,编译器容易实现
    • 不需要硬件支持,可移植性更好,更好实现跨平台
  • 基于寄存器架构
    • 典型的应用是x86的二进制指令集传统PC以及Android的Davlik虚拟机
    • 指令集架构完全依赖硬件可移植性差
    • 性能优秀和执行更高效
    • 花费更少的指令去完成一项操作
    • 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令二地址指令三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主

ToS

栈顶缓存 ToSTop-of-Stack Cashing
JVM基于栈式架构,使用的零地址指令更加紧凑,完成一项操作的时候必然需要使用更多的入栈和出栈指令,意味着将需要更多的指令分派instruction dispatch次数和内存读写次数
由于操作数是存储在内存中,因此频繁地执行内存读写操作必然会影响执行速度,为了解决此问题,HotSpot JVM的设计者们提出栈顶缓存技术ToS Top-of-Stack Cashing

栈顶元素全部缓存在物理CPU寄存器CPU寄存器:指令更少,执行速度快,降低对内存的读写次数,提升execution engine的执行效率。ToS还需要在HotSpot JVM具体进行测试才能运用

拓展

jvm stack 溢出的情况: Xss10M 设置 jvm stack size
jvm stack固定分配大小,当最后一个stack frame所占内存大于stack的剩余容量即会出现StackOverFlowError
jvm stack动态分配,当尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的JVM stack,则OutOfMemoryError

调整stack大小,能保证不出现溢出吗? stack frame不确定,方法嵌套调用深度不确定,故扩大stack size不能100%保证不出现溢出,最多延迟溢出的时间
eg: 递死归

分配stack越大越好吗

  • 不是,要综合考虑
  • 挤占线程空间
  • 挤占其他空

Garbage Collection 是否会涉及到 jvm stack

  • 不会涉及jvm stack

方法中定义的局部变量(local variable)是否线程安全?

  • 不一定,如果局部变量一直在当前方法内存活,则线程安全\
  • 如果局部变量作为参数传入,如果多线程调用此方法,则该局部变量不安全\
  • 如果局部变量作为返回值返回,并被其他方法使用时,如果多线程,也不安全

变量分类

  • 按照数据类型分类
    • 基本数据类型
    • 引用数据类型
  • 按照在类中声明的位置分类
    • 成员变量: 使用前都经历过,默认初始化赋值
      • 类变量static修饰又称为静态变量linkingprepare阶段会给类变量赋默认值 --> Initialization阶段显示赋值静态代码块赋值
      • 实例变量:随着对象的创建,会在heap空间中分配实例变量空间并进行默认赋值
    • 局部变量:使用前必须显示赋值,否则编译不通过