• JVM笔记(2)-笑谈Java字节码指令


    基础不牢,地动山摇,Java仍然是业界主流的开发语言之一,Java生态圈中有大量的组件框架,也包括大量大数据的组件如Hadoop等。我们想要更熟练透彻地掌握这些组件框架并更好地开发自己的程序,深入学习JVM的基础很有必要。本篇深入浅出来讲述Java字节码指令运行的过程,避免过度深入太多细节,让学习者可以对JVM解析字节码以及运行指令的过程有一个宏观的认识,对继续深入学习JVM相关知识有总体的把控。

    1.准备程序和字节码

    Java:

    public class ByteCodeT {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void hi() {
            String s12 = "hi ".concat(name);
            hello(s12);
        }
    
        public String hello(String s2) {
            return s2;
        }
    
        public String getToken() {
            return UUID.randomUUID()
                    .toString()
                    .replace("-", "")
                    + name.hashCode();
        }
    }

    在class文件目录输入命令打印出字节码(-v是打印全面字节码信息,-p是涵盖所有成员):

    javap -v -p ByteCodeT

    字节码:

    警告: 二进制文件ByteCodeT包含com.lims.pracpro.jdkprac.ByteCodeT
    Classfile /E:/Code/flickeringproject/pracpro/target/classes/com/lims/pracpro/jdkprac/ByteCodeT.class
      Last modified 2020-9-17; size 1319 bytes
      MD5 checksum 1c5e715a75b59bba2b0228e7757fa33d
      Compiled from "ByteCodeT.java"
    public class com.lims.pracpro.jdkprac.ByteCodeT
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #18.#40        // java/lang/Object."<init>":()V
       #2 = Fieldref           #17.#41        // com/lims/pracpro/jdkprac/ByteCodeT.name:Ljava/lang/String;
       #3 = String             #42            // hi
       #4 = Methodref          #43.#44        // java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/String;
       #5 = Methodref          #17.#45        // com/lims/pracpro/jdkprac/ByteCodeT.hello:(Ljava/lang/String;)Ljava/lan
    g/String;
       #6 = Class              #46            // java/lang/StringBuilder
       #7 = Methodref          #6.#40         // java/lang/StringBuilder."<init>":()V
       #8 = Methodref          #47.#48        // java/util/UUID.randomUUID:()Ljava/util/UUID;
       #9 = Methodref          #47.#49        // java/util/UUID.toString:()Ljava/lang/String;
      #10 = String             #50            // -
      #11 = String             #51            //
      #12 = Methodref          #43.#52        // java/lang/String.replace:(Ljava/lang/CharSequence;Ljava/lang/CharSeque
    nce;)Ljava/lang/String;
      #13 = Methodref          #6.#53         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBu
    ilder;
      #14 = Methodref          #43.#54        // java/lang/String.hashCode:()I
      #15 = Methodref          #6.#55         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      #16 = Methodref          #6.#49         // java/lang/StringBuilder.toString:()Ljava/lang/String;
      #17 = Class              #56            // com/lims/pracpro/jdkprac/ByteCodeT
      #18 = Class              #57            // java/lang/Object
      #19 = Utf8               name
      #20 = Utf8               Ljava/lang/String;
      #21 = Utf8               <init>
      #22 = Utf8               ()V
      #23 = Utf8               Code
      #24 = Utf8               LineNumberTable
      #25 = Utf8               LocalVariableTable
      #26 = Utf8               this
      #27 = Utf8               Lcom/lims/pracpro/jdkprac/ByteCodeT;
      #28 = Utf8               getName
      #29 = Utf8               ()Ljava/lang/String;
      #30 = Utf8               setName
      #31 = Utf8               (Ljava/lang/String;)V
      #32 = Utf8               hi
      #33 = Utf8               s12
      #34 = Utf8               hello
      #35 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
      #36 = Utf8               s2
      #37 = Utf8               getToken
      #38 = Utf8               SourceFile
      #39 = Utf8               ByteCodeT.java
      #40 = NameAndType        #21:#22        // "<init>":()V
      #41 = NameAndType        #19:#20        // name:Ljava/lang/String;
      #42 = Utf8               hi
      #43 = Class              #58            // java/lang/String
      #44 = NameAndType        #59:#35        // concat:(Ljava/lang/String;)Ljava/lang/String;
      #45 = NameAndType        #34:#35        // hello:(Ljava/lang/String;)Ljava/lang/String;
      #46 = Utf8               java/lang/StringBuilder
      #47 = Class              #60            // java/util/UUID
      #48 = NameAndType        #61:#62        // randomUUID:()Ljava/util/UUID;
      #49 = NameAndType        #63:#29        // toString:()Ljava/lang/String;
      #50 = Utf8               -
      #51 = Utf8
      #52 = NameAndType        #64:#65        // replace:(Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/S
    tring;
      #53 = NameAndType        #66:#67        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      #54 = NameAndType        #68:#69        // hashCode:()I
      #55 = NameAndType        #66:#70        // append:(I)Ljava/lang/StringBuilder;
      #56 = Utf8               com/lims/pracpro/jdkprac/ByteCodeT
      #57 = Utf8               java/lang/Object
      #58 = Utf8               java/lang/String
      #59 = Utf8               concat
      #60 = Utf8               java/util/UUID
      #61 = Utf8               randomUUID
      #62 = Utf8               ()Ljava/util/UUID;
      #63 = Utf8               toString
      #64 = Utf8               replace
      #65 = Utf8               (Ljava/lang/CharSequence;Ljava/lang/CharSequence;)Ljava/lang/String;
      #66 = Utf8               append
      #67 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
      #68 = Utf8               hashCode
      #69 = Utf8               ()I
      #70 = Utf8               (I)Ljava/lang/StringBuilder;
    {
      private java.lang.String name;
        descriptor: Ljava/lang/String;
        flags: ACC_PRIVATE
    
      public com.lims.pracpro.jdkprac.ByteCodeT();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: return
          LineNumberTable:
            line 11: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/lims/pracpro/jdkprac/ByteCodeT;
    
      public java.lang.String getName();
        descriptor: ()Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=1, args_size=1
             0: aload_0
             1: getfield      #2                  // Field name:Ljava/lang/String;
             4: areturn
          LineNumberTable:
            line 15: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       5     0  this   Lcom/lims/pracpro/jdkprac/ByteCodeT;
    
      public void setName(java.lang.String);
        descriptor: (Ljava/lang/String;)V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: aload_0
             1: aload_1
             2: putfield      #2                  // Field name:Ljava/lang/String;
             5: return
          LineNumberTable:
            line 19: 0
            line 20: 5
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       6     0  this   Lcom/lims/pracpro/jdkprac/ByteCodeT;
                0       6     1  name   Ljava/lang/String;
    
      public void hi();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=1
             0: ldc           #3                  // String hi
             2: aload_0
             3: getfield      #2                  // Field name:Ljava/lang/String;
             6: invokevirtual #4                  // Method java/lang/String.concat:(Ljava/lang/String;)Ljava/lang/Stri
    ng;
             9: astore_1
            10: aload_0
            11: aload_1
            12: invokevirtual #5                  // Method hello:(Ljava/lang/String;)Ljava/lang/String;
            15: pop
            16: return
          LineNumberTable:
            line 23: 0
            line 24: 10
            line 25: 16
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      17     0  this   Lcom/lims/pracpro/jdkprac/ByteCodeT;
               10       7     1   s12   Ljava/lang/String;
    
      public java.lang.String hello(java.lang.String);
        descriptor: (Ljava/lang/String;)Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=1, locals=2, args_size=2
             0: aload_1
             1: areturn
          LineNumberTable:
            line 28: 0
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       2     0  this   Lcom/lims/pracpro/jdkprac/ByteCodeT;
                0       2     1    s2   Ljava/lang/String;
    
      public java.lang.String getToken();
        descriptor: ()Ljava/lang/String;
        flags: ACC_PUBLIC
        Code:
          stack=4, locals=1, args_size=1
             0: new           #6                  // class java/lang/StringBuilder
             3: dup
             4: invokespecial #7                  // Method java/lang/StringBuilder."<init>":()V
             7: invokestatic  #8                  // Method java/util/UUID.randomUUID:()Ljava/util/UUID;
            10: invokevirtual #9                  // Method java/util/UUID.toString:()Ljava/lang/String;
            13: ldc           #10                 // String -
            15: ldc           #11                 // String
            17: invokevirtual #12                 // Method java/lang/String.replace:(Ljava/lang/CharSequence;Ljava/lan
    g/CharSequence;)Ljava/lang/String;
            20: invokevirtual #13                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/la
    ng/StringBuilder;
            23: aload_0
            24: getfield      #2                  // Field name:Ljava/lang/String;
            27: invokevirtual #14                 // Method java/lang/String.hashCode:()I
            30: invokevirtual #15                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
    
            33: invokevirtual #16                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
            36: areturn
          LineNumberTable:
            line 32: 0
            line 33: 10
            line 34: 17
            line 35: 27
            line 32: 36
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      37     0  this   Lcom/lims/pracpro/jdkprac/ByteCodeT;
    }
    SourceFile: "ByteCodeT.java"
    View Code

    可以看出,几个方法的复杂度:getName<setName<hi<getToken;我们一步一步来看。

    选择方法getName():

     

     一共三行指令:

     第一行,aload_0,看起来应该是加载东西的,先不管;

    第二行,getfield,后面引用了常量池的2号常量,查看常量池可知,2号常量是字段引用(即为String name)

     第三行,areturn,即是return返回。

    仔细想来,getfield指令传入了参数“Field name:Ljava/lang/String;”并不完整,后半部分只是Ljava/lang/String类型,整个过程究竟取谁的name值?这里get的是谁的field?

    由此可以想到第一步的指令是为第二步的取值准备了数据。

    结合局部变量表来看,aload_0正是取了局部变量表中第0个槽的数据,意思是将Slot 0中的值压入栈中,其值为this,就是当前类的实例,即getfield的主体。

    2.解析

    Jvm大帝是神之旨意的履行者(Jvm大帝就是虚拟机,神就是开发者,神之旨意是开发者写好并编译后的字节码...),当Jvm大帝带领Java世界运行进入了一个新的方法后,会为这个方法在栈内存大陆上创造两个重要的领域:局部变量表操作数栈

    局部变量表里一般会包含this指针(针对实例方法,静态方法当然无此)、方法的所有传入参数方法中所开辟的本地变量

    我们再引入另外一个比喻,如果把运行Java方法理解为拍戏,那么局部变量表里的各个局部变量就是这部戏的核心主角,或者说领衔主演,而操作数栈正是这部戏的舞台。所谓操作数栈搭台,局部变量唱戏,是也。那么aload_0就是告诉Jvm导演(大帝已沦落为导演),请0号演员this同志登台(压栈),演后边的本子。

    当然了,这个比喻并不完全恰当,因为操作数栈并不是“舞台”的结构,而是栈的结构。但是这个比喻可以很好地说明局部变量表和操作数栈之间的关系,以及aload_0的作用。

     getfield做的操作:this入栈——》弹出this——》取值

    3.方法执行深入

    方法执行的操作:

     

    上图展示指令操作数栈局部变量表三者的运作关系

    关于字节码指令,一是指令的功能,二是指令操作的数据类型。先从功能说起,指令主要可以分为如下几类:

    1. 存储和加载类指令:主要包括load系列指令、store系列指令和ldc、push系列指令,主要用于在局部变量表、操作数栈和常量池三者之间进行数据调度;(关于常量池前面没有特别讲解,这个也很简单,顾名思义,就是这个池子里放着各种常量,好比片场的道具库)
    2. 对象操作指令(创建与读写访问):比如我们刚刚的putfield和getfield就属于读写访问的指令,此外还有putstatic/getstatic,还有new系列指令,以及instanceof等指令。
    3. 操作数栈管理指令:如pop和dup,他们只对操作数栈进行操作。
    4. 类型转换指令和运算指令:如add/div/l2i等系列指令,实际上这类指令一般也只对操作数栈进行操作。
    5. 控制跳转指令:这类里包含常用的if系列指令以及goto类指令。
    6. 方法调用和返回指令:主要包括invoke系列指令和return系列指令。这类指令也意味这一个方法空间的开辟和结束,即invoke会唤醒一个新的java方法小宇宙(新的栈和局部变量表),而return则意味着这个宇宙的结束回收。

    指令操作的数据类型来讲:指令开头或尾部的一些字母,就往往表明了它所能操作的数据类型:

    a对应对象,表示指令操作对象性数据,比如aload和astore、areturn等等。
    i对应整形。也就有iload,istore等i系列指令。
    f对应浮点型。
    l对应long,b对应byte,d对应double,c对应char。
    另外地,ia对应int array,aa对应object array,da对应double array。不在一一赘述。

    解析复杂方法:

     解析:

    1. (红色第1部分)new指令执行,引用常量池6号参数,创建一个StringBuilder对象,在内存中开辟空间(并将其引用入栈),用于实现连接字符串功能,类似C++中的运算符重载。
    2. dup指令执行,复制栈顶,并入栈,此时栈深为2(2个引用)——(一般new指令后会跟dup复制一份引用,其中一个引用用于init初始化,一个引用给程序员用于赋值等使用
    3. invokespecial指令执行,使用常量池7号符号引用(<init>方法初始化),此时用到了栈顶的1个引用参数,栈深变为1。
    4. (黄色第2部分)invokestatic指令执行,调用UUID.randomUUID()静态方法,结果压栈,并弹出,调用String的toString方法,结果再压栈,此时栈深2。
    5. (绿色第3部分)字符串“-”、“”入栈,栈深为4,弹出栈顶3个元素,调用String的replace方法,最后一个元素调用方,其余两个为参数,结果入栈,此时栈深2。
    6. (青色第5部分)弹出栈顶2个元素,调用StringBuilder的append方法,最后一个元素为调用方,先弹出的为参数,结果入栈,栈深为1。
    7. (粉色第5部分)aload_0指令执行,将局部变量表的第0个变量压栈(this入栈),栈深为2,;弹出栈顶调用方,调用getfield方法,结果入栈,栈深为2;弹出栈顶调用方,调用hashCode方法,结果入栈,栈深为2。
    8. (洋红第6部分)弹出栈顶2个元素,调用StringBuilder的append方法,最后弹出元素为调用方,先弹出元素为append参数,结果入栈,栈深1;弹出栈顶,调用toString方法,结果压栈,栈深1。
    9. areturn返回栈顶,栈空,结果返回,方法调用完成。

  • 相关阅读:
    一、JQuery选择器
    二、HelloMaven-第一个Maven项目
    一、maven的简介和环境搭建
    Junit源码
    五、spring和Hibernate整合
    JS 冒泡排序从学到优化
    JS小案例(基础好烦恼少)----持续更新
    JS+PHP实现用户输入数字后取得最大的值并显示为第几个
    将博客搬至CSDN
    HTML表单相关
  • 原文地址:https://www.cnblogs.com/limaosheng/p/13685613.html
Copyright © 2020-2023  润新知