百度百科:
https://baike.baidu.com/item/%E5%B8%B8%E9%87%8F%E6%B1%A0/3855836?fr=aladdin
先暫時(shí)認(rèn)為:JDK1.8 的所有類型常量池都存儲(chǔ)在元空間(方法區(qū)) (后續(xù)會(huì)有解釋)
目錄
1:什么叫常量池
2:常量結(jié)構(gòu)實(shí)例說明
3:字符串常量特殊講解
4:拘留字的實(shí)際使用 與 常量池的作用
5:有什么類型常量池
6:JVM在JDK 1.6 1.7 1.8到達(dá)經(jīng)歷了什么 (不糾結(jié))
7:為什么永久代要替換成元空間
8:驗(yàn)證移除是否已經(jīng)永久代:
1:什么叫常量池
常量池在java用于保存在編譯期已確定的麻捻,已編譯的class文件中的一份數(shù)據(jù)埃唯。
它包括了關(guān)于類,方法,接口等中的常量,也包括字符串常量,如String s = "java"這種申明方式;
當(dāng)然也可擴(kuò)充,執(zhí)行器產(chǎn)生的常量也會(huì)放入常量池筋蓖,故認(rèn)為常量池是JVM的一塊特殊的內(nèi)存空間卸耘。
Java是一種動(dòng)態(tài)鏈接的語言,常量池的作用非常重要粘咖,常量池中
1:字面值:包含代碼中所定義的各種基本類型(如int蚣抗、long等等)和對(duì)象型(如String及數(shù)組)的常量值
2:符號(hào)引用 ,以文本形式出現(xiàn)的比如:
類和接口的全限定名瓮下;
字段的名稱和描述符翰铡;
方法的名稱和描述符。
所以讽坏,與Java語言中的所謂“常量”不同锭魔,class文件中的“常量”內(nèi)容很豐富,這些常量集中在class中的一個(gè)區(qū)域存放路呜,一個(gè)緊接著一個(gè)迷捧,這里就稱為“常量池”。
2:常量結(jié)構(gòu)實(shí)例說明
在Java程序中胀葱,有很多的東西是永恒的漠秋,不會(huì)在運(yùn)行過程中變化。
字面值 與 符號(hào)應(yīng)用 舉例(不需要糾結(jié)是哪一個(gè)):
比如一個(gè)類的名字:ClassTest
比如一個(gè)類字段的名字/所屬類型:String name ="HeSuiJIn"
比如一個(gè)常量抵屿,int age =18
比如一個(gè)類方法的名字/返回類型/參數(shù)名與所屬類型:setName (String name
public class ClassTest {
private String name ="HeSuiJIn";
private final int age =18 ;
public void setName (String name ){...}
}
而這些在JVM解釋執(zhí)行程序的時(shí)候是非常重要的庆锦。
那么編譯器將源程序編譯成class文件后,會(huì)用一部分字節(jié)分類存儲(chǔ)這些代碼轧葛。而這些字節(jié)我們就稱為常量池搂抒。
事實(shí)上,只有JVM加載class后朝群,在方法區(qū)中為它們開辟了空間才更像一個(gè)“池”
3:字符串常量特殊講解
在Java源代碼中的每一個(gè)字面值字符串燕耿,都會(huì)在編譯成class文件階段,形成標(biāo)志號(hào)為8(CONSTANT_String_info)的常量表 姜胖。
當(dāng)JVM加載 class文件的時(shí)候誉帅,會(huì)為對(duì)應(yīng)的常量池建立一個(gè)內(nèi)存數(shù)據(jù)結(jié)構(gòu),并存放在方法區(qū)中右莱。
同時(shí)JVM會(huì)自動(dòng)為CONSTANT_String_info常量表中的字符串常量的字面值 在堆中創(chuàng)建新的String對(duì)象(intern字符串對(duì)象 蚜锨,又叫拘留字符串對(duì)象)。
然后把CONSTANT_String_info常量表的入口地址轉(zhuǎn)變成這個(gè)堆中String對(duì)象的直接地址(常量池解析)慢蜓。
4:拘留字的實(shí)際使用 與 常量池的作用
拘留字符串對(duì)象
源代碼中所有相同字面值的字符串常量只可能建立唯一 一個(gè)拘留字符串對(duì)象亚再。
實(shí)際上JVM是通過一個(gè)記錄了拘留字符串引用的內(nèi)部數(shù)據(jù)結(jié)構(gòu)來維持這一特性的。
在Java程序中晨抡,可以調(diào)用String的intern()方法來使得一個(gè)常規(guī)字符串對(duì)象成為拘留字符串對(duì)象氛悬。
(1)String s=new String("Hello world");
事實(shí)上则剃,在加載完成之后,在運(yùn)行這段指令之前如捅,JVM就已經(jīng)為"Hello world"在堆中創(chuàng)建了一個(gè)拘留字符串
值得注意的是:如果源程序中還有一個(gè)"Hello world"字符串常量棍现,那么他們都對(duì)應(yīng)了同一個(gè)堆中的拘留字符串。
1:局部變量s實(shí)際上存儲(chǔ)的是new出來的堆對(duì)象地址镜遣,
2:然后用這個(gè)拘留字符串的值來初始化堆中用new指令創(chuàng)建出來的新的String對(duì)象己肮,
(2)String s="Hello world";:
這跟(1)中創(chuàng)建指令有很大的不同,此時(shí)局部變量s存儲(chǔ)的是早已創(chuàng)建好的拘留字符串的堆地址悲关。
java常量池技術(shù) java中的常量池技術(shù)谎僻,是為了方便快捷地創(chuàng)建某些對(duì)象而出現(xiàn)的,
當(dāng)需要一個(gè)對(duì)象時(shí)寓辱,就可以從池中取一個(gè)出來(如果池中沒有則創(chuàng)建一個(gè))艘绍,
則在需要重復(fù)創(chuàng)建相等變量時(shí)節(jié)省了很多時(shí)間。
常量池其實(shí)也就是一個(gè)內(nèi)存空間讶舰,常量池存在于方法區(qū)中
5:有什么類型常量池
Java中的常量池鞍盗,實(shí)際上分為兩種形態(tài):
靜態(tài)常量池(類常量池)(類文件常量池)(class constant pool)
運(yùn)行時(shí)常量池(runtime constant pool)
Java語言并不要求常量一定只能在編譯期產(chǎn)生:
1:運(yùn)行期間也可能產(chǎn)生新的常量,這些常量被放在運(yùn)行時(shí)常量池中跳昼“慵祝
2:類加載后,同時(shí)靜態(tài)常量池中的數(shù)據(jù)也會(huì)在運(yùn)行時(shí)常量池中存放鹅颊。
這里所說的運(yùn)行期間也可能產(chǎn)生新的常量包括:
1:基本類型的包裝類
2:String(也可以通過String.intern()方法可以強(qiáng)制將String放入運(yùn)行時(shí)常量池中)
字符串常量池(String pool)(這里先了解)(不糾結(jié)) 下節(jié)會(huì)詳細(xì)說明
又稱為全局常量池 存放字符串的指針
由于隨著JDK版本的變遷 存儲(chǔ)常量池的地方不斷的改變 導(dǎo)致各種概念魚龍混雜
根據(jù)網(wǎng)上的各種說常量池所存儲(chǔ)的位置可能為 永久代 方法區(qū) 元空間 堆
所以需要先弄清楚這些的概念
6:JVM在JDK 1.6 1.7 1.8到底經(jīng)歷了什么 (不糾結(jié))
這里僅針對(duì)JDK 1.8 做詳細(xì)講解
千萬不要糾結(jié):既然你不是使用 JDK1.6 JDK1.7
那么現(xiàn)在不要把所有概念都混在一起敷存,這樣更難理解。
注意:
我們只關(guān)注 常量池 永久代 方法區(qū) 元空間 堆 這幾個(gè)關(guān)鍵詞
JDK1.6
永久代又稱為方法區(qū)
1:存放Class加載相關(guān)信息
2:靜態(tài)常量池:存放 符號(hào)引用 與 各種字面量
3:運(yùn)行時(shí)常量池
4:字符串常量池
JDK1.7
永久代又稱為方法區(qū)
存放Class加載相關(guān)信息
其他信息逐步移除 同時(shí)被轉(zhuǎn)移到堆中
1:靜態(tài)常量池:存放 符號(hào)引用 與 各種字面量
2:運(yùn)行時(shí)常量池
3:字符串常量池
JDK1.8
永久代被完全移除
元空間出現(xiàn) 同時(shí)元空間又稱為方法區(qū)
元空間
存放Class加載相關(guān)信息
從JVM的內(nèi)存結(jié)構(gòu)來看:
JDK1.6到JDK1.8的過程
實(shí)際上就是 永久代的功能逐步被削弱 最終被完成移除的過程
7:為什么永久代要替換成元空間
關(guān)于為什么移除永久代堪伍?
字符串存在永久代中锚烦,容易出現(xiàn)性能問題和內(nèi)存溢出。
類及方法的信息等比較難確定其大小帝雇,因此對(duì)于永久代的大小指定比較困難涮俄,
太小容易出現(xiàn)永久代溢出,太大則容易導(dǎo)致老年代溢出尸闸。
永久代會(huì)為 GC 帶來不必要的復(fù)雜度彻亲,并且回收效率偏低。
元空間的本質(zhì)和永久代類似吮廉,都是對(duì)JVM規(guī)范中方法區(qū)的實(shí)現(xiàn)苞尝。
不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機(jī)中,而是使用本地內(nèi)存宦芦。
因此宙址,默認(rèn)情況下,元空間的大小僅受本地內(nèi)存限制调卑,但可以通過以下參數(shù)來指定元空間的大新丈啊:
-XX:MetaspaceSize大咱,初始空間大小
-XX:MaxMetaspaceSize 最大空間
也因此有時(shí)候我們把元空間 也叫做 方法區(qū)
8:驗(yàn)證移除是否已經(jīng)永久代
我們可以通過一段程序來比較 JDK 1.6 與 JDK 1.7及 JDK 1.8 的區(qū)別,以字符串常量為例:
public class StringOomMock {
static String base = "string";
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i=0;i< Integer.MAX_VALUE;i++){
String str = base + base;
base = str;
list.add(str.intern());
}
}
}
從上述結(jié)果可以看出注益,
JDK 1.6下徽级,會(huì)出現(xiàn)“PermGen Space”的內(nèi)存溢出,
而在 JDK 1.7和 JDK 1.8 中聊浅,會(huì)出現(xiàn)堆內(nèi)存溢出,
并且 JDK 1.8中 PermSize 和 MaxPermGen 已經(jīng)無效现使。
因此低匙,可以大致驗(yàn)證 JDK 1.7 和 1.8 將字符串常量由永久代轉(zhuǎn)移到堆中