不少朋友Java的字符串常量池的概念困擾了很長一段時間,最近研究了一下jvm指令碼,終於對它有了大概的瞭解。 在展現案例前,咱們須要先搞清楚一個概念,衆所周知,jvm的內存模型由程序計數器、虛擬機棧、本地方法棧、堆、元空間(方法區)、直接內存組成。 今天咱們談到的概念只和虛擬機棧、堆、元空間(方法區)有關。 先舉個例子說明兩種關於字符串最基本的使用狀況:java
下面經過javap -v xxx.class命令來查看class文件的指令碼,經過分析這些指令碼更確切的瞭解咱們想知道的問題。筆者的jdk是1.8版本的,版本不一樣可能效果也不一樣。spring
首先展現咱們的源代碼springboot
public class StringCodeTest { public static void main(String[] args) { String a = "abc"; System.out.println(a); } }
咱們執行 javap -v StringCodeTest .class>StringCodeTest.txt命令,將指令碼放到StringCodeTest.txt中。app
Classfile /C:/Users/zhiyi/IdeaProjects/springboottest/target/classes/cn/lizy/service/StringCodeTest.class Last modified 2020-11-7; size 610 bytes MD5 checksum 2adeba87a0f1b315019efe540bb058cd Compiled from "StringCodeTest.java" public class cn.lizy.service.StringCodeTest minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#22 // java/lang/Object."<init>":()V #2 = String #23 // abc #3 = Fieldref #24.#25 // java/lang/System.out:Ljava/io/PrintStream; #4 = Methodref #26.#27 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #28 // cn/lizy/service/StringCodeTest #6 = Class #29 // java/lang/Object #7 = Utf8 <init> #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcn/lizy/service/StringCodeTest; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 a #19 = Utf8 Ljava/lang/String; #20 = Utf8 SourceFile #21 = Utf8 StringCodeTest.java #22 = NameAndType #7:#8 // "<init>":()V #23 = Utf8 abc #24 = Class #30 // java/lang/System #25 = NameAndType #31:#32 // out:Ljava/io/PrintStream; #26 = Class #33 // java/io/PrintStream #27 = NameAndType #34:#35 // println:(Ljava/lang/String;)V #28 = Utf8 cn/lizy/service/StringCodeTest #29 = Utf8 java/lang/Object #30 = Utf8 java/lang/System #31 = Utf8 out #32 = Utf8 Ljava/io/PrintStream; #33 = Utf8 java/io/PrintStream #34 = Utf8 println #35 = Utf8 (Ljava/lang/String;)V { public cn.lizy.service.StringCodeTest(); 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 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcn/lizy/service/StringCodeTest; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: ldc #2 // String abc 2: astore_1 3: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 6: aload_1 7: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 10: return LineNumberTable: line 5: 0 line 6: 3 line 24: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 args [Ljava/lang/String; 3 8 1 a Ljava/lang/String; } SourceFile: "StringCodeTest.java"
關鍵要看Constant pool:(這個就是常量池)和main方法。
jvm
main方法中ldc #2 的意思是把常量池中的#2項壓入到棧,而#2關聯的#23就是「abc」常量。這就說明棧中的對象是直接指向了常量池的。String s = 「abc」只生成了一個字符串對象(常量池中的對象)和一個棧中的引用
ui
源代碼this
public class StringCodeTest { public static void main(String[] args) { String b = new String("abc"); System.out.println(b) } }
爲了節省篇幅,第二個例子和第三個例子的jvm指令碼就再也不展現了,只截圖關鍵點,有須要能夠本身經過javap命令生成spa
main方法中先new #2, 而#2關聯的#25就是String對象, 而後再ldc #3(關聯的#26就是「abc」常量)也就是說,在執行時,先在堆中建立String對象,再把常量池中的#3項壓入到棧(這裏的棧應該是操做數棧,參考最上面的內存模型圖)供String對象使用。因此,棧中的引用指向了堆中的String對象,String對象指向了常量池中的「abc」項,String s = new String(「abc」)生成了一個字符串對象(常量池中的對象)、一個堆中的String對象和一個棧中的引用。
code
源代碼對象
public class StringCodeTest { public static void main(String[] args) { String c = new String("abc")+"abc"; System.out.println(c); } }
main方法中先建立了Stringbuiler對象,而後建立了String對象(就是new String("abc")), 再執行ldc #3將常量池中的字符串壓入到棧。最後作計算,過程和第二個例子差很少,只不過多了個Stringbuiler的append操做。
總結:理解了大致的原理後,再遇到像 ==判斷字符串相等,或者計算String s = new String(「abc」)再內存中建立了幾個對象這樣的問題時思路就清晰了
感謝你看到這裏,文章有什麼不足還請指正,以爲文章對你有幫助的話記得給我點個贊,天天都會分享java相關技術文章或行業資訊,歡迎你們關注和轉發文章!