講一講Java的字符串常量池,看完你的思路就清晰了

前言

不少朋友Java的字符串常量池的概念困擾了很長一段時間,最近研究了一下jvm指令碼,終於對它有了大概的瞭解。 在展現案例前,咱們須要先搞清楚一個概念,衆所周知,jvm的內存模型由程序計數器、虛擬機棧、本地方法棧、堆、元空間(方法區)、直接內存組成。 今天咱們談到的概念只和虛擬機棧、堆、元空間(方法區)有關。 先舉個例子說明兩種關於字符串最基本的使用狀況:java

  • String s =「abc」;在編譯期間,會將等號右邊的「abc」常量放在常量池中,在程序運行時,會將s變量壓棧,棧中s變量直接指向元空間的字符串常量池abc項,沒有通過堆內存。
  • String s = new String(「abc」);在編譯期間,會將等號右邊的「abc」常量放在常量池中,在程序運行時,先在堆中建立一個String對象,該對象的內容指向常量池的「abc」項。而後將s變量壓棧,棧中s變量指向堆中的String對象。

下面經過javap -v xxx.class命令來查看class文件的指令碼,經過分析這些指令碼更確切的瞭解咱們想知道的問題。筆者的jdk是1.8版本的,版本不一樣可能效果也不一樣。spring

第一個例子:String a = 「abc」

首先展現咱們的源代碼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

第二個例子 String s = new String(「abc」);

源代碼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

第三個例子:String c = new String(「abc」)+「abc」;

源代碼對象

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相關技術文章或行業資訊,歡迎你們關注和轉發文章!

相關文章
相關標籤/搜索