Jvm常量池、運行時常量池、字符串常量池理解

常量池

是.class文件的常量池,也可以理解爲一張表,虛擬機指令根據這張常量表找到要執行的類名,方法名,參數類型,字面量等信息

運行時常量池

常量池是*.class文件中的,當該類被加載,它的常量池信息就會放入運行時常量池,並把裏面的符號地址變爲真實地址

常量池只有類文件在編譯的時候纔會產生,而且是存儲在類文件中的。而運行時常量池是在方法區,而且可在JVM運行期間動態向運行時常量池中寫入數據。

字符串常量池(string pool)

字符串常量池裏的內容是在類加載完成,經過驗證,準備階段之後在堆中生成字符串對象實例,然後將該字符串對象實例的引用值存到string pool中(記住:string pool中存的是引用值而不是具體的實例對象,具體的實例對象是在堆中開闢的一塊空間存放的)。string pool在每個HotSpot VM的實例只有一份,被所有的類共享。在jdk1.8後,將String常量池放到了堆中。

String table還存在一個hash表的特性,裏面不存在相同的兩個字符串,默認容量爲1009。當字符串常量池中的存儲比較多的字符串時,會導致hash衝突,從而每個節點形成長長的鏈表,導致性能下降。所以在使用字符串常量池時,一定要控制容量。

一道面試題

package com.example;

public class Test {

    public static void main(String[] args) {
        String s1 = "a";

        String s2 = "b";

        String s3 = "a" + "b";

        String s4 = s1 + s2;

        String s5 = "ab";

        String s6 =s4.intern();

        System.out.println(s3 == s4);

        System.out.println(s3 == s5);

        System.out.println(s3 == s6);
    }

}

輸出的結果是:false true true

代碼分析

我們使用javap -v 類名命令反編譯我們的類

貼上我的反編譯類

Classfile /D:/myproject/cloud-parent/student-provider-1001/target/classes/com/example/Test.class
  Last modified 2020-5-4; size 1107 bytes
  MD5 checksum c61895056cf3de488b1ad949bccf93d2
  Compiled from "Test.java"
public class com.example.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #13.#38        // java/lang/Object."<init>":()V
   #2 = String             #39            // a
   #3 = String             #40            // b
   #4 = String             #41            // ab
   #5 = Class              #42            // java/lang/StringBuilder
   #6 = Methodref          #5.#38         // java/lang/StringBuilder."<init>":()V
   #7 = Methodref          #5.#43         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #5.#44         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #9 = Methodref          #45.#46        // java/lang/String.intern:()Ljava/lang/String;
  #10 = Fieldref           #47.#48        // java/lang/System.out:Ljava/io/PrintStream;
  #11 = Methodref          #49.#50        // java/io/PrintStream.println:(Z)V
  #12 = Class              #51            // com/example/Test
  #13 = Class              #52            // java/lang/Object
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               LocalVariableTable
  #19 = Utf8               this
  #20 = Utf8               Lcom/example/Test;
  #21 = Utf8               main
  #22 = Utf8               ([Ljava/lang/String;)V
  #23 = Utf8               args
  #24 = Utf8               [Ljava/lang/String;
  #25 = Utf8               s1
  #26 = Utf8               Ljava/lang/String;
  #27 = Utf8               s2
  #28 = Utf8               s3
  #29 = Utf8               s4
  #30 = Utf8               s5
  #31 = Utf8               s6
  #32 = Utf8               StackMapTable
  #33 = Class              #24            // "[Ljava/lang/String;"
  #34 = Class              #53            // java/lang/String
  #35 = Class              #54            // java/io/PrintStream
  #36 = Utf8               SourceFile
  #37 = Utf8               Test.java
  #38 = NameAndType        #14:#15        // "<init>":()V
  #39 = Utf8               a
  #40 = Utf8               b
  #41 = Utf8               ab
  #42 = Utf8               java/lang/StringBuilder
  #43 = NameAndType        #55:#56        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #44 = NameAndType        #57:#58        // toString:()Ljava/lang/String;
  #45 = Class              #53            // java/lang/String
  #46 = NameAndType        #59:#58        // intern:()Ljava/lang/String;
  #47 = Class              #60            // java/lang/System
  #48 = NameAndType        #61:#62        // out:Ljava/io/PrintStream;
  #49 = Class              #54            // java/io/PrintStream
  #50 = NameAndType        #63:#64        // println:(Z)V
  #51 = Utf8               com/example/Test
  #52 = Utf8               java/lang/Object
  #53 = Utf8               java/lang/String
  #54 = Utf8               java/io/PrintStream
  #55 = Utf8               append
  #56 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #57 = Utf8               toString
  #58 = Utf8               ()Ljava/lang/String;
  #59 = Utf8               intern
  #60 = Utf8               java/lang/System
  #61 = Utf8               out
  #62 = Utf8               Ljava/io/PrintStream;
  #63 = Utf8               println
  #64 = Utf8               (Z)V
{
  public com.example.Test();
    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   Lcom/example/Test;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=7, args_size=1
         0: ldc           #2                  // String a
         2: astore_1
         3: ldc           #3                  // String b
         5: astore_2
         6: ldc           #4                  // String ab
         8: astore_3
         9: new           #5                  // class java/lang/StringBuilder
        12: dup
        13: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        16: aload_1
        17: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        20: aload_2
        21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: astore        4
        29: ldc           #4                  // String ab
        31: astore        5
        33: aload         4
        35: invokevirtual #9                  // Method java/lang/String.intern:()Ljava/lang/String;
        38: astore        6
        40: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        43: aload_3
        44: aload         4
        46: if_acmpne     53
        49: iconst_1
        50: goto          54
        53: iconst_0
        54: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
        57: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        60: aload_3
        61: aload         5
        63: if_acmpne     70
        66: iconst_1
        67: goto          71
        70: iconst_0
        71: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
        74: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
        77: aload_3
        78: aload         6
        80: if_acmpne     87
        83: iconst_1
        84: goto          88
        87: iconst_0
        88: invokevirtual #11                 // Method java/io/PrintStream.println:(Z)V
        91: return
      LineNumberTable:
        line 6: 0
        line 8: 3
        line 10: 6
        line 12: 9
        line 14: 29
        line 16: 33
        line 18: 40
        line 20: 57
        line 22: 74
        line 23: 91
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      92     0  args   [Ljava/lang/String;
            3      89     1    s1   Ljava/lang/String;
            6      86     2    s2   Ljava/lang/String;
            9      83     3    s3   Ljava/lang/String;
           29      63     4    s4   Ljava/lang/String;
           33      59     5    s5   Ljava/lang/String;
           40      52     6    s6   Ljava/lang/String;
      StackMapTable: number_of_entries = 6
        frame_type = 255 /* full_frame */
          offset_delta = 53
          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, 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, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 79 /* 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, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
        frame_type = 79 /* 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, class java/lang/String ]
          stack = [ class java/io/PrintStream, int ]
}
SourceFile: "Test.java"

Process finished with exit code 0

s3=s4輸出false

我們看如圖反編譯代碼紅圈圈出部分:

從反編譯代碼中我們可以看出,String s4 = s1 + s2;  其實就是new StringBuilder().append("a“).append("b").toString,查看StringBuilder源碼的toString方法會發現,調用了new String(),那麼就會在堆中重新開闢一個內存空間,與之前的s3字符串常量池沒什麼關係,也就是指向地址都不一樣了,所以會輸出false

s3=s5輸出true

看如圖反編譯代碼紅圈圈出部分:

可以看出s3和s5都是對#4常量池的引用,爲true的原因是jvm存在編譯期優化的機制,在編譯期(javac *.java時)會將可以拼接的字符串常量幫你自動拼接了,由於字符串常量池中已經存在了,因此會讓s3指向一個與s5相同的那塊地址,因此爲true。

s3=s6輸出true

簡單介紹intern方法,可以使用intern方法,主動將串池中還沒有的字符串對象放入字符串常量池

通過intern方法主動將s4的字符串放入了字符串常量池,將這個字符串對象嘗試放入串池,如果有則不會放入,如果沒有則放入串池,會把串池中的對象返回,在放入s4的時候恰好存在,因此返回該存在的引用,所以s3 = s6爲true

考慮假如運行環境爲jdk1.6,s3==s6?

1.6將這個字符串對象嘗試放入串池,如果有則不會放入,如果沒有會把此對象複製一份,會把串池中的對象返回。

若常量池中不存在等值的字符串,JVM就會在常量池中創建一個等值的字符串,然後返回該字符串的引用

所以s3和s6的指向地址並不相同,所以會返回false

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章