常量池

我們都知道,在堆中有一個方法區,但有時也稱之爲非堆,它是各個線程共享的內存區域。用來存儲加載的類信息、常量、靜態變量、即時編譯器編譯後的大麥等數據。這裏面有一個常量池,而不是運行時常量池。這裏的常量池是用來存放在jvm編譯器期間,就已經確定的常量,比如以下的代碼:

測試對象

在創建測試對象前,我們需要創建兩個Javabean類

Student類

創建Student類:
public class Student {

    public String sname;
    public Integer sno;

    public Student() {
        super();
    }

    public Student(String sname) {
        this.sname = sname;
    }

    public Student( int sno,String name) {
        this.sname = name;
        this.sno = sno;
    }
    public String getName() {
        return sname;
    }
    public void setName(String name) {
        this.sname = name;
    }
    public int getSno() {
        return sno;
    }
    public void setSno(int sno) {
        this.sno = sno;
    }

    public String getString(){
        return "你好";
    }

    public static Student getStaticString(){
        return new Student();
    }

    public static String getRepeatedString(){
        return new Student().getString();
    }

SClass類型

static class SClass {

    private Map<Integer, List<Student>> maps;
    private String className;

    public SClass(Map<Integer, List<Student>> maps, String className) {
        this.maps = maps;
        this.className = className;
    }

    public Map<Integer, List<Student>> getMaps() {
        return maps;
    }

    public void setMaps(Map<Integer, List<Student>> maps) {
        this.maps = maps;
    }

    public String getClassName() {
        return className;
    }

    public SClass() {

    }

    public void setClassName(String className) {
        this.className = className;
    }

    public String getStr(){
        return "你好";
    }
}

測試類

public static void main(String[] args) {

    Student stu1=new Student("jack");
    Student stu2=new Student("tom");

    String stu1Str=stu1.getString();
    String stu2Str=stu1.getString();

    //語句1
    System.out.println("stu1Str is equals to stu2Str:\t"+(stu1Str==stu2Str));

    //語句2
    System.out.println("\n"+Student.getStaticString());
    System.out.println(Student.getStaticString());
    System.out.println(stu1.getStaticString());
    System.out.println(Student.getStaticString()==stu1.getStaticString());

    String strReapeatedStr1=stu1.getRepeatedString();
    String strRepeatedStr2=Student.getRepeatedString();
    //語句3
    System.out.println("\nstrReapeatedStr1 is equal to strRepeatedStr2:\t"+(strReapeatedStr1==strRepeatedStr2));

    String str=new String("jack");
    String str1="jack";
    //語句4
    System.out.println("\nstr is equals to str1:\t"+(str1==str));
    System.out.println("str1 is equals to stu.getName():"+(str1==stu1.getName()));

    String thisStr1=getString();
    String thisStr2="meili";
    String thisStr3=getString("meili");
    //語句5
    System.out.println("\nthisStr1 is equal to thisStr2:\t"+(thisStr1==thisStr2));
    System.out.println("\nthisStr2 is equal to thisStr3:\t"+(thisStr2==thisStr3));

    //語句6
    String studengStr=new Student().getString();
    String classStr=new SClass().getString();
    System.out.println("\nstudengStr is equal to classStr:\t"+(studengStr==classStr));
}

分析語句1

語句1的輸出結果爲:stu1Str is equals to stu2Str: true

我們明明是說,當我們在創建一個對象時,會在棧裏面分配一個地址,棧中的地址有一個別名,即爲實例對象名。地址存存儲着引用堆中對象的首地址。如果兩個別名存儲的對象引用地址的不同,那麼這兩個別名對應的棧地址就不相同。
堆空間隨機分配的一個地址來存儲對象的信息,每個對象都有自己的屬性,並不包括對象的方法和常量池。常量池是放在方法區中的,是在編譯時期就已經確定的大小和類型的常量。方法是存儲在類空間,每個對象都可以共享類的方法,因而,語句1中返回的是方法的中的數據。數據時在編譯時期就已經確定,並且放在常量池中,那麼它是按照什麼規則來安放的?

比如說在聲明第一句時,Student stu1=new Student(“jack”);在編譯器期間時,jvm會在堆中分配一個地址空間,用來存放常量數據的,這個常量數據包括——直接常量(String)和對其他類型的初始化值、方法內中聲明的局部常量、字段的符號引用。池中的數據和數組一樣通過索引訪問。其次,jvm就會去常量池中,查找是否存有“jack”這個堆地址,如果沒有“jack”這個堆地址,它就隨機分配一個地址,來存放“jack”這個常量。如果沒有存放“jack”這個常量的地址,那麼就會隨機分配一個地址,來存放“jack”這個常量的二進制數據。如果再有類的方法、構造器、String中聲明這個“jack”時,變量就可以直接指向“jack”地址的引用。諸如“你好”,也是同樣的道理。

分析語句2

語句2輸出結果爲:
    com.mybaits.entity.Student@6084fa6a
    com.mybaits.entity.Student@3a5476a7
    com.mybaits.entity.Student@7f39ebdb
    false

爲什麼語句2不相等呢?正如我們知道的那樣,方法區還有類信息的數據,包括靜態變量、靜態方法等。對象可以訪問靜態方法,這是爲什麼呢?在靜態方法中不能訪問私有私有方法,但在私有方法可以訪問靜態方法,由此,也可以說明對象可以訪問靜態方法。我們扯遠了,把話題再繞回來。我在Student類定義的這個方法:

public static Student getStaticString(){
    return new Student();
}

它返回的是一個對象,該對象信息存儲在堆空間的一個隨機地址中,這也是我們可以不用通過new來創建對象的一個方式。既然創建的每一個對象的堆空間的地址是隨機分配的,那麼每一個對象的實例變量名所引用的實例對象的首地址都是不一樣的,雖然我沒有聲明對象名,但這是匿名對象,因而,他們他們是不相同的。

語句3

語句三的輸出結果:
    strReapeatedStr1 is equal to strRepeatedStr2:   true

爲什麼語句3的輸出結果爲true?
你看看我在Student類中定義的方法:
    public static String getRepeatedString(){
        return new Student().getString();
    }

雖然每個調用這個方法時,在堆空間都會生成不同地址的Student對象,但是Student對象中只包含各自的屬性,而方法是在類信息中的供所有對象的來共享的。當類加載到內存空間時,“構造器,組合關係中的對象的數據,方法中的數據等”就已加載到常量池中了。因而,不論調用了多少次getRepeatedString()方法,其內部都會返回“你好”對象的引用。
因而,語句3是true

分析語句4

語句4的輸出結果:
    str is equals to str1:  false

    str1 is equals to stu.getName():true

str確實不等於str1,爲什麼呢?str存儲的是指向new String(“jack”)的首地址,而str1存儲的是指向在常量池中的對象“jack”的地址,它是在編譯器就已經放進常量池中的,因而,str1不等於str。但stu1.getName()是不同的,它返回的是指向常量池中“jack”的地址,而str1存儲的也是這個地址,因而,後者是相等。

分析語句5

語句5的輸出結果:
    thisStr1 is equal to thisStr2:  true

    thisStr2 is equal to thisStr3:  true

同時,這個也是相等,因爲各自返回的是常量池中的“meili”的地址,因而,是相等。

分析語句6

語句6的輸出結果:
    studengStr is equal to classStr:    true

爲什麼也爲true呢?
    SClass類的該方法:
        public String getString(){
            return "你好";
        }
    Student類的方法:
        public String getStr(){
            return "你好";
        }

常量池不是針對哪個類的,而是針對整體數據的,當類加載Student時,會在常量池中遍歷,是否存有“你好”的隨機地址,如果沒有則分配並返回“你好”對象的引用,如果有則直接放回“你好”對象的引用,這就類似於String.intern()方法一樣。既然他們返回的是同一個地址的引用,自然就想等了。

以上就是個人對常量池的分析,如果有不對的地方,還請大牛指教。

發佈了54 篇原創文章 · 獲贊 30 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章