java 字节码 简单入门

环境

java:1.7

前言

最近在了解StringStringBuilder
类似如下代码:

package test;

public class StringBuilderTest {

    public static void main(String[] args) {
        String result = "bb";
        for(int i=0; i<20;i++){
            result += "a";
            new StringBuilder().append(result);
        }
        System.out.println(result);
    }
}

我们都知道result += "a";这段代码,JVM会不断的创建StringBuilder对象。
但是从字节码层面更好的理解呢?
经过翻阅资料,自己人肉演示,算是有点眉目了。

字节码

我们先来看下,它的字节码:

执行:javap -v .\StringBuilderTest > test.txt

这个javap -v命令让我好找啊,之前一直执行javap -c -l -p -s .\StringBuilderTest 就是没有我想要的常量池。

Classfile /E:/java_project/execlTest/bin/test/StringBuilderTest.class
  Last modified 2018-5-17; size 937 bytes
  MD5 checksum 9477783775fde82741d8c8d78a153df4
  Compiled from "StringBuilderTest.java"
public class test.StringBuilderTest
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // test/StringBuilderTest
   #2 = Utf8               test/StringBuilderTest
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Utf8               Code
   #8 = Methodref          #3.#9          // java/lang/Object."<init>":()V
   #9 = NameAndType        #5:#6          // "<init>":()V
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Ltest/StringBuilderTest;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = String             #17            // bb
  #17 = Utf8               bb
  #18 = Class              #19            // java/lang/StringBuilder
  #19 = Utf8               java/lang/StringBuilder
  #20 = Methodref          #21.#23        // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #21 = Class              #22            // java/lang/String
  #22 = Utf8               java/lang/String
  #23 = NameAndType        #24:#25        // valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #24 = Utf8               valueOf
  #25 = Utf8               (Ljava/lang/Object;)Ljava/lang/String;
  #26 = Methodref          #18.#27        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #27 = NameAndType        #5:#28         // "<init>":(Ljava/lang/String;)V
  #28 = Utf8               (Ljava/lang/String;)V
  #29 = String             #30            // a
  #30 = Utf8               a
  #31 = Methodref          #18.#32        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #32 = NameAndType        #33:#34        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #33 = Utf8               append
  #34 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #35 = Methodref          #18.#36        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #36 = NameAndType        #37:#38        // toString:()Ljava/lang/String;
  #37 = Utf8               toString
  #38 = Utf8               ()Ljava/lang/String;
  #39 = Fieldref           #40.#42        // java/lang/System.out:Ljava/io/PrintStream;
  #40 = Class              #41            // java/lang/System
  #41 = Utf8               java/lang/System
  #42 = NameAndType        #43:#44        // out:Ljava/io/PrintStream;
  #43 = Utf8               out
  #44 = Utf8               Ljava/io/PrintStream;
  #45 = Methodref          #46.#48        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #46 = Class              #47            // java/io/PrintStream
  #47 = Utf8               java/io/PrintStream
  #48 = NameAndType        #49:#28        // println:(Ljava/lang/String;)V
  #49 = Utf8               println
  #50 = Utf8               args
  #51 = Utf8               [Ljava/lang/String;
  #52 = Utf8               result
  #53 = Utf8               Ljava/lang/String;
  #54 = Utf8               i
  #55 = Utf8               I
  #56 = Utf8               StackMapTable
  #57 = Utf8               SourceFile
  #58 = Utf8               StringBuilderTest.java
{
  public test.StringBuilderTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #8                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ltest/StringBuilderTest;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=3, args_size=1
         0: ldc           #16                 // String bb
         2: astore_1
         3: iconst_0
         4: istore_2
         5: goto          31
         8: new           #18                 // class java/lang/StringBuilder
        11: dup
        12: aload_1
        13: invokestatic  #20                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        16: invokespecial #26                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        19: ldc           #29                 // String a
        21: invokevirtual #31                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #35                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore_1
        28: iinc          2, 1
        31: iload_2
        32: bipush        20
        34: if_icmplt     8
        37: getstatic     #39                 // Field java/lang/System.out:Ljava/io/PrintStream;
        40: aload_1
        41: invokevirtual #45                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        44: return
      LineNumberTable:
        line 6: 0
        line 7: 3
        line 8: 8
        line 7: 28
        line 10: 37
        line 11: 44
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      45     0  args   [Ljava/lang/String;
            3      42     1 result   Ljava/lang/String;
            5      32     2     i   I
      StackMapTable: number_of_entries = 2
        frame_type = 253 /* append */
          offset_delta = 8
          locals = [ class java/lang/String, int ]
        frame_type = 22 /* same */
}
SourceFile: "StringBuilderTest.java"

分析

说明
[ 0: this, 1: x, 2: undefined | this, null ]
这种记法是我参考RednaxelaFX大佬的:
这个记法用方括号括住一个Java栈帧的状态。中间竖线是分隔符,左边是局部变量区,右边是操作数栈。局部变量区每个slot有标号,也就是slot number,这块可以随机访问;操作数栈的slot则没有标号,通常只能访问栈顶或栈顶附近的slot。跟之前用的记法类似,操作数栈也是靠左边是栈底,靠右边是栈顶。局部变量区里如果有slot尚未赋初始值的话,则标记为undefined。

[0:udf, 1:udf, 2:udf | "bb"]  --- ldc  加载 字符串 "bb"
[0:udf, 1:"bb", 2:udf | ] --- astore_1 将栈顶元素"bb"弹出,赋值给slot为1的变量
[0:udf, 1:"bb", 2:udf | 0] --- iconst_0 加载int型常量 0
[0:udf, 1:"bb", 2:0 | ] --- istore_2 将栈顶元素2弹出,赋值给slot为2的变量

goto  31 --- 无条件跳转语句,跳转到31//这一步其实是为了判断刚进for循环时,是否满足条件:0<20显然满足条件

[0:udf, 1:"bb", 2:0 | "StringBuilder"] --- new 创建一个StringBuilder对象

[0:udf, 1:"bb", 2:0 | "StringBuilder", "StringBuilder"] --- dup 赋值栈顶元素,并入栈

[0:udf, 1:"bb", 2:0 | "StringBuilder", "StringBuilder", "bb"] --- aload_1 将slot为1的值复制到栈顶

[0:udf, 1:"bb", 2:0 | "StringBuilder", "StringBuilder", "bb"] --- invokestatic String.valueOf("bb")

    [0:udf, 1:"bb", 2:0 | "StringBuilder", "StringBuilder", "bb"] --- invokespecial 调用类的构造器 弹出栈顶两个元素初始化
    [0:udf, 1:"bb", 2:0 | "StringBuilder"]

[0:udf, 1:"bb", 2:0 | "StringBuilder", "a"] --- ldc 加载常量"a"
[0:udf, 1:"bb", 2:0 | "StringBuilder", "a"] --- invokevirtual 调用append("a")方法,拼接成 "bba"
    [0:udf, 1:"bb", 2:0 | ] --- 弹出 "StringBuilder", "a"  调用append("a")方法,拼接成 "bba"
    [0:udf, 1:"bb", 2:0 | "StringBuilder"] 因为有返回值,所以这个值会重新进栈

[0:udf, 1:"bb", 2:0 | "StringBuilder"] --- invokevirtual 根据方法toString()可知,弹出栈顶元素 进行toString()的操作
    [0:udf, 1:"bb", 2:0 | "String"] --- "StringBuilder".toString(); 返回的String,该值("bba")要重新入栈

[0:udf, 1:"bb", 2:0 | "String"] --- astore_1 将栈顶元素弹出赋值给slot为1的变量
    [0:udf, 1:"bba", 2:0 | ] 

[0:udf, 1:"bba", 2:0 | ] --- iinc   slot为2的,加1
    [0:udf, 1:"bba", 2:1 | ]

[0:udf, 1:"bba", 2:1 | ] --- iload_2 加载slot为2的值复制到栈顶
    [0:udf, 1:"bba", 2:1 | 1]

[0:udf, 1:"bba", 2:1 | 1] --- bipush 将字节常量20压入栈顶
    [0:udf, 1:"bba", 2:1 | 1,20]

[0:udf, 1:"bba", 2:1 | 1,20] --- if_icmplt 弹出栈顶两个元素 进行比较,如果为真,就跳转8
    [0:udf, 1:"bba", 2:1 | ]

[0:udf, 1:"bba", 2:1 | ] --- getstatic 加载字段System.out,其是PrintStream
    [0:udf, 1:"bba", 2:1 | "PrintStream"]
[0:udf, 1:"bba", 2:1 | "PrintStream"] --- aload_1 将slot为1的变量的值复制并压入栈顶
    [0:udf, 1:"bba", 2:1 | "PrintStream", "bba"]

[0:udf, 1:"bba", 2:1 | "PrintStream", "bba"] --- invokevirtual 根据方法可知,弹出栈顶两个元素,并执行PrintStream.println
    [0:udf, 1:"bba", 2:1 | ] --- System.out.println("bba")
return

这里面的分析,大部分都OK;只有少部分难懂;
比如:invokespecial这道指令就是去调用构造方法的;因为构造方法属于特殊方法;
invokestatic 这道指令是用来调静态方法的;
invokevirtual 这道指定是用来调实例方法的;

注意:不管哪种指令,如果调用的方法有返回值,都会在方法调用完毕后将返回值压入栈顶;并且参数都会丢弃掉!
这句话是RednaxelaFX大佬给我的答复
详情查看知乎对话:https://www.zhihu.com/question/52749416

总结

目前只是简单的入门,路还很长

参考地址:

Jvm系列2—字节码指令

关于JVM字节码中dup指令的问题?

猜你喜欢

转载自blog.csdn.net/u013066244/article/details/80358302