方法區和常量池的一些思考
文章目錄
方法區?
方法區(Method area)是可供各個執行緒共享的執行時記憶體區域,它儲存了每一個類的結構資訊,相當於把程式中共性的部分抽離出來。
例如:執行時常量池(Runtime constant pool)。欄位和方法資料、建構函式和普通方法的位元組碼內容,還包括一些在類、例項、介面初始化時用到的特殊方法。
方法區在虛擬機器啟動的時候建立,方法區的容量可以是固定的,也可以隨著程式的執行實現動態擴充套件,並在不需要過多空間的時候自動回收。方法區在實際記憶體中可以是不連續的。
JDK7 之前
在 JDK 之前,方法區位於永久代(PermGen),永久代和堆相互隔離,永久代的大小在 JVM 中可以設定一個固定值,不可變。方法區是一種jvm的規範,而永久代是一種實現,並且只有 HotSpot 才有 “PermGen space”,而對於其他型別的虛擬機器,如 JRockit(Oracle)、J9(IBM) 並沒有“PermGen space”。由於方法區主要儲存類的相關資訊,所以對於動態生成類的情況比較容易出現永久代的記憶體溢位。
永久代是 Hotspot 虛擬機器特有的概念,是方法區的一種實現,別的 JVM 都沒有這個東西。永久代與新生代和老年代相樣,前者並不是位於堆中,後後兩者是位於堆中的。
在這個時候永久代就是方法區的實現,這個時期預設方法區即永久代。在這個時候永久代裡面存放了很多東西,例如,符號引用(Symbols)、字串常量池(interned strings)、類的靜態變數(class static variables)、執行時常量池以及其它資訊。由於這個時候方法區是由永久代實現的,那麼方法區出現異常後會丟擲這樣的資訊:java.lang.OutOfMemoryError: PermGen, 這裡的 PermGen 就是永久代的意思,從這個就可以看出此時的方法區的實現是永久代。
JDK7
在這個時候官方以及發現用永久代實現方法區容易導致記憶體洩漏的問題了,同時為了後面將 Hotspot 虛擬機器與其他虛擬機器整合,已經有將方法區改用其他的方式類實現了,但是並沒有動工!此時只是將原本位於永久代中的字串常量、類的靜態變數池轉移到了 Java Heap 中,還有符號引用轉移到了 Native Memory
在 Jdk6 以及以前的版本中,字串的常量池是放在堆的 Perm 區的,Perm 區是一個類靜態的區域,主要儲存一些載入類的資訊,常量池,方法片段等內容,預設大小隻有 4m,一旦常量池中大量使用 intern 是會直接產生 java.lang.OutOfMemoryError:PermGen space 錯誤的。
JDK8 及以後
取消永久代,方法區由元空間(Metaspace)實現,元空間仍然與堆不相連,但與堆共享實體記憶體,邏輯上可認為在堆中。
元空間的本質和永久代類似,都是對 JVM 規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體。,理論上取決於 32 位/64 位系統可虛擬的記憶體大小。可見也不是無限制的,需要配置引數。
在之前的永久代實現中,如果要修改方法區的大小配置,需要使用 PermSize ,MaxPermSize 引數,而改為元空間之後,就需要使用 MetaspaceSize,MaxMetaspaceSize。
為什麼移除永久代?
字串存在永久代中,容易出現效能問題和記憶體溢位。
永久代大小不容易確定,PermSize 指定太小容易造成永久代 OOM
永久代會為 GC 帶來不必要的複雜度,並且回收效率偏低。
Oracle 可能會將 HotSpot 與 JRockit 合二為一。
常量池、執行時常量池、字串常量池
在JDK7之前,字串常量是存在永久帶Perm 區的,JDK7開始在將常量池遷移到堆中,這個變化也導致了String的新特性,接下來,我們按照jdk7開始後的版本進行介紹。
常量池分類
- 執行時常量池
- Class檔案常量池
- 字串常量池
Class檔案常量池
.java經過編譯後生成的.class檔案,是Class檔案的資源倉庫。
常量池中主要存放倆大常量:字面量(文字字串,final常量)和符號引用(類和介面的全域性定名,欄位的名稱和描述,方法的名稱和描述),如下圖:
執行時常量池(Constant Pool)
執行時常量池是方法區的一部分。在Class常量池中,用於存放編譯期間生成的字面量和符號量,在類載入完之後,存入執行時常量池中。而執行時常量池期間也有可能加入新的常量(如:String.intern方法)
String常量池,
String常量池,JVM為了減少字串物件的重複建立,在堆區開一段記憶體存放字串。