环境
java:1.7
前言
最近在了解String
和StringBuilder
;
类似如下代码:
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
这道指定是用来调实例方法的;
局部变量区有多少个slot是传入的参数可以看javap
输出的“args_size
”属性;
局部变量区总共有多少个slot
可以看“locals
”属性;
操作数栈最高的高度可以看“stack
“属性
注意:不管哪种指令,如果调用的方法有返回值,都会在方法调用完毕后将返回值压入栈顶;并且参数都会丢弃掉!
这句话是RednaxelaFX
大佬给我的答复
详情查看知乎对话:https://www.zhihu.com/question/52749416
总结
目前只是简单的入门,路还很长
参考地址: