字符串常量池和運行時常量池是在堆還是在方法區?


我們知道在JDK1.8中取消了永久代,區而代之使用了元空間來實現方法區。話雖如此,但是關於字符串常量池和運行時常量池的模棱兩可的說法一直都是爭論不休的。

1)方法區包含哪些內容?

方法區包含哪些內容,摘錄自《java虛擬機規范-第8版》:
https://uploader.shimo.im/f/vKNgpXh1S1Zbfg9Y.png
方法區包含:

  • 運行時常量池
  • 自動和方法數據
  • 構造函數和普通方法的字節碼內容
  • 一些特殊方法

這里雖然沒有說明“字符串常量池”,但是它也是方法區的一部分。

2)運行時常量池存在什么地方?

下面是《深入理解Java虛擬機》一段摘錄:
https://uploader.shimo.im/f/juhqOqEiuB2zy5AA.png
能夠看到

  • 運行時常量池是在方法區中

  • 對於運行時常量池,《Java虛擬機規范》並沒有做任何細節的要求,不同提供商實現的虛擬機可以按照自己的需要來實現這個內存區域

3)取消永久代后,方法區的實現?

https://uploader.shimo.im/f/IEpnvrNBUecsaSAr.png
取消永久代后,使用元空間來實現方法區。
在JDK1.8中,把JDK 7中永久代還剩余的內容(主要是類型信息)全部移到元空間中。注意這里的剩余內容:說明原來移除從永久代移出的字符串常量池,靜態常量,在更換了方法區實現后,並沒有順勢進入到元空間,那么它們到哪里去了呢?

4)字符串常量池和運行時常量池究竟去了哪里?

在JDK1.8中,使用元空間代替永久代來實現方法區,但是方法區並沒有改變,所謂"Your father will always be your father",變動的只是方法區中內容的物理存放位置。正如上面所說,類型信息(元數據信息)等其他信息被移動到了元空間中;但是運行時常量池和字符串常量池被移動到了堆中。但是不論它們物理上如何存放,邏輯上還是屬於方法區的。

JDK1.8中字符串常量池和運行時常量池邏輯上屬於方法區,但是實際存放在堆內存中,因此既可以說兩者存放在堆中,也可以說兩則存在於方法區中,這就是造成誤解的地方。

關於佐證運行常量池和字符串常量池被移動到了堆中,可以參考這個博客:https://mp.weixin.qq.com/s?__biz=MzI4NDY5Mjc1Mg==&mid=2247485613&idx=1&sn=b2b1679033d24e965a3dd73dfab6dfaa&chksm=ebf6d0d2dc8159c4ae291d99e9c337b0cdb05578ecf7bb43671ad02cdbecfdf916a98ab18929&scene=27#wechat_redirect

其實,移除永久代的工作從JDK1.7就開始了。JDK1.7中,存儲在永久代的部分數據就已經轉移到了Java Heap或者是 Native Heap。但永久代仍存在於JDK1.7中,並沒完全移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了java heap;類的靜態變量(class statics)轉移到了java heap。我們可以通過一段程序來比較 JDK 1.6 與 JDK 1.7及 JDK 1.8 的區別,以字符串常量為例:

package com.paddx.test.memory;

import java.util.ArrayList;
import java.util.List;

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());
        }
    }
}

這段程序以2的指數級不斷的生成新的字符串,這樣可以比較快速的消耗內存。我們通過 JDK 1.6、JDK 1.7 和 JDK 1.8 分別運行:
https://uploader.shimo.im/f/6wQzZwxo7nfnJ83U.png
https://uploader.shimo.im/f/C9LvVvhgjnnyXps0.png
從上述結果可以看出,JDK 1.6下,會出現“PermGen Space”的內存溢出,而在 JDK 1.7和 JDK 1.8 中,會出現堆內存溢出,並且 JDK 1.8中 PermSize 和 MaxPermGen 已經無效。因此,可以大致驗證 JDK 1.7 和 1.8 將字符串常量由永久代轉移到堆中

4)元空間是什么?

元空間的本質和永久代類似,都是對JVM規范中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存限制,但可以通過以下參數來指定元空間的大小:

-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行類型卸載,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那么在不超過MaxMetaspaceSize時,適當提高該值。

-XX:MaxMetaspaceSize,最大空間,默認是沒有限制的。

除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:

-XX:MinMetaspaceFreeRatio,在GC之后,最小的Metaspace剩余空間容量的百分比,減少為分配空間所導致的垃圾收集.

-XX:MaxMetaspaceFreeRatio,在GC之后,最大的Metaspace剩余空間容量的百分比,減少為釋放空間所導致的垃圾收集

現在我們在 JDK 8下重新運行一下代碼段 4,不過這次不再指定 PermSize 和 MaxPermSize。而是指定 MetaSpaceSize 和 MaxMetaSpaceSize的大小。輸出結果如下:

https://uploader.shimo.im/f/rs9VTTFhMGUYDTQ3.png

5)關於為什么移除永久代?

  • 字符串存在永久代中,容易出現性能問題和內存溢出。
  • 類及方法的信息等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。
  • 永久代會為 GC 帶來不必要的復雜度,並且回收效率偏低。
  • Oracle 可能會將HotSpot 與 JRockit 合二為一。

5)補充

某種程度上說,該圖也是正確的:來源:https://www.zhihu.com/question/300075241/answer/519570351

參考鏈接:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM