文章导航:
1.描述:
被真相蒙蔽,是一件很痛苦的事。
希望读者依然本着怀疑的态度,带着求知欲到达理想彼岸。
读懂本篇文章需要会解读字节码文件,请移步:
2.代码案例(JDK8):代码–图解–分析过程
public class TopicString {
public static void main(String[] args) {
String s1 = "1";
String s2 = new String("1");
String s3 = "1" + "2" + "3";
String s4 = "123";
String s5 = "1" + "3" + new String("1") + "4";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
System.out.println(s3 == s4);
System.out.println(s3.equals(s4));
}
}
以上代码在内存中的分布图:
3.提出问题:
- 输出结果(不许偷偷的运行代码哦)
- s1,s2,s3,s4,s5各创建了几个对象?分析原因?
4.分析代码:
问1:如何分析以上代码?心中没有思路...没有底气!!
答1:java文件被javac编译,然后交给jvm,jvm调用机器指令和计算底层对话,并返回结果(总体探索思路)
问2:javac做了什么事?产生的结果是什么?
答2:java文件被编译后,产生一个我们熟悉的class文件(你真的熟悉class文件),那看看class信息吧
a.案例代码class文件主要操作指令信息:
- 查看信息命令:
javap -v java文件名
或者javap -c java文件名
这里笔者用java -v输出的信息
。解读class文件,笔者后续会写一篇解读class信息的文章。暂时看不懂没有关系,主要部分都要图解。
F:\install-dev\java8\bin\javap.exe -v com.lsz.interview.questions.TopicString
Classfile /F:/project-my/pro-github/books/big_talk_java_performance/out/production/big_talk_java_performance/com/lsz/interview/questions/TopicString.class
Last modified 2020-4-29; size 1186 bytes
MD5 checksum d522389930b4243c44f3b7c836f09984
Compiled from "TopicString.java"
public class com.lsz.interview.questions.TopicString
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #16.#40 // java/lang/Object."<init>":()V
#2 = String #41 // 1
#3 = Class #42 // java/lang/String
#4 = Methodref #3.#43 // java/lang/String."<init>":(Ljava/lang/String;)V
#5 = String #44 // 123
#6 = Class #45 // java/lang/StringBuilder
#7 = Methodref #6.#40 // java/lang/StringBuilder."<init>":()V
#8 = String #46 // 13
#9 = Methodref #6.#47 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#10 = String #48 // 4
#11 = Methodref #6.#49 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#12 = Fieldref #50.#51 // java/lang/System.out:Ljava/io/PrintStream;
#13 = Methodref #52.#53 // java/io/PrintStream.println:(Z)V
#14 = Methodref #3.#54 // java/lang/String.equals:(Ljava/lang/Object;)Z
#15 = Class #55 // com/lsz/interview/questions/TopicString
#16 = Class #56 // java/lang/Object
#17 = Utf8 <init>
#18 = Utf8 ()V
#19 = Utf8 Code
#20 = Utf8 LineNumberTable
#21 = Utf8 LocalVariableTable
#22 = Utf8 this
#23 = Utf8 Lcom/lsz/interview/questions/TopicString;
#24 = Utf8 main
#25 = Utf8 ([Ljava/lang/String;)V
#26 = Utf8 args
#27 = Utf8 [Ljava/lang/String;
#28 = Utf8 s1
#29 = Utf8 Ljava/lang/String;
#30 = Utf8 s2
#31 = Utf8 s3
#32 = Utf8 s4
#33 = Utf8 s5
#34 = Utf8 StackMapTable
#35 = Class #27 // "[Ljava/lang/String;"
#36 = Class #42 // java/lang/String
#37 = Class #57 // java/io/PrintStream
#38 = Utf8 SourceFile
#39 = Utf8 TopicString.java
#40 = NameAndType #17:#18 // "<init>":()V
#41 = Utf8 1
#42 = Utf8 java/lang/String
#43 = NameAndType #17:#58 // "<init>":(Ljava/lang/String;)V
#44 = Utf8 123
#45 = Utf8 java/lang/StringBuilder
#46 = Utf8 13
#47 = NameAndType #59:#60 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#48 = Utf8 4
#49 = NameAndType #61:#62 // toString:()Ljava/lang/String;
#50 = Class #63 // java/lang/System
#51 = NameAndType #64:#65 // out:Ljava/io/PrintStream;
#52 = Class #57 // java/io/PrintStream
#53 = NameAndType #66:#67 // println:(Z)V
#54 = NameAndType #68:#69 // equals:(Ljava/lang/Object;)Z
#55 = Utf8 com/lsz/interview/questions/TopicString
#56 = Utf8 java/lang/Object
#57 = Utf8 java/io/PrintStream
#58 = Utf8 (Ljava/lang/String;)V
#59 = Utf8 append
#60 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#61 = Utf8 toString
#62 = Utf8 ()Ljava/lang/String;
#63 = Utf8 java/lang/System
#64 = Utf8 out
#65 = Utf8 Ljava/io/PrintStream;
#66 = Utf8 println
#67 = Utf8 (Z)V
#68 = Utf8 equals
#69 = Utf8 (Ljava/lang/Object;)Z
{
public com.lsz.interview.questions.TopicString();
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 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/lsz/interview/questions/TopicString;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=6, args_size=1
0: ldc #2 // String 1
2: astore_1
3: new #3 // class java/lang/String
6: dup
7: ldc #2 // String 1
9: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
12: astore_2
13: ldc #5 // String 123
15: astore_3
16: ldc #5 // String 123
18: astore 4
20: new #6 // class java/lang/StringBuilder
23: dup
24: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V
27: ldc #8 // String 13
29: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
32: new #3 // class java/lang/String
35: dup
36: ldc #2 // String 1
38: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
41: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
44: ldc #10 // String 4
46: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
49: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
52: astore 5
54: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
57: aload_1
58: aload_2
59: if_acmpne 66
62: iconst_1
63: goto 67
66: iconst_0
67: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V
70: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
73: aload_1
74: aload_2
75: invokevirtual #14 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
78: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V
81: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
84: aload_3
85: aload 4
87: if_acmpne 94
90: iconst_1
91: goto 95
94: iconst_0
95: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V
98: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream;
101: aload_3
102: aload 4
104: invokevirtual #14 // Method java/lang/String.equals:(Ljava/lang/Object;)Z
107: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V
110: return
LineNumberTable:
line 14: 0
line 15: 3
line 16: 13
line 17: 16
line 18: 20
line 20: 54
line 21: 70
line 22: 81
line 23: 98
line 24: 110
LocalVariableTable:
Start Length Slot Name Signature
0 111 0 args [Ljava/lang/String;
3 108 1 s1 Ljava/lang/String;
13 98 2 s2 Ljava/lang/String;
16 95 3 s3 Ljava/lang/String;
20 91 4 s4 Ljava/lang/String;
54 57 5 s5 Ljava/lang/String;
StackMapTable: number_of_entries = 4
frame_type = 255 /* full_frame */
offset_delta = 66
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
frame_type = 90 /* same_locals_1_stack_item */
stack = [ class java/io/PrintStream ]
frame_type = 255 /* full_frame */
offset_delta = 0
locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]
stack = [ class java/io/PrintStream, int ]
}
SourceFile: "TopicString.java"
}
SourceFile: "TopicString.java"
分析得出初始图:
b.main方法中的指令解读::
编译之后,class文件只是一个普通本地文件,存在C盘或者D盘而已,和txt、excel、word一样都是一个"静态文件"。因此java文件,
接下会进行class类加载,将静态文件class加载到内存中,jvm读取指令然后操作指令
# 表示:多少号,例如:#2表示常量池中的2号位置
常量池数据类型:boolean、byte、short、int、char、float、reference或者returnAddress
第一组指令:String s1 = "1";
- 指令:
ldc
将常量池中的数据类型推入栈顶 - 指令:
astore_1
将栈顶类型值保存到局部变量表slot=1位置
将常量池中 #2 位置的值推到栈顶,从反编译得知:// 执行指令 0: ldc #2 // String 1 2: astore_1 // 对应源码 String s1 = "1";
#2
最终的结果是:String 1
,但是常量池中的数据类型没有String啊,是的!因此这里保存的是引用地址
,也就是常量池数据类型中的reference类型
,出栈。执行指令后:
第二组指令:String s2 = new String("1");
-
指令:
new
创建新的对象实例,并将引用(reference)地址
压入栈 -
指令:
dup
复制栈顶数值,并将复制值压入栈顶 -
指令:
ldc
将常量池中的数据类型推入栈顶 -
指令:
invokespecial
编译时调用方法,初始化对象值,出栈。 -
指令:
astore_2
将栈顶类型保存到局部变量表slot=2的位置,出栈。// 执行指令 3: new #3 // class java/lang/String 6: dup 7: ldc #2 // String 1 9: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 12: astore_2 // 对应的源码 String s2 = new String("1");
1.执行
3: new #3
指令和dup
指令
2.执行7: ldc #2
指令和9: invokespecial #4
指令:
3.执行
12: astore_2
指令:
第三组指令:String s3 = "1" + "2" + "3";
和String s4 = "123";
-
类比上边的参数解读
// 执行指令 13: ldc #5 // String 123 15: astore_3 16: ldc #5 // String 123 18: astore 4 // 对应的源码 String s3 = "1" + "2" + "3"; String s4 = "123";
问:为什么s3和s4的地址一样?
答:因为java编译器在编译的时候优化了,这个概念称为:常量折叠,感兴趣的童鞋可以去看javac源码
第四组指令:String s5 = "1" + "3" + new String("1") + "4";
-
类比上边的参数解读
// 执行指令 20: new #6 // class java/lang/StringBuilder 23: dup 24: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 27: ldc #8 // String 13 29: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 32: new #3 // class java/lang/String 35: dup 36: ldc #2 // String 1 38: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 41: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 44: ldc #10 // String 4 46: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 49: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 52: astore 5 // 对应的源码 String s5 = "1" + "3" + new String("1") + "4";
再往下读:注意的指令
aload_1:从局部变量表slot=1位置的数据类型推到栈顶
aload_2:从局部变量表slot=2位置的数据类型推到栈顶
然后进行比较,这样看就知道== 和 equals的区别,答案就在眼前
5.得出总的结果分析图:
5.细看String类型:
- JDK源码:
public final class String implements java.io.Serializable,Comparable<String>,CharSequence { /**字符存储 */ private final char value[]; /** 缓存字符串的哈希码 默认值为0*/ private int hash; /**无参构造方法*/ public String() { this.value = "".value; } /**其它构造方法之一*/ public String(char value[]) { this.value = Arrays.copyOf(value, value.length); } }
- 代码示例:
查看String在内存中对象布局,public class TopicString4 { public static void main(String[] args){ char c1 = 'a'; char c2 = 'b'; char c3 = 'c'; char[] data = {c1, c2, c3}; String str1 = new String(data); String str2 = "abc"; System.out.println(str1 == str2); System.out.println(str1.equals(str2)); } }
上图发现:创建String并赋值,实质是创建一个char类型的数组,String引用char[]
6.附加信息(验证字符串常量池的位置):
public class StringPoolPosition {
static String base = "string";
public static void main(String[] args) {
// 打印java版本
System.out.println(System.getProperty("java.version"));
for (int i = 0; i < Integer.MAX_VALUE; i++) {
base = base.concat(base);
// 插入到字符常量池中
base.intern();
}
}
}
// 结果分析
1.8.0_181
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.String.concat(String.java:2032)
at com.lsz.stack.StringPoolPosition.main(StringPoolPosition.java:16)
// 字符串常量池,在堆中
7.结语:
切勿人云亦云,不轻易被真相蒙蔽。
有问题可以私信或者评论下方留言哦!! 希望对你有帮助!!!