Java中幾種常量池的區分 -->按此文章說法: 真實的字符串對象仍是在堆中,字符串常量池中存的是引用。git
常量池是爲了不頻繁的建立和銷燬對象而影響系統性能,其實現了對象的共享。例如字符串常量池,在編譯階段就把全部的字符串文字放到一個常量池中github
(1)節省內存空間:常量池中全部相同的字符串常量被合併,只佔用一個空間。緩存
(2)節省運行時間:比較字符串時,==比equals()快。對於兩個引用變量,只用==判斷引用是否相等,也就能夠判斷實際值是否相等安全
Class 文件常量池指的是編譯生成的 class 字節碼文件,其結構中有一項是常量池(Constant Pool Table),用於存放編譯期生成的各類字面量(Literal)和符號引用(Symbolic References),這部份內容將在類加載後進入方法區的運行時常量池中存放。函數
運行時常量池是方法區的一部分,是一塊內存區域。Class 文件常量池將在類加載後進入方法區的運行時常量池中存放, 每個類加載到 JVM 中後對應一個運行時常量池。運行時常量池相對於 Class 文件常量池來講具有動態性,Class 文件常量只是一個靜態存儲結構,裏面的引用都是符號引用。而運行時常量池能夠在運行期間將一部分符號引用解析爲直接引用。好比說類的靜態方法或私有方法,實例構造方法,父類方法,這是由於這些方法不能被重寫其餘版本,因此能在加載的時候就能夠將符號引用轉變爲直接引用,而其餘的一些方法是在這個方法被第一次調用的時候纔會將符號引用轉變爲直接引用的。將符號引用替換成直接引用,解析的過程會去查詢全局字符串池StringTable,以保證運行時常量池所引用的字符串與全局常量池中的引用值保持一致。性能
能夠說運行時常量池就是用來索引和查找字段和方法名稱和描述符的,給定任意一個方法或字段的索引,經過這個索引最終可獲得該方法或字段所屬的類型信息和名稱及描述符信息,這涉及到方法的調用和字段獲取。this
字符串常量池是全局的,JVM 中獨此一份,所以也稱爲全局字符串常量池。運行時常量池中的字符串字面量如果已經明確初始化賦值的成員變量,則在類的加載初始化階段就使用到了字符串常量池;如果本地的,則在使用到的時候(執行此代碼時)纔會使用到字符串常量池。
經過stringTable維護,它是一個哈希表,裏面存的是駐留字符串(也就是常說的用雙引號括起來的)的引用(而不是駐留字符串實例自己),也就是說在堆中的某些字符串實例被這個StringTable引用以後就等同被賦予了」駐留字符串」的身份。這個StringTable在每一個HotSpot VM的實例只有一份,被全部的類共享。spa
字符串常量池實現的前提條件就是Java中String對象是不可變的,這樣能夠安全保證多個變量共享同一個對象。若是Java中的String對象可變的話,一個引用操做改變了對象的值,那麼其餘的變量也會受到影響,顯然這樣是不合理的。.net
「使用常量池」對應的字節碼是一個 ldc <在編譯文件中實錘>指令。在給 String 類型的引用賦值的時候會先執行這個指令,看常量池中是否存在這個字符串對象的引用,如有就直接返回這個引用,若沒有,就在堆裏建立這個字符串對象並在字符串常量池中記錄下這個引用(jdk1.7 +)。String 類的 intern() 方法還可在運行期間把字符串放到字符串常量池中。JVM 中除了字符串常量池,8種基本數據類型中除了兩種浮點類型float、double, 剩餘的6種基本數據類型的包裝類,都使用了緩衝池技術,可是 Byte<[-128,127] >、Short<[-128,127] >、Integer<[-128,127] >、Long<[-128,127] >、Character<[0,127] > 這5種整型的包裝類也只是在對應值在範圍內 時纔會使用緩衝池,超出此範圍仍然會去建立新的對象。其中:
package com.noob.learn.netty; public class Main { private String s1 = new String("a"); private final String s2 = "b"; private final String s3 = "c" + "d"; private String s4 = s1 + s2; private String s5 = s3 + s1; private String s6 = s3 + s2; private final String s10 = new String("z") + s2; private final String s11 = new String("m"); private String s12 = s11 + s3; public static void test(String args) { String s5 = new String("e"); final String s6 = "f"; final String s7 = "g" + "h"; String s8 = s6 + s7; String s9 = s6 + "i"; String s10 = new String("j") + "i"; } private void get() { String s15 = new String("q"); String s16 = new String("p"); } public void get2() { String s15 = new String("x"); String s16 = new String("z"); } public static void main(String[] args) { } }
經過: javap -verbose Main.class 反編譯
在本文以下事例中,該反編譯文件顯示出的內容含: 類文件的描述信息<類名、更新時間、版本>、類文件常量池<符號引用、字面量等>、方法信息(除private修飾外)。
方法:每個方法包含四個區域,
- 簽名和訪問標籤
- 字節碼
- LineNumberTable:爲調試器提供源碼中的每一行對應的字節碼信息。例子中,Java 源碼裏的第 26 行與 get 函數字節碼序號 0 相關,第 27 行與字節碼序號 3 相關。
- LocalVariableTable:列出了全部棧幀中的局部變量。方法是非靜態時,有局部變量 this。
結論:
方法體內和成員變量同時適用如下規則。 類文件常量池在類加載入運行常量池,建立對象入字符串常量池
一、文本字符串在編譯期間入類文件常量池: 「a」, "b","z", "m","e","f","j","i";
二、常量字符串的「+」操做,編譯階段直接會合成爲一個字符串再入類文件常量池,單獨的字符不入 : "cd",gh」;
三、 對於final的常量字符串(注意:必定要是文本字符串,new的都不能),編譯直接進行了常量替換。入 類文件常量池: s6 -> 'cdb' ; s8 -> 'fgh' ; s9 -> 'fi';
四、'ldc'、'new' 指令出如今方法體內部