JVM系列二:Java虚拟机栈和本地栈

本篇文章主要聊聊Java虚拟机栈和本地栈

Java虚拟机栈(Java Virtual MechineStacks )

也可以回顾上一篇文章

官网描述

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.

翻译软件直接翻译一下:
每个Java虚拟机线程都有一个私有的Java虚拟机堆栈,与线程同时创建。Java虚拟机栈存储帧(§2.6)。Java虚拟机堆栈类似于传统语言(如C)的堆栈:它保存局部变量和部分结果,并在方法调用和返回中发挥作用。因为除了push和pop帧之外,Java虚拟机堆栈从来不会直接操作,所以帧可能会被堆分配。Java虚拟机堆栈的内存不需要是连续的。

在Java®虚拟机规范的第一版中,Java虚拟机堆栈被称为Java堆栈。

该规范允许Java虚拟机栈的大小固定,也可以根据计算的需要动态扩展和收缩。如果Java虚拟机堆栈的大小是固定的,那么每个Java虚拟机堆栈的大小可以在创建堆栈时独立选择。

Java虚拟机实现可以为程序员或用户提供对Java虚拟机堆栈初始大小的控制,在动态扩展或收缩Java虚拟机堆栈的情况下,还可以提供对最大和最小大小的控制。

以下异常情况与Java虚拟机栈关联:

如果在一个线程中计算需要比允许的更大的Java虚拟机堆栈,Java虚拟机抛出一个StackOverflowError。

如果Java虚拟机栈可以动态地扩展,和扩张是未遂但可以可用内存不足影响扩张,或者内存不足可以创建一个新线程的初始Java虚拟机栈,Java虚拟机抛出一个OutOfMemoryError。

理解下:
1、虚拟机栈是一个线程执行的区域,保存着一个线程中方法的调用状态。换句话说,一个Java线程的运行
2、状态,由一个虚拟机栈来保存,所以虚拟机栈肯定是线程私有的,独有的,随着线程的创建而创建。
3、每一个被线程执行的方法,为该栈中的栈帧,即每个方法对应一个栈帧。
4、调用一个方法,就会向栈中压入一个栈帧;一个方法调用完成,就会把该栈帧从栈中弹出。

栈和栈帧

官网地址:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.6
我们先看下图:
在这里插入图片描述
我们分别看下描述:
1、栈帧:每个栈帧对应一个被调用的方法,可以理解为一个方法的运行空间。
2、每个栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向运行时常量池的引用(A reference to the run-time constant pool)、方法返回地址(Return Address)和附加信息。
3、局部变量表
方法中定义的局部变量以及方法的参数存放在这张表中 局部变量表中的变量不可直接使用,如需要使用的话,必须通过相关指令将其加载至操作数栈中作为操作数使用。
4、操作数栈
以压栈和出栈的方式存储操作数的
5、动态链接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态 连接(Dynamic Linking)。
a、每一个栈帧当中都包含以个指向运行时常量池重该栈帧所属方法的引用(invokedynamic指令)
在java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里;
比如:描述一个方法调用的另外的其它方法时,就是通过常量池中指向该方法的符号引用来表示,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用
在这里插入图片描述

6、方法返回地址
当一个方法开始执行后,只有两种方式可以退出,一种是遇到方法返回的字节码指令;一种是遇见异常,并且 这个异常没有在方法体内得到处理。

例子

说这块,我们先看下我们的java例子。


class Member {
    private int id;
    private String name;
    private int age;
    private static String address;
    private final static String hobby = "Java";

    public void say() {
        System.out.println("Member say...");
    }

    public static int calcCount(int op1, int op2) {
        op1 = 10;
        return op1 + op2;
    }

    public static void main(String[] args){
        calcCount(1,2);
    }
}

接下来,我们需要反编译执行,具体命令可以网上查找:
https://blog.csdn.net/fclwd/article/details/112997165

反编译后,我们看calcCount方法。

Compiled from "Member.java"
class Member {
  Member();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public void say();
    Code:
       0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: ldc           #3                  // String Member say...
       5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8: return

  public static int calcCount(int, int);
    Code:
       0: bipush        10    //将int类型常量10压入[操作数栈]
       2: istore_0			  //将int类型值存入[局部变量0]
       3: iload_0			  //从[局部变量0]中装载int类型值入栈
       4: iload_1			  //从[局部变量1]中装载int类型值入栈
       5: iadd				  //将栈顶元素弹出栈,执行int类型的加法,结果入栈
       6: ireturn			  //从方法中返回int类型的数据

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: iconst_2
       2: invokestatic  #5                  // Method calcCount:(II)I
       5: pop
       6: return
}

我们再画个图理解下:
在这里插入图片描述

栈与堆

栈指向堆

如果在栈帧中有一个变量,类型为引用类型,比如Object obj=new Object(),这时候就是典型的栈中元素指向堆中的
对象。
在这里插入图片描述

方法区指向堆

方法区中会存放静态变量,常量等数据。如果是下面这种情况,就是典型的方法区中元素指向堆中的对象。
private static Object obj=new Object();

堆指向方法区

方法区中会包含类的信息,堆中会有对象,那怎么知道对象是哪个类创建的呢?
一个对象怎么知道它是由哪个类创建出来的?怎么记录?这就需要了解一个Java对象的具体信息?
看看Java对象内存布局:
一个Java对象在内存中包括3个部分:对象头、实例数据和对齐填充
在这里插入图片描述
显而易见,在内存中的对象头信息中,包含了Class Pointer ,找到了对应的方法区的类元数据内存地址。

本地方法栈(Native Method Stacks)

对于一个运行中的Java程序而言,它还可能会用到一些跟本地方法相关的数据区。当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,但不止如此,它还可以做任何它想做的事情。

本地方法本质上时依赖于实现的,虚拟机实现的设计者们可以自由地决定使用怎样的机制来让Java程序调用本地方法。

任何本地方法接口都会使用某种本地方法栈。当线程调用Java方法时,虚拟机会创建一个新的栈帧并压入Java栈。然而当它调用的是本地方法时,虚拟机会保持Java栈不变,不再在线程的Java栈中压入新的帧,虚拟机只是简单地动态连接并直接调用指定的本地方法。

如果某个虚拟机实现的本地方法接口是使用C连接模型的话,那么它的本地方法栈就是C栈。当C程序调用一个C函数时,其栈操作都是确定的。传递给该函数的参数以某个确定的顺序压入栈,它的返回值也以确定的方式传回调用者。同样,这就是虚拟机实现中本地方法栈的行为。
  
  很可能本地方法接口需要回调Java虚拟机中的Java方法,在这种情况下,该线程会保存本地方法栈的状态并进入到另一个Java栈。

下图描绘了这样一个情景,就是当一个线程调用一个本地方法时,本地方法又回调虚拟机中的另一个Java方法。

这幅图展示了JAVA虚拟机内部线程运行的全景图。一个线程可能在整个生命周期中都执行Java方法,操作它的Java栈;或者它可能毫无障碍地在Java栈和本地方法栈之间跳转。

在这里插入图片描述

该线程首先调用了两个Java方法,而第二个Java方法又调用了一个本地方法,这样导致虚拟机使用了一个本地方法栈。假设这是一个C语言栈,其间有两个C函数,第一个C函数被第二个Java方法当做本地方法调用,而这个C函数又调用了第二个C函数。之后第二个C函数又通过本地方法接口回调了一个Java方法(第三个Java方法),最终这个Java方法又调用了一个Java方法(它成为图中的当前方法)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南巷Dong

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值