JVM那些事儿-栈内存解析(二)

简介

每当启动一个新线程时,Java虚拟机都会为它分配一个Java栈。Java栈以帧为单位保存线程的运行状态。虚拟机只会直接对Java栈执行两种操作:以帧为单位的压栈和出栈;
帧存储了方法的局部变量表,操作数栈,动态连接和方法返回地址等信息。

栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到了方法表的Code属性中,因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体虚拟机的实现;

线程中的方法调用链可能会很长,每个方法都会生成对应的一块栈帧空间,方法以两种方式完成,一种通过return返回的,称为正常返回;一种是通过抛出异常而异常终止的。不管以哪种方式返回,虚拟机都会将当前帧弹出Java栈然后释放掉,这样上一个方法的帧就成为当前帧了。

栈帧的结构

image.png

局部变量表(Local Variable Table)

局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序编译为Class文件时,就在方法表的Code属性的max_locals数据项中确定了该方法需要分配的最大局部变量表的容量。

局部变量表的容量以变量槽(Slot)为最小单位,虚拟机规范中未说明它该有多大,只说每个Slot都应该能存放一个boolean、byte、char、short、int、float、reference或returnAddress类型的数据,reference类型表示对一个对象实例的引用,虚拟机对它的长度和结构没有说明。

为了尽可能节省栈帧空间,局部变量表中的Slot是可以重用的,方法体中定义的变量其作用域并不一定会覆盖整个方法体,如果当前字节码程序计数器的值已经超出了某个变量的作用域,那这个变量对应的Slot就可以交给其他变量使用。不过这样的设计虽节省了空间,但也会有一定的副作用,例如在某些情况下,Slot的复用会直接影响到系统的垃圾收集行为。

说了那么多,其实就把它理解为存储当前方法内的局部变量的。

操作数栈(Operand Stack)

操作数栈也常被称为操作栈,它是一个后入先出栈。同局部变量表一样,操作数栈的最大深度也是编译的时候被写入到方法表的Code属性的 max_stacks数据项中。操作数栈的每一个元素可以是任意Java数据类型,包括long和double。32位数据类型所占的栈容量为1,64位 数据类型所占的栈容量为2。栈容量的单位为“字宽”,对于32位虚拟机来说,一个”字宽“占4个字节,对于64位虚拟机来说,一个”字宽“占8个字节。

当一个方法刚刚执行的时候,这个方法的操作数栈是空的,在方法执行的过程中,会有各种字节码指向操作数栈中写入和提取值,也就是入栈与出栈操作。例如,在做算术运算的时候就是通过操作数栈来进行的,又或者调用其它方法的时候是通过操作数栈来行参数传递的。

另外,在概念模型中,两个栈帧作为虚拟机栈的元素,相互之间是完全独立的,但是大多数虚拟机的实现里都会作一些优化处理,令两个栈帧出现一部分重叠。让下 栈帧的部分操作数栈与上面栈帧的部分局部变量表重叠在一起,这样在进行方法调用返回时就可以共用一部分数据,而无须进行额外的参数复制传递了。

我们可以通过一段代码看下

public class Match {
   public static final int initData = 666;
    public static Sat sat = new Sat();
    public byte[] arr = new byte[1024 * 25];

    public int compute(){
        int a = 1;
        int b = 2;
        int c = (a + b) * 100;
        return c;
    }

    public static void main(String[] args) throws Exception {
        Match math = new Match();
        int result = math.compute();
        System.out.println(result);
    }
}

如果我们执行上述代码,此线程开始到此线程结束,共执行了两个方法,该线程对应的也就是两个栈帧,同数据结构栈一致,也是遵循先进后出的方式进行栈帧的加载。


image.png

image.png

通过字节码反汇编后

Compiled from "Match.java"
public class com.kxl.e.invoice.admin.utils.Match {
  public static final int initData;

  public static com.kxl.e.invoice.admin.biz.cert.dao.entity.Sat sat;

  public byte[] arr;

  public com.kxl.e.invoice.admin.utils.Match();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: sipush        25600
       8: newarray       byte
      10: putfield      #2                  // Field arr:[B
      13: return

  public int compute();
    Code:
       0: iconst_1  //iconst_1 将int类型常量1压入栈
       1: istore_1  //istore_1 将int类型值存入局部变量1
       2: iconst_2 //iconst_2 将int类型常量2压入栈
       3: istore_2 //istore_2 将int类型值存入局部变量2
       4: iload_1 //iload_1 从局部变量1中装载int类型值
       5: iload_2 //iload_2 从局部变量2中装载int类型值
       6: iadd //iadd 执行int类型的加法
       7: bipush        100 //bipush 将一个8位带符号整数压入栈
       9: imul //mul 执行int类型的乘法
      10: istore_3 //istore_3 将int类型值存入局部变量3
      11: iload_3 //iload_3 从局部变量3中装载int类型值
      12: ireturn //ireturn 从方法中返回int类型的数据

 public static void main(java.lang.String[]) throws java.lang.Exception;
    Code:
       0: new           #3                  // class com/kxl/e/invoice/admin/utils/Match
       3: dup
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1
       8: aload_1
       9: invokevirtual #5                  // Method compute:()I
      12: istore_2
      13: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      16: iload_2
      17: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
      20: return

  static {};
    Code:
       0: new           #8                  // class com/kxl/e/invoice/admin/biz/cert/dao/entity/Sat
       3: dup
       4: invokespecial #9                  // Method com/kxl/e/invoice/admin/biz/cert/dao/entity/Sat."<init>":()V
       7: putstatic     #10                 // Field sat:Lcom/kxl/e/invoice/admin/biz/cert/dao/entity/Sat;
      10: return
}

具体细节可以参照“JVM指令手册”
地址://www.greatytc.com/p/53a052adffc1

操作架构图:

0: iconst_1  //iconst_1 将int类型常量1压入栈
1: istore_1  //istore_1 将int类型值存入局部变量1
image.png
4: iload_1 //iload_1 从局部变量1中装载int类型值       
5: iload_2 //iload_2 从局部变量2中装载int类型值
image.png
  6: iadd //iadd 执行int类型的加法
image.png

image.png
 7: bipush        100 //bipush 将一个8位带符号整数压入栈
image.png
 9: imul //mul 执行int类型的乘法
image.png

image.png
10: istore_3 //istore_3 将int类型值存入局部变量3
image.png

动态链接

符号引用和直接引用在运行时进行**解析和链接的过程,叫动态链接
一个方法调用另一个方法,或者一个类使用另一个类的成员变量时,需要知道其名字
符号引用就相当于名字,这些被调用者的名字就存放在Java字节码文件里(.class 文件)
名字知道了,但是Java真正运行起来的时候,如何靠这个名字(符号引用)找到相应的类和方法
需要解析成相应的直接引用,利用直接引用来准确地找到。

方法出口

image.png

当代码执行到26行的时候,进入compute方法之前执行该指令,将 compute方法执行完毕之后执行的代码行号保存到 compute方法对应" 栈帧 " 中的方法出口中,compute方法的 " 方法出口 " 是第 26 行代码!

帧数据区

帧数据区的大小依赖于 JVM 的具体实现

程序计数器

image.png

image.png

指向当前线程所执行的字节码指令(地址)行号。
程序计数器由字节码执行引擎控制操作。
注意:为什么字节码中没有行数8 ,其实100 对应的就是8,只不过是一个隐形的行数

本地方法栈

本地方法栈和虚拟机栈所发挥的作用时非常相似的,虚拟机栈为虚拟机执行Java方法服务,本地方法栈则为虚拟机使用到Native方法服务,虚拟机规范中对本地方法栈中的方法使用的语言、使用方式与数据结构并没有强制规定,因此具体的虚拟机可以自由实现它。

Navtive 方法是 Java 通过 JNI 直接调用本地 C/C++ 库,可以认为是 Native 方法相当于 C/C++ 暴露给 Java 的一个接口,Java 通过调用这个接口从而调用到 C/C++ 方法。当线程调用 Java 方法时,虚拟机会创建一个栈帧并压入 Java 虚拟机栈。然而当它调用的是 native 方法时,虚拟机会保持 Java 虚拟机栈不变,也不会向 Java 虚拟机栈中压入新的栈帧,虚拟机只是简单地动态连接并直接调用指定的 native 方法。

代码案例

image.png
image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容