• Java虚拟机栈(java stack)


    虚拟机栈(java stack)

    百度图片搜索里的动图搜索功能不错,可以搜索一些动图,展示操作数栈的操作过程,比较形象。这点google差点意思

    • 虚拟机栈(jvm stacks)是线程独占的

    • 里面是多个栈帧(frame)或叫方法帧(class里的每个方法独占一个栈帧,所以也可以称之为方法帧)

    • 每个栈帧里包含:局部变量区/操作数栈/动态链接/方法的返回地址

    示例

    1. 文件SimpleExample.java

      1 class SimpleExample {
      2 	public static void main(String[] args) {
      3 		int result = add(2,3);
      4 		System.out.println(result);
      5 	}
      6 	public static int add(int a, int b) {
      7 		return a+b;
      8 	}
      9 }
      
    2. 编译源代码生产字节码文件SimpleExample.class

      -g Generates all debugging information, including local variables. By default, only line number and source file information is generated.

      作用是反编译的时候产生局部变量表

      javac -g SimpleExample.java
      
    3. 反编译

      javap -v -l -p SimpleExample.class
      

      man -a javap查看帮助信息

      -l Prints line and local variable tables.

      -v Prints stack size, number of locals and arguments for methods.

      -p Shows all classes and members.

      ➜  myJavaDir javap -v -l -p  SimpleExample
      Classfile /Users/xxxx/myJavaDir/SimpleExample.class
        Last modified Oct 29, 2019; size 642 bytes
        // 字节码的md5值
        MD5 checksum e54540b1d2c0620b7e4eb555a8192a78
        Compiled from "SimpleExample.java"
      class SimpleExample
        minor version: 0
        major version: 52
        flags: ACC_SUPER
      //class常量池
      Constant pool:
         #1 = Methodref          #6.#26         // java/lang/Object."<init>":()V
         #2 = Methodref          #5.#27         // SimpleExample.add:(II)I
         #3 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
         #4 = Methodref          #30.#31        // java/io/PrintStream.println:(I)V
         #5 = Class              #32            // SimpleExample
         #6 = Class              #33            // java/lang/Object
         #7 = Utf8               <init>
         #8 = Utf8               ()V
         #9 = Utf8               Code
        #10 = Utf8               LineNumberTable
        #11 = Utf8               LocalVariableTable
        #12 = Utf8               this
        #13 = Utf8               LSimpleExample;
        #14 = Utf8               main
        #15 = Utf8               ([Ljava/lang/String;)V
        #16 = Utf8               args
        #17 = Utf8               [Ljava/lang/String;
        #18 = Utf8               result
        #19 = Utf8               I
        #20 = Utf8               add
        #21 = Utf8               (II)I
        #22 = Utf8               a
        #23 = Utf8               b
        #24 = Utf8               SourceFile
        #25 = Utf8               SimpleExample.java
        #26 = NameAndType        #7:#8          // "<init>":()V
        #27 = NameAndType        #20:#21        // add:(II)I
        #28 = Class              #34            // java/lang/System
        #29 = NameAndType        #35:#36        // out:Ljava/io/PrintStream;
        #30 = Class              #37            // java/io/PrintStream
        #31 = NameAndType        #38:#39        // println:(I)V
        #32 = Utf8               SimpleExample
        #33 = Utf8               java/lang/Object
        #34 = Utf8               java/lang/System
        #35 = Utf8               out
        #36 = Utf8               Ljava/io/PrintStream;
        #37 = Utf8               java/io/PrintStream
        #38 = Utf8               println
        #39 = Utf8               (I)V
      {
        // 默认构造函数,即使我们不写,也会自动产生
        SimpleExample();
          descriptor: ()V
          flags:
          Code:
            // 栈帧的操作数栈里有一个元素,栈帧的局部变量表里有一个变量(为this即SimpleExample对象),构造方法有一个入参(this),构建方法的第一个参数(和默认参数)是this
            stack=1, locals=1, args_size=1
               // 把局部变量表(数组结构)里的索引为0的数据(this对象)放入操作数栈
               0: aload_0
               // 调用(this)父类(这里是Object)的构造方法
               1: invokespecial #1                  // Method java/lang/Object."<init>":()V
               // 返回,清空操作数栈和局部变量表
               4: return
            // 源代码和字节码指令序号的映射表   
            LineNumberTable:
              // 代码的第一行("class SimpleExample {")对应的指令为0: aload_0
              line 1: 0
            // 局部变量表  
            LocalVariableTable:
            	// 变量名字是this,长度为5个字节。局部变量表第一项是个reference(引用),它指定的就是对象本身的引用,也就是我们常用的this,但是在静态方法中,没这个引用
              Start  Length  Slot  Name   Signature
                  0       5     0  this   LSimpleExample;
      
        public static void main(java.lang.String[]);
          descriptor: ([Ljava/lang/String;)V
          // 方法修饰符public static
          flags: ACC_PUBLIC, ACC_STATIC
          Code:
            // 栈帧里的操作数栈为2,局部变量数量为2,参数个数为1
            stack=2, locals=2, args_size=1
               // 把常量池里的整数2压入操作数栈
               0: iconst_2
               // 把常量池里的整数3压入操作数栈
               1: iconst_3
               // 调用class常量池里的#2即静态方法add,返回结果入栈顶。由于该方法需要两个整数做参数,所以invokestatic从操作数栈中弹出两个元素,并将它们传给由JVM为add()创建的新栈帧,保存在add方法帧的局部变量表里。main()的操作数栈此时是空的。
               2: invokestatic  #2                  // Method add:(II)I
               // 把操作数栈的栈顶的一个整数元素取出,存入局部变量表的索引为1的位置,也就是result的值
               5: istore_1
               // getstatic将类型为java/io/PrintStream的静态字段java/lang/System.out压入栈中
               6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
               // iload_1将局部变量表(数组结构)里的索引位置为1处的变量(就是result的值,现在等于5)压入到操作数栈中。所以此时栈保存了两个值:out字段和值5
               9: iload_1
               // 现在invokevirtual准备调用PrintSteam.println()方法。它从栈中弹出两个元素:第一个元素是对将要调用println()方法的对象的引用。第二个元素是一个要传递给println()方法的整型参数,它只需要一个参数。这是main()方法打印相加的结果的地方
              10: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
              // return命令完成该方法。主帧被丢弃,JVM进程结束
              13: return
            LineNumberTable:
              // 源代码第3行(int result = add(2,3);)对应字节码指令(0: iconst_2,1: iconst_3, 2: invokestatic #2,5: istore_1)直到(line 4: 6)
              line 3: 0
              line 4: 6
              line 5: 13
            LocalVariableTable:
              // main方法栈帧的局部变量变里有两个变量args、result。静态方法,没有this引用,第一个变量就不再是this
              Start  Length  Slot  Name   Signature
                  0      14     0  args   [Ljava/lang/String;
                  6       8     1 result   I
      
        public static int add(int, int);
          // (II)I表示方法add为两个int的入参数,返回类型为int
          descriptor: (II)I
          flags: ACC_PUBLIC, ACC_STATIC
          Code:
            // 栈帧里的操作数栈为2,局部变量数量为2,参数个数为2
            stack=2, locals=2, args_size=2
               // 把add方法帧局部变量表里索引为0的元素值(这里为整数2)压入add方法帧的操作数栈
               0: iload_0
               // 把add方法帧局部变量表里索引为1的元素值(这里为整数3)压入add方法帧的操作数栈
               1: iload_1
               // 把操作数栈的栈顶的2个整数从操作数栈里取出,然后相加,最后把结果再次存入操作数栈
               2: iadd
               // 正常情况下,当前帧操作数栈的栈顶值出栈,压入调用方法的操作上栈的栈顶,丢弃当前帧操作数栈的其他值,销毁了add方法帧。恢复到调用方法帧,调用方法(invoker)继续执行
               3: ireturn
            LineNumberTable:
              // 源代码的第7行(return a+b;)对应上面的字节码指令(0: iload_0)及后续指令
              line 7: 0
            LocalVariableTable:
              // 栈帧里的局部变量表里有2个变量a和b,类型为int,大小都为4个字节,在局部变量表里的索引位置为0和1
              Start  Length  Slot  Name   Signature
                  0       4     0     a   I
                  0       4     1     b   I
      }
      SourceFile: "SimpleExample.java"
      
      

    示例:

    package com.demo3;
    public class Test {
        public static void foo() {
            int a = 1;
            int b = 2;
            int c = (a + b) * 5;
        }
        public static void main(String[] args) {
            foo();
        }
    }
    

    基于栈的Hotspot的执行过程如下:

    img

    参考

    浅谈虚拟机内存模型

    Java虚拟机—栈帧、操作数栈和局部变量表

    JVM体系结构101:了解虚拟机

    jvm指令集

    通过javap命令分析java汇编指令

  • 相关阅读:
    使用WCF建立起Silverlight客户端与服务端的桥梁
    Silverlight WCF RIA服务(三十三)身份验证、角色、个性化 4
    陶哲轩实分析习题 12.1.3
    Asymptote 学习记录(6) 练习用模块roundedpath画出一个图
    使用Asymptote的循环功能画出绿叶阵
    使用Asymptote的循环功能画出绿叶阵
    度量空间的一个例子:离散度量空间
    练习: 使用Asymptote 画出字母R的轮廓曲线
    练习: 使用Asymptote 画出字母R的轮廓曲线
    陶哲轩实分析命题 11.10.7
  • 原文地址:https://www.cnblogs.com/shengulong/p/11761018.html
Copyright © 2020-2023  润新知