簡體   English   中英

Java 的 String 常量池在哪里,堆還是堆棧?

[英]Where does Java's String constant pool live, the heap or the stack?

我知道常量池和 JVM 用來處理字符串文字的字符串常量池的概念。 但我不知道 JVM 使用哪種類型的內存來存儲 String 常量文字。 堆棧還是堆? 由於它是一個與任何實例都沒有關聯的文字,我認為它將被存儲在堆棧中。 但是,如果它沒有被任何實例引用,則必須通過 GC 運行來收集文字(如果我錯了,請糾正我),那么如果它存儲在堆棧中,如何處理呢?

從技術上講,答案是否定的。 根據 Java 虛擬機規范,存儲字符串文字的區域位於運行時常量池中 運行時常量池內存區域是基於每個類或每個接口分配的,因此它根本不綁定到任何對象實例。 運行時常量池是方法區的一個子集,它“存儲每個類的結構,如運行時常量池、字段和方法數據,以及方法和構造函數的代碼,包括在類和實例初始化和接口中使用的特殊方法類型初始化”。 VM 規范說,雖然方法區在邏輯上是堆的一部分,但它並沒有規定在方法區中分配的內存受垃圾收集或與分配給堆的正常數據結構相關聯的其他行為的影響。

正如這個答案所解釋的那樣,字符串池的確切位置沒有指定,並且可能因一個 JVM 實現而異。

有趣的是,在 Java 7 之前,該池位於熱點 JVM 上堆的永久代空間中,但自 Java 7 以來它已移至堆的主要部分

地區: 熱點
概要:在 JDK 7 中,interned 字符串不再分配在 Java 堆的永久代中,而是分配在 Java 堆的主要部分(稱為年輕代和年老代)中,以及其他創建的對象應用程序。 此更改將導致更多數據駐留在主 Java 堆中,而永久代中的數據更少,因此可能需要調整堆大小。 由於這種變化,大多數應用程序只會看到相對較小的堆使用差異,但加載許多類或大量使用 String.intern() 方法的較大應用程序將看到更顯着的差異。 RFE:6962931

而在 Java 8 Hotspot 中,永久代已被完全刪除。

字符串文字不存儲在堆棧中。 絕不。 實際上,堆棧中沒有存儲任何對象。

字符串(或更准確地,代表他們的字符串對象 在歷史存儲在堆稱為“PermGen的”堆。 (Permgen 是永久代的縮寫。)

在正常情況下,字符串文字和永久生成堆中的許多其他內容是“永久”可訪問的,並且不會被垃圾收集。 (例如,始終可以從使用它們的代碼對象訪問字符串文字。)但是,您可以配置 JVM 以嘗試查找和收集不再需要的動態加載的類,這可能會導致字符串文字被垃圾收集.

澄清 #1 - 我並不是說 Permgen 不會被 GC 處理。 它確實如此,通常是在 JVM 決定運行 Full GC 時。 我的觀點是,只要使用它們的代碼是可訪問的,字符串文字將是可訪問的,並且只要代碼的類加載器可訪問,代碼也將是可訪問的,對於默認的類加載器,這意味着“永遠”。

澄清 #2 - 事實上,Java 7 及更高版本使用常規堆來保存字符串池。 因此,表示字符串文字和實習字符串的 String 對象實際上位於常規堆中。 (有關詳細信息,請參閱@assylias 的回答。)


但我仍然試圖找出字符串文字的存儲和用new創建的字符串之間的細線。

沒有“細線”。 這真的很簡單:

  • 表示/對應於字符串文字的String對象保存在字符串池中。
  • String::intern調用創建的String對象保存在字符串池中。
  • 所有其他String對象都不保存在字符串池中。

然后是字符串池“存儲”在哪里的單獨問題。 在 Java 7 之前,它是 permgen 堆。 從 Java 7 開始,它是主堆。

字符串池化

字符串池化(有時也稱為字符串規范化)是用單個共享 String 對象替換具有相同值但不同身份的多個 String 對象的過程。 您可以通過保留自己的地圖(根據您的要求可能使用軟引用或弱引用)並使用地圖值作為規范化值來實現此目標。 或者您可以使用 JDK 提供給您的 String.intern() 方法。

在 Java 6 使用 String.intern() 的時候,許多標准都禁止使用 String.intern(),因為如果池化失控,很有可能會出現 OutOfMemoryException。 字符串池的 Oracle Java 7 實現發生了很大變化。 您可以在http://bugs.sun.com/view_bug.do?bug_id=6962931http://bugs.sun.com/view_bug.do?bug_id=6962930 中查找詳細信息。

Java 6 中的 String.intern()

在那些美好的過去,所有的內部字符串都存儲在 PermGen 中——堆的固定大小部分主要用於存儲加載的類和字符串池。 除了顯式插入的字符串,永久代字符串池還包含程序中早期使用的所有文字字符串(這里使用的重要詞是——如果一個類或方法從未被加載/調用,則不會加載其中定義的任何常量)。

Java 6 中這種字符串池的最大問題是它的位置——永久代。 PermGen 有固定大小,不能在運行時擴展。 您可以使用 -XX:MaxPermSize=96m 選項設置它。 據我所知,默認的 PermGen 大小在 32M 到 96M 之間變化,具體取決於平台。 你可以增加它的大小,但它的大小仍然是固定的。 這種限制需要非常小心地使用 String.intern——你最好不要使用這種方法來實習任何不受控制的用戶輸入。 這就是為什么在 Java 6 時代的字符串池主要在手動管理的映射中實現。

Java 7 中的 String.intern()

Oracle 工程師對 Java 7 中的字符串池邏輯進行了極其重要的更改——字符串池被重定位到堆中。 這意味着您不再受單獨的固定大小內存區域的限制。 所有字符串現在都位於堆中,就像大多數其他普通對象一樣,這允許您在調整應用程序時僅管理堆大小。 從技術上講,僅此一項就足以成為重新考慮在 Java 7 程序中使用 String.intern() 的理由。 但還有其他原因。

字符串池值被垃圾收集

是的,如果您的程序根目錄中沒有對它們的引用,則 JVM 字符串池中的所有字符串都有資格進行垃圾回收。 它適用於所有討論過的 Java 版本。 這意味着如果你的實習字符串超出范圍並且沒有其他引用它 - 它將從 JVM 字符串池中垃圾收集。

有資格進行垃圾收集並駐留在堆中,JVM 字符串池似乎是您所有字符串的正確位置,不是嗎? 理論上這是真的——未使用的字符串將從池中垃圾收集,使用的字符串將允許您節省內存,以防您從輸入中獲得相等的字符串。 似乎是一個完美的內存節省策略? 差不多。 在做出任何決定之前,您必須知道字符串池是如何實現的。

來源。

正如其他答案所解釋的,Java 中的內存分為兩部分

1.堆棧:每個線程創建一個堆棧,它存儲堆棧幀,該堆棧幀再次存儲局部變量,如果變量是引用類型,則該變量指代實際對象在堆中的內存位置。

2. 堆:各種對象只會在堆中創建。

堆內存又分為3部分

1. Young Generation:存儲生命周期較短的對象,Young Generation本身可以分為Eden SpaceSurvivor Space兩大類。

2. 老年代:存儲在多次垃圾回收周期中幸存下來並且仍然被引用的對象。

3. 永久代:存儲有關程序的元數據,例如運行時常量池。

字符串常量池屬於Heap內存的永久生成區。

我們可以使用javap -verbose class_name在字節碼中查看代碼的運行時常量池,它將向我們顯示方法引用(#Methodref)、類對象(#Class)、字符串文字(#String)

運行時常量池

您可以在我的文章JVM 如何內部處理方法重載和覆蓋中閱讀更多相關信息。

對於這里已經包含的很棒的答案,我想添加一些我認為缺少的東西 - 一個插圖。

因為您已經 JVM 將分配給 Java 程序的內存分為兩部分。 一個是堆棧,另一個是 堆棧用於執行目的,堆用於存儲目的。 在該堆內存中,JVM 分配了一些專門用於字符串文字的內存。 這部分堆內存稱為字符串常量池

例如,如果您初始化以下對象:

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

字符串字面量s1s2將進入字符串常量池,對象 obj1、obj2、obj3 將進入堆。 所有這些都將從堆棧中引用。

另外,請注意“abc”將出現在堆和字符串常量池中。 為什么String s1 = "abc"String obj1 = new String("abc")會以這種方式創建? 這是因為String obj1 = new String("abc")顯式地創建了一個新的和引用不同的 String 對象實例,並且String s1 = "abc"可以重用字符串常量池中的一個實例(如果有的話)。 更詳細的解釋: https : //stackoverflow.com/a/3298542/2811258

在此處輸入圖片說明

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM