說明
有關java併發可以看這裡:
yuedu.baidu.com/ebook/d09e3…
要學習Java或者任意一門技術,我覺得最好的是從官網的資料開始學習。官網所給出的資料總是最權威最知道來龍去脈的。而Java中間,垃圾回收與記憶體管理是Java中非常重要的一部分。《Hotspot記憶體管理白皮書》是瞭解Java垃圾收集器最權威的文件。相比於其他的一些所謂翻譯文章,本文的翻譯更加準確,通順和全面。在翻譯的過程中如果出現一些問題,如果出現問題或者表述不清楚的地方,可以直接在評論區評論。
1.簡介
JavaTM 2 Platform, Standard Edition (J2SETM) 其中一個強大特性便是自動記憶體管理,從而對開發者遮蔽了複雜的顯式記憶體管理。
這篇論文針對在J2SE 5.0版本釋出的JAVA Hotspot虛擬機器,對它做出了簡要概述。本論文描述了垃圾收集器如何執行記憶體管理,並且在對垃圾收集器的選擇和配置、可操作的記憶體區域的大小設定給出了建議。本文也提供了一些連結,其中一部分連結列舉了可以影響垃圾收集器行為的選項,另一部分列舉了更多詳細的文件。
文章的第二節為那些對於自動記憶體管理還是新事物的使用者而設立。它簡明的討論了與需要程式設計師顯示的回收記憶體相比,自動記憶體管理的有何益處。文章第三節緊接著描述分代垃圾回收的概念,方案選擇,效能指標。本節也介紹了一個叫作“分代”的通用記憶體區域劃分的方法,它是基於物件生存時間的記憶體區域劃分方法。對於大部分應用來說,這種基於代的劃分有效地減少了垃圾回收時的暫停時間和整體效能開銷。
本論文的剩餘部分提供了Hotspot VM的 細節資訊。第四部分描述了可用的四個垃圾回收器,包括在J2SE 5.0 update 6新出現的回收器,以及這些垃圾回收器都在使用的分代的記憶體劃分方法。對任何一個收集器,第4部分對適合於他們各自的垃圾回收演算法的型別和要求做出了概述。
第五部分描述了在J2SE 5.0中釋出的可以在根據執行中的應用程式的平臺和作業系統中來系統地自動選擇垃圾收集器(1),堆記憶體大小,HotSpot虛擬機器(客戶端模式或者伺服器模式),以及動態垃圾回收(2)以便自適應使用者自定義的行為的技術,這項技術被稱為人體工程學。
第六部分提供了選擇和配置垃圾回收器的推薦規範。它也提供了關於OutOfMemoryErrors時該如何做的一些建議。第七部分簡明的描述了評估垃圾收集效能時可以用到的一些工具。第八部分例舉了有關垃圾收集器相關選項和行為的一些通用命令列。最終,第九部分提供了本論文所覆蓋的觀點的詳細文件的連結。
2.顯式VS自動記憶體管理
記憶體管理是一個識別不再使用的被分配的物件,重新分配(釋放)被這些物件佔用的記憶體,並且使這一系列的分配可用的過程。在一些程式語言中,記憶體管理是程式設計師的責任。這個複雜的任務造成了許多通用的能夠造成異常和錯誤的程式執行行為以及崩潰。結果,大量的開發者把時間花費在除錯和力圖糾正這些錯誤上面。
一個在顯式記憶體管理程式設計上面經常出現的問題就是懸空引用。他是一個物件使用空間已經被重新分配但是其他物件仍在引用。如果一個物件試圖去引用這個物件最初的引用,但是這個物件已經被分配到新的物件,這個結果是未知的並且不是被期望的。
另外一個顯式記憶體管理的公共問題是記憶體空間洩露。洩露造成的原因是物件不再被引用但是並沒有被釋放。舉個例子,如果你打算釋放一個連結串列佔用的空間,但是你犯了一個只釋放了這個連結串列頭結點的錯誤,連結串列剩餘的元素不再引用但他們在程式中不可達,並且不能被重新使用或者重新覆蓋。如果足夠的洩露發生,他們能不斷的消耗記憶體直到所有記憶體被耗盡。
使用叫做垃圾回收器的程式來自動管理記憶體被認為是記憶體管理的一種通用替代方法,尤其是在現在大多數物件導向的語言中。自動記憶體管理增加了介面的抽象和程式碼的可靠性。
垃圾收集器避免了懸空引用的問題。因為仍然被引用的物件永遠不會被垃圾會收取回收所以肯定不會被釋放。垃圾收集器也解決了上面描述的記憶體空間洩漏的問題,因為它會自動釋放所以不再引用的記憶體。
3.垃圾收集器的理念
垃圾收集器有如下職責:
1.分配記憶體
2.確保任何被引用的物件仍然在記憶體中間
3.重新覆蓋那些在執行程式碼中引用不可達的記憶體。
仍然被引用的物件叫做存活的物件。不再引用的物件被認為是死亡的物件,術語上叫做垃圾。發現和釋放(或者被稱為再生)這些物件所佔用物件的過程叫做垃圾回收。
垃圾回收器解決了大量的但並不是所有的記憶體分配問題。比如你可以無限期的建立物件和引用它們直到沒有記憶體可以使用。垃圾收回收器自身所佔用的時間和資源也非常複雜。
垃圾回收器使用精確的演算法來組織記憶體的分配和再分配,並且對程式設計師遮蔽細節。記憶體空間通常從叫作“堆”的記憶體池中分配。
垃圾收集的時機取決於垃圾收集器。通常是垃圾佔滿了整個堆或者一部分的某個臨界值的時候開始回收垃圾。
比較令人滿意的記憶體分配請求包括了在堆中發現一塊大小確定的未被使用的記憶體,但這卻是一個複雜的任務。主要是問題是大多數動態記憶體分配演算法都要避免碎片化來保持物件的分配和釋放都很高效。
令人滿意的垃圾收集器特質
一個垃圾回收器必須安全和全面。也就是說,活著的物件不能被錯誤的釋放,並且垃圾物件在經歷超過數個較小數字的回收週期以後不能仍然無人認領。
高效的垃圾收集操作也是比較令人滿意的,它不會在應用執行期間造成長時間的停頓。在與計算機有關的系統中,需要在時間,空間,和回收頻率上尋求平衡。比如,如果堆比較小,收集會非常快但是卻容易被用滿,因此需要更頻繁的回收。相反的,一個比較大的堆填滿需要更長的時間,回收頻率也會變少,但是回收的時間會變長。
另外一個令人滿意指標的垃圾回收器地碎片化的限度。當一個垃圾物件的記憶體被釋放時,釋放的空間可能會出現一系列連續的小塊,但是這些連續的小塊任何一塊都不足以分配一個較大的物件。一個消除碎片化的方法叫做壓縮,就是下面一系列垃圾回收器所討論的設計思想。
可擴充套件性也很重要。記憶體的分配不應該成為可擴充套件性的瓶頸。回收也不應該成為瓶頸。
設計思想
在設計或者選擇垃圾回收演算法的時候有幾個思想是必須考慮的:
-
序列還是並行
在序列收集器中,同一個時間只有一件事發生。舉個例子,即使是多個CPU的系統,也只有一個CPU用來執行垃圾回收。當並行的垃圾回收器用的時候,垃圾回收任務被同時地分配在不同的CPU,這些同時地操作使得垃圾回收更快地完成,但是增加了複雜性和潛在碎片的風險。
-
併發 VS Stop-the-world
當“Stop-the-world”型別收集器執行的時候,整個應用程式會被完全掛起。相對的,一個或者多個垃圾回收器能夠併發地執行,也就是說與應用程式同時執行。通常,一個併發的垃圾收集器自己通常自身是併發的工作,但是偶爾也會造成造成一個短暫的“Stop-the-world”停頓。 “Stop-the-world”要比並發的收集器簡單,因為在整個收集期間,堆被凍結並且物件不再改變。由於某些應用程式的暫停會造成不良的影響,所以這可能是一個劣勢。相應的,並行的垃圾收集暫停的時間更短,但是垃圾收集器卻需要更多小心,因為垃圾收集正在操作的物件可能會同時被應用程式更新。對於併發收集器來說這些額外的開銷會影響效能並且需要更大的堆。
-
壓縮 VS 不壓縮 VS 拷貝
當一個垃圾收集器決定記憶體中的哪些物件存活哪些物件需要回收的時候,它可以壓縮記憶體,把活著的物件移動到一起然後完整的恢復剩餘的記憶體。壓縮之後,非常容易快速的釋放和回收記憶體。使用一個簡單的指標來跟蹤下一個可以分配物件的記憶體位置。與壓縮收集器相對應的是,不壓縮的收集器原地釋放被垃圾物件佔用的空間,也就是說,它不會採用與壓縮的垃圾回收器移動所有活著的物件的方式來建立記憶體區域。這樣做的優勢是垃圾回收會非常快,但是缺點就是會有潛在的碎片。通常來說,原地釋放的記憶體上重新分配物件也比在壓縮的堆上分配記憶體代價要更昂貴。不壓縮的垃圾回收器必須搜尋一個連續的足夠大的區域來容納新物件。第三個可以選擇的垃圾收集器就是吧或者的物件拷貝或者疏散到不同的記憶體區域。這樣的好處是源區域可以很快的被清空並且容易的連續分配。缺點是增加了拷貝需要的時間並且可能需要額外的空間。
效能指標
有諸多的指標來計算垃圾收集器的效能,包括:
- 吞吐量:未被垃圾收集的時間佔總時間的百分比,應該被認為在一個長時間內。
- 垃圾回收的天花板:吞吐量的倒數,也就是說,垃圾回收佔總時間的百分比。
- 暫停時間: 當應用程式暫停以執行垃圾回收的時間。
- 回收頻率:多久垃圾回收發生一次,相對與整個應用程式的執行時間。
- 記憶體需求:堆大小的測量,比如堆大小
- 實時性:在物件變成垃圾到記憶體變得可用的時間。
一個互動系統可能需要低暫停時間,然而對於一個非互動系統應用程式而言整個可執行的時間卻更加重要。一個實時系統在任何時間段下都需要更小的垃圾回收暫停上界和垃圾回收的的佔比。在個人電腦中或者嵌入式系統中,對小記憶體需求可能比較關心。
分代回收
當一個叫做“分代回收”的垃圾收集被運用的時候,記憶體被劃分成了不同的代,也就是劃分出了不同的池來持有不同年齡的物件。比如,最廣泛的使用的配置是分成兩個代:年輕代和老年代。
不同的演算法運用在不同的代來執行垃圾回收。每一個演算法基於各自代的特性來優化。在許多語言寫成的應用程式中,這樣的設計是基於弱年代假設(Weak Generational Hypothesis),包括Jav語言:
1.越早分配的物件越容易失效。
2.只有少數的老年代物件引用年輕代物件存在。
年輕代回收相對頻繁和快速,因為年輕代的空間通常很小並且包含了大量不再使用的物件。
經過幾次回收還存活的物件最終會被提升或者終生晉升到老年代。如圖1。老年代通常比年輕代大並且增長緩慢。所以,老年代的垃圾回收不頻繁,但是會佔用更多的時間。
年輕代垃圾回收演算法選擇通常會在速度上花費高昂的代價,因為年輕代的物件收集會非常頻繁,另一方面,管理老年代垃圾回收的演算法則更有效。因為老年代通常佔據堆的大部分,並且老年代的演算法必須在低迴收密度下工作的很好。
4.J2SE5.0中的Hotspot VM的垃圾收集器
J2SE 5.0 update 6釋出的Hotspot VM包含了四中垃圾收集器。所有的垃圾收集器都是分代的。本節描述了分代和回收器的型別,並討論了為什麼物件的分配會頻繁並且快速和高效,本文也提供了關於每個垃圾收集器的細節。
HotSpot中的分代
HotSpot虛擬機器被劃分成了三個代:年輕代,老年代和一個永久代。大部分物件最初被分配到了年輕代,老年代包含了幾次垃圾回收以後還存活的物件。此外一些大的物件一開始就被分配到老年代。永久帶持有JVM比較容易找到並管理的物件,比如描述類和方法的物件,也包括類和方法自身。
年輕代由叫做Eden區和兩個較小的survivor區組成。如圖2,大部分物件分配在Eden區。(注意,一些大的物件一開始就被分配到老年代)survivor區持有那些至少在一次垃圾收集中存活並且被認為在足夠老並且晉升到老年代之前仍然有機會死亡的物件。在任何給定的時間內,一個survivor區總是持有存活的物件,而另外一個survivor區則是空置的,直到下一次回收器開始工作。
垃圾收集的型別
當年輕代被佔滿的時候,年輕代的垃圾收集就會執行工作。(有時候也叫作次收集。次收集
垃圾收集按頻率可分為: 次收集(Minor Collection):頻繁發生在年輕代,收集快速消亡的物件;主收集(Major Collection):年輕代和年老代的全範圍收集,頻率很低)當老年代被佔滿或者永久帶被佔滿的時候,會發生整個gc(有時候也叫做主收集),也就是說,所有的代都會被收集。通常,年輕代首先被收集。因為年輕代的演算法通常是最高效的。接下來是使用“老年代垃圾收集演算法”的垃圾收集器開始在老年代和永久代工作。如果在收集的過程中發生了壓縮,每一個代內部都會各自壓縮。
如果年輕代優先被收集,老年代就會因為太滿以至於無法接受來自年輕代晉升而來的物件。在這種情況下,除了CMS收集器執行之外,年輕代所有的垃圾回收演算法將會被暫停。與之替代的是,老年代的演算法將會被運用到整個堆。(CMS的老年代演算法是一個特例,因為它無法回收年輕代)。
快速分配
在下面關於垃圾收集器你將看到,在很多情況下都是從一塊連續的很大的記憶體上面分配物件。利用一個叫做“空閒指標”的技術,使得在記憶體塊上分配物件很高效。指標始終保持最後一個分配的物件的記憶體位置。當有新的物件需要分配記憶體時,只需要檢查剩餘的空間是否存放新的物件,如果能夠,更新指標的位置並且初始化物件。
對於多執行緒的應用程式來說,物件的分配需要保證執行緒安全。如果使用全域性鎖來保證這些,那麼物件的分配就會成為瓶頸且效能下降。因此你,hotspot虛擬機器採用了一個叫做“執行緒分配緩衝(TLAB)”的技術。通過給每個執行緒設定自己的快取來分配物件來提高多執行緒的記憶體分配吞吐量。這樣每個TLAB只會有一個執行緒分配物件,這樣不需要鎖,空閒指標會移動的很快。但當一個執行緒將自己的快取用光以後再分配新的,則必須要同步,但是這並不頻繁。HotSpot虛擬機器會採取一系列的技術來減少由於使用了執行緒本地分配快取而帶來記憶體浪費。比如,TLAB的分配器只會造成Edon區平均大約少於百分之一的損耗。使用Edon和空閒指標能夠做到每一個分配都高效,這隻需要大約10個本地指令。
序列回收器
在序列收集器中,所有的年輕代和老老年代都是序列的執行的(一次只用一個CPU),序列回收器會產生stop-the-world
,也就是說,整個應用在垃圾回收期間會被掛起。
使用序列回收器的年輕代
圖3插圖說明了序列回收器年輕代操作的情形。edon區活著的物件被拷貝到了Survivor區,有一種情況是其中一個區太大以至於不能完全拷貝到Survivor區,這些物件直接拷貝到了老年代。from區存活的相對來說比較年輕的物件也會被拷貝到另外一個Survivor區,相對比較老的物件則直接拷貝到老年代。注意:如果to區已經滿了,edon區或者from區將不再拷貝,不管有多少物件存活。任何在edon區或者from區,經過拷貝過並且還存活的物件,在定義上是不存活。因此他們也不需要被檢查。(這些物件就是在下圖中標記為“x”物件,儘管事實上垃圾收集器並不會檢查或者標記這些物件。)
在年輕代的垃圾收集完成以後,Edon區和早先被佔用的Survivor區被清空,早先空的Survivor區持有了活著的物件。基於這一點,兩個Survivor區交換了角色。如圖4
使用序列收集器的老年代收集器
在序列收集器中,老年代和永久帶通過一種“標記-清除-壓縮”的演算法。在標記階段,收集器識別哪些物件是存活的。在清除階段,清除那些被識別為垃圾的物件。然後收集器則執行“滑動壓縮”。把所有活著的物件移動到老年代起始的地方(永久代類似)。這樣就在堆得末端留下一個相當大的連續的區塊。如圖5。壓縮允許在老年代或者永久代使用空閒指標的技術分配任何預分配的物件。
什麼時候使用序列回收器
序列回收器是大部分執行在客戶端型別機器的應用程式的選擇,這些應用程式不要求有低延遲。在如今的硬體條件下,序列收集器能夠很容易管理一個有64mb的記憶體,發生一次full gc相對最壞的暫停不會超過半秒的時間。
序列收集器的選擇:
在j2se5中,序列收集器自動被非server模式的機器所選擇。如第五節描述的那樣,在其他型別的機器上,需要使用-XX:+UseSerialGC
來顯示地指定。
#並行收集器
如今,許多java應用程式執行在大記憶體和多核CPU上面。並行收集器,也就是熟知的高吞吐收集器。它利用了多核CPU的優勢,而不是隻在一個CPU上執行垃圾收集的工作。
年輕代的並行垃圾收集器
年輕代的垃圾收集器是序列收集器的並行版本。它仍然是一個具有stop the world
暫停和物件拷貝的收集器。由於使用了多核CPU,在執行的時候確是併發的,這樣減少了垃圾收集器的消耗,因此增加了應用程式的吞吐量。圖6闡明瞭序列收集器與並行收集器在年輕代中的不同。
老年代使用並行收集器
老年代的並行收集器工作模式與序列收集器的標記-清除-壓縮演算法相同。
什麼時候使用併發收集器
應用程式程式能夠從執行在多個CPU的併發收集器受益,並且不會有長時間暫停的限制。但是在某些情況下,比如批處理任務,賬單系統,工資系統,科學計算等系統中,這些應用系統的老年代垃圾收集回收雖然不頻繁,但是耗費時間可能會很長。這個時候你就可以考慮使用並行的壓縮回收器(下面將要描述的)而不是併發收集器了,因為早先的併發收集器適用於所有的代,並不僅僅是年輕代才能使用。
併發收集器的選擇
J2SE 5.0發行版中,並行回收器是server型別的機器的預設的收集器。(文章第五節定義)。在其他型別的機器上,使用XX:+UseParallelGC
命令來顯示地指定使用並行收集器。
併發壓縮收集器
J2SE 5.0 update 6 引進了併發壓縮收集器。不同於併發收集器的地方在於它為老年代使用了新的算。注意: 併發壓縮收集器終會替代並行垃圾回收器
。
年輕代使用併發壓縮收集器
年輕代的併發壓縮收集器與年輕代使用併發收集器的演算法一樣。
老年代使用併發壓縮收集器
在併發壓縮收集器中,老年代和永久帶在stop-the-world的過程中,大部分併發模式都是滑動壓縮。收集器分為三步來回收垃圾:第一步,每一個代都被邏輯地分為幾個固定的區域。在標記階段,初始的直接可到達的物件被劃分到不同的垃圾收集執行緒。然後所有活著的物件被併發的標記。當一個物件被確定是活著的,關於這個區域的資訊和這個物件的位置的資料將會被更新。彙總階段:彙總階段的操作基於區,而不是基於物件。由於上一次垃圾回收時的壓縮操作, 一般來說代空間的左邊區域存活物件的密度會較高. 這種密度高的區域中, 可以回收的空間不多, 所以壓縮他們的可用空間的代價太高. 所以彙總階段做的第一件事情就是測試區域密度。 從最左邊的那個區域開始, 一直到找到一個點, 壓縮這個點的右邊的區域是代價是值得的. 這個點左邊的區域叫做密度字首 (Dense Prefix), 這些區域不會有新的物件寫入。 這個點右邊的區域將被壓縮, 並清除所有死亡物件。彙總階段計算並儲存了每個壓縮區域的存活物件的第一個位元組的地址。注意: 彙總階段目前的是實現是序列執行, 因為相對來說,標記和壓縮階段的並行執行更重要。
在壓縮階段,垃圾回收使用從彙總階段收集而來的資料來確定哪些區域,並且執行緒可以獨立的拷貝資料到這些區域。這樣就造就了在一端是密度很高的物件區塊,另外一端是一個連續大的可用區塊。
什麼時候使用併發壓縮演算法
與並行回收演算法類似,並行壓縮演算法對在超過一個CPU允許的應用程式有益。除此之外,老年代的並行操作減少了暫停時間並且併發壓縮的收集器更加適合對暫停時間有嚴格限制的應用。併發壓縮演算法可能不適用於那些執行在大型共享機的機器上面的應用,他們不允許單個應用程式長時間的壟斷CPU。在這些機器上,就得考慮減少垃圾回收的執行緒(通過–XX:ParallelGCThreads=n)或者選擇不同的回收器。
並行壓縮收集器選擇
如果你想指定使用並行壓縮收集器,使用-XX:+UseParallelOldGC 命令列選項。
併發的標記-清除收集器
在很多應用程式中,端到端的吞吐量並與快速響應的時間相比並不重要。年輕代通常情況下並不會造成長時間的停頓。然後老年代雖然並不頻繁,但是會造成長時間的停頓,尤其是涉及到很大的堆。為了解決這個問題,HotSpot VM引入了一種叫做併發的標記清除收集器,也叫作低延遲收集器。
年輕代使用併發標記清除收集器
CMS與年輕代中的並行收集器一樣。
老年代使用CMS收集器
大多數的CMS收集器是在並行地執行。
CMS收集器開始於一個叫做初始化標記的暫停。初始化標記是標記那些在應用程式中直接可達的活著的物件。然後,在併發標記階段,收集器標記那些間接可達的活著的物件。由於在標記階段發生的時候,應用程式不停地在執行並且更新引用,不是所有活著的物件都能在標記階段被確定地標記。為了處理這個問題,應用程式將會暫停很短的時間,叫做remark。就是重新標記對在標記階段中任何發生了改變的物件。因為remark階段的暫停要比初始化標記的時間要長,所以會使用多執行緒執行以便提高效率。
在remark標記的結束以後,所有活著的物件都確保被標記了。所以隨後的併發清除階段就是清除所有的垃圾。圖7顯示了使用序列的標記清除回收器與並行的標記清除回收器有何不同。
由於一些諸如在remark階段重新檢索物件等額外的任務,垃圾回收器增加了一些額外的工作,同時也增加了一些簡接消耗。所以對於大多數收集器來說,在正確性與暫停時間之間都會存在一種平衡和取捨。
CMS收集器是唯一的不壓縮的收集器。也就是說垃圾物件在釋放以後,並不會把活著的物件移動到代的一端,參見圖8
這樣做雖然節省了時間,但由於自由空間不是連續的,收集器不再使用一個簡單的指標來指示下一個可以使用的位置,使得下一個物件可以分配。相反,它現在需要空閒空間的列表。也就是說它現在需要一些列表把未分配的記憶體區域連線在一起,每一次需要分配物件,就必須在適當的列表(基於所需的記憶體)必須尋找一個區域足夠容納物件,分配到老年代。比用一個簡單的bump-the-pointer技術更加昂貴。這也對年輕代的收集產生了額外的開銷,因為大多數老年代的物件都是從年輕代晉升而來的。
CMS垃圾回收器的另一個缺點是需要比其他垃圾回收器更大的堆。考慮到應用程式在標記階段可以繼續分配記憶體,從而老年代可能會持續增長。此外,儘管收集器保證在標記階段期間識別所有活的物件,但一些物件可能在這個階段成為垃圾,他們將不會再被標記,直到下一個老年代回收開始。這樣的物件被稱為漂浮垃圾。
最後,由於缺乏壓縮,碎片可能會產生。為了處理碎片,CMS回收器會指定一個常用的物件大小,估算出未來的需求,並且會分割或者合併空閒的記憶體塊去符合需求。
與其他的回收器不同,CMS回收器不會等到老年代的空間變滿的時候才開始回收工作。它試圖在足夠早的時間就開始回收工作。否則,CMS回收器會比使用標記-清除-壓縮演算法的並行和序列垃圾回收器造成更多的暫停。為了避免它,CMS回收器會在達到某個閾值的時候時候啟動回收操作,這個閾值基於前面垃圾回收的次數和垃圾回收的耗時來統計。當老年代被佔用到超過了一個稱之為初始化佔用值的時候也會開始執行垃圾回收。初始化佔用可以通過–XX:CMSInitiatingOccupancyFraction=n 這個命令列選項來設定,n是老年代物件大小的百分比,預設值是68。
總之,並行收集器相比,CMS收集器降低了老年代的所帶來的停頓,但令人感到戲劇性的是,它反而提升了年輕代的暫停時間,還減少了部分吞吐量,並且需要額外的堆空間。
增量模式
CMS收集器可以使用一種模式,這個模式可以讓併發階段逐步完成,而不是一次整個完成。這個模式打算減少由長期併發階段造成的影響,它採用了定期地暫停當前的併發階段使得當前的回收工作被掛起,讓出處理器來處理應用程式。它的工作是這樣的,在年輕代回收操作工作時,回收器將老年代劃分成不同的塊單獨回收。當應用程式要求回收器暫停時間要較短而又執行在小數量處理器的機器中時,這是很有用的。更多關於使用這個模型的資訊,參見第九節“java5.0 虛擬機器上的垃圾回收器調優”。
何時使用CMS收集器
如果你的應用程式需要一個較短停頓的垃圾回收器,並且可以讓垃圾回收器在應用程式執行過程中分享處理器資源,那麼就合適使用CMS回收器(由於它的併發性,CMS回收器在回收週期使用的cpu週期與應用程式無關)。一般情況下,應用程式存在一個相對大的集合來儲存長期存活的資料(一個足夠大的老年代),並且執行它的機器擁有兩個或更多的處理器,常常趨向於使用這個回收器。比如web伺服器,CMS垃圾回收器通常被那些需要短時暫停的需求的應用程式所採用。它通常也適合那些在單個處理器上擁有合適老年代大的互動式應用程式。
選擇CMS收集器
如果你想要使用的CMS收集器,您必須通過指定的命令列選項xx:+ UseConcMarkSweepGC
顯式地選擇它。如果你想要在增量模式下執行,使用-xx:+ CMSIncrementalMode
選項。
5.人體工程學-自動選擇和行為調優
在J2SE 5.0的發行版中,垃圾收集器預設的值、堆大小、以及HotSpot VM的模式(客戶端模式或者伺服器模式)都是根據應用程式執行的平臺或者作業系統自動選擇的。這些自動化選項能夠更好的匹配不同型別的應用程式的需要。當然比先前的發行版本中相比需要的命令列選項更少了。
值得一提的是,一種新的回收器新的動態適配方法已經加入到並行垃圾收集器中。使用這種方法時,使用者能指定期望的行為,並且垃圾回收器能動態的調整堆區域的大小以便試圖與使用者請求的行為取得一致。這種綜合考慮了平臺依賴的預設選項和使用使用者期望的垃圾收集器的組合模式就叫做人體工程學。整個人體工程學的目標是使用最少的命令列來達到最好的JVM效能。
收集器自動選項的堆大小和虛擬機器
一個伺服器型別(Server模式)的機器定義如下:
- 2個或者兩個以上的物理處理器
-
2g或者2g以上的實體記憶體
這種伺服器型別(Server模式)的定義適用於所有的平臺。除了執行在32位Windows作業系統下。如果機器不是在伺服器型別下的機器,預設的JVM,垃圾收集器和和堆大小如下:
-
客戶端模式的JVM
- 序列收集器
- 初始化堆大小4M
- 堆最大64M
在server模式的機器,JVM通常是Server模式,除非你想顯示的使用client命令列來請求JVM使用client模式。在伺服器型別的機器上執行JVM都是server模式的JVM,預設的垃圾收集器是並行收集器。否則是序列收集器。在server 型別的機器上執行任何一中JVM模式指定使用並行收集器,預設的初始化引數和最大堆得引數:
- 初始化堆大小為實體記憶體的64分之一,上限是1G。注意最小的堆是32M。因為server型別的機器至少有2G記憶體,64分之一就是32M。
- 最大的堆是實體記憶體的4分之一。上限是1G。
否則,將按照非server型別的機器配置(4M初始堆記憶體和64M最大記憶體)預設的值通常會被命令列設定的值覆蓋。有關選項在本文第八節說明。
基於行為的併發回收器的優化
在J2SE 5.0版本中,新增了一種新的優化方法來並行垃圾收集器,它可以按照應用程式的期望的預期行為對垃圾進行收集。使用命令列選項指定所需的行為目標的最大暫停時間和應用程式吞吐量。
最大暫停時間
最大暫停的時間通過如下命令列設定:-XX:MaxGCPauseMillis=n
這解釋為提示並行收集器暫停時間被期望為n毫秒或者更少。並行收集器將調整堆大小和其他垃圾收集相關引數,試圖保持垃圾收集停頓時間短於n毫秒。這些調整可能導致垃圾收集器來減少應用程式的整體吞吐量,並在某些情況下所需的暫停時間的目標不可能實現。
最大暫停時間的目標是分別適用於每一代。通常情況下,如果不滿足我們的目標,代會被劃分的更小以期望試圖達到這個目標。沒有預設設定最大暫停時間的目標。
吞吐量目標
吞吐量目標是以一種測量垃圾收集花費的時間和和垃圾收集花費之外的時間(叫做應用程式花費時間)。這個目標可以使用如下命令列指定:-XX:GCTimeRatio=n
垃圾回收的時間和應用時間的比率:
1/(1+ n)
例如- xx:GCTimeRatio = 19設定了總時間的的5%的目標是垃圾收集的時間。預設的目標是1%(n = 99)。垃圾收集的時間是所有代的總時間。如果吞吐量的目標沒有被滿足,一代又一代的大小增加,以增加時間集合之間的應用程式可以執行。大的一代需要更多的時間來填滿。
效能消耗的目標
如果吞吐量和最大暫停時間都被滿足,垃圾收集器會減少堆得大小直到其中一個目標不被滿足(一般會是吞吐量)的臨界值。未被達成的目標將會被放到一邊。
目標優先順序
並行垃圾收集器首先試圖滿足最大暫停時間的目標。只有在最大暫停時間滿足以後收集器解決吞吐量目標。同樣,效能消耗目標是前兩個目標已經達到之後才會思考的問題。
6.建議
在前一節中描述的人體工程學中垃圾收集器,虛擬機器以及堆大小的選擇,對大部分應用程式是合理的。因此,最初的建議選擇和配置一個垃圾收集器是什麼都不做!即沒有指定使用一個特定的垃圾收集器,等等。讓系統根據應用程式正在執行的平臺和作業系統自動選擇。然後測試應用程式。如果它的效能是可以接受的,有夠高的吞吐量和低暫停時間,這樣就夠了。你不需要排查垃圾收集器或修改選項。
另一方面,如果您的應用程式似乎出現了與垃圾收集有關的效能問題,那麼你能做的最簡單的事情就是集合應用程式和平臺特點,認為預設的垃圾收集器是否合適。如果不是,顯式地選擇你認為合適的收集器,然後看看是否成為可接受的效能。
你 可以使用如第7節中描述的那些工具測量和分析效能。根據結果,您可以考慮修改選項,比如那些控制堆大小和垃圾收集行為。一些最常用的具體選項部分8所示。請注意:最好的效能調優方法是測量第一,然後調整。(Measure using tests relevant for how your code will actually be used)。測試用例取決於你的程式碼如何執行。同時,謹防過度優化,因為應用程式的資料集,硬體,所以甚至垃圾收集器的實現!可能隨時間改變。
本節提供的資訊選擇一個垃圾收集器和指定堆大小。然後,提供建議,優化並行垃圾收集器,並給出一些建議關於如何處理outofmemoryerror錯誤。
何時選擇不同的垃圾回收器
在第四節中,介紹個每個回收器的適用情形。章節五描述了不同的平臺上序列回收器和並行回收器的預設選擇。如果你的應用程式或者環境特性與預設的回收器的適用情況不同,請使用以下的其中一個命令列選項來明確使用一個垃圾回收器:
–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC
堆大小調整
第五節告訴預設的初始和最大堆大小。這些預設大小可能對大多數情況來說會工作的很好,但是如果你的應用出現了效能問題(見第7節)或一個OutOfMemoryError(本節稍後討論)並且已經通過分析確定是代或者整個堆大小的問題,您可以通過8節中指定的命令列選項修改大小。例如,預設最大堆大小64 mb的non-server-class機器通常是太小了,所以你可以通過- xmx選項指定一個更大的堆。除非你有長時間的暫停問題,儘量給予儘可能多的記憶體堆。吞吐量可用記憶體的數量成正比。有足夠的可用記憶體影響垃圾收集的效能是最重要的因素。在已經決定你可以給整個堆的全部記憶體有多少後,然後您可以考慮調整各個代大小不同。第二個影響垃圾收集的效能最有影響力的因素是堆的年輕代比例。除非你找到老年代增長過快或這暫停時間過長的問題所在,儘量給予年輕代更多的多記憶體。然而,如果你使用的是序列收集器,不要給予年輕代超過總堆大小的一半的記憶體。
當你使用一個併發的垃圾收集器,最好指定所期望的行為,而不是準確的堆大小值。讓收集器自動和動態修改堆大小以實現這一行為,下面將講到這一點。
並行收集器的調優策略
如果(無論是自動或顯式地)選擇的是並行收集器或並行壓縮收集器,然後為您的應用程式指定一個吞吐量目標(見第五節)。不要貿然的更改一個堆的最大值,除非你知道你需要一個大於預設最大堆大小的堆值。堆將增長或縮小規模來支援選擇的吞吐量目標。堆得大小在初始化值和變化值之間的振盪是可以預期的。如果堆增長到最大值,在大多數情況下,這意味著在已經最大值的情況下還是不能達到吞吐量目標。此時為應用程式設定的最大的堆大小值來接近平臺上所有的實體記憶體但不包含引交換分割槽,再一次執行這個應用程式。如果吞吐量目標仍然沒有被實現,那麼應用程式的所期望的目標執行時間對於該平臺上的可用的記憶體來說太高了。如果吞吐量目標可以被實現,但是暫停的時間太長了,會優先選擇一個最大的暫停時間。選擇一個最大的暫停時間意味著你的吞吐量目標將不會被實現,因此應當選擇一個應用程式可以妥協的值。堆大小會在垃圾回收器試圖滿足相互競爭的目標之間進行搖擺,即使應用程式達到一個穩定的狀態。這之間的壓力來自達到吞吐量目標(這將會需要一個更大的堆)與最大暫停時間和最小記憶體需求(這將會需要一個更小的堆)。
如何處理記憶體溢位異常
許多開發人員必須解決的一個常見的問題就是應用程式因為java.lang.OutOfMemoryError而終止。這個錯誤在沒有足夠的空間來分配一個物件時丟擲。垃圾收集,不能分配任何進一步可用的的可用空間以適應一個新物件而且堆又不能進一步擴大。OutOfMemoryError錯誤並不一定意味著記憶體洩漏。這個問題可能是配置問題,例如如果指定的堆大小(如果未指定或預設大小)對應用程式來說是不夠的。
OutOfMemoryError錯誤診斷的第一步是檢查錯誤訊息。在丟擲異常後,“java.lang.OutOfMemoryError”會提供進一步的資訊。這裡有一些常見的例子,額外的資訊可能是什麼,它可能意味著什麼,以及如何應對:
Java堆空間
這表明了一個物件無法在堆上分配。這個問題可能只是一個配置問題。你可以捕獲到這個錯誤,例如,如果使用-Xmx命令列選項來指明最大的堆大小(或者是預設值)無法滿足應用程式的需求。他也可能表明一個不再被使用的物件不被垃圾回收器回收,因為應用程式無意地保持了這些物件的引用。HAT工具(見章節七)可以用來觀察所有的可達物件和明確哪一個引用來保持哪一個物件的存活。另一個潛在的錯誤來源有可能是在應用程式中過多地使用了 finalizers 以致於執行緒呼叫 finalizers 無法跟得上新增finalizers到佇列的速度。Jconsole管理工具可以用來監控在銷燬期間的物件的數目。
永久帶空間
這表明永久代已經滿了。如前所述,堆得這部分割槽域是JVM的堆儲存用來儲存後設資料的。如果一個應用程式載入大量的類,永久代就可能需要增加。可以通過指定的命令列選項-xx:MaxPermSize = n
,其中n指定大小。
陣列大小超過了VM的限制
這意味著應用程式試圖分配比堆大小的陣列。例如,如果一個應用程式試圖分配512 mb的陣列但最大堆大小為256。 mb,那麼就會丟擲這個錯誤。在大多數情況下,問題很可能是堆大小是太小或應用程式中,陣列大小被計算錯誤,使得陣列大小很大。
第7節中描述的一些工具可以用來診斷OutOfMemoryError問題。一些最有用的工具,這個任務是堆分析工具(HAT),jconsole管理工具,jmap工具組織選項。
分析回收器效能的工具
有一系列的監控和診斷可以利用起來,來計算垃圾回收器的效能。本段提供了這些工具的簡要說明。要獲得更多的資訊,請訪問第九部分關於工具和故障排除的章節。
–XX:+PrintGCDetails 命令列
一個最簡單獲取垃圾收集器初始化資訊就是使用–XX:+PrintGCDetails命令。對於任何收集器來說,輸出的結果包括了垃圾回收之前和之後各個代存活的物件的大小,每一個代可以用的空間以及垃圾回收所花費的時間。
–XX:+PrintGCTimeStamps 命令列
輸出垃圾回收器開始的時間戳。如果使用了PrintGCDetails的話還會輸出一些額外的資訊。時間戳能幫助你理清垃圾回收日誌和其他日誌事件的關係。
jmap
jmap是一個命令列工具,包含在Solaris作業系統環境和linux(不包含windows)的Java 開發工具集(JDK)中。它會列印出執行中的JVM或者核心檔案的記憶體相關統計資料。在不使用任何命令列選項的情況下,它會列印出所有被載入的共享物件,與Solaris的pmap工具相似的輸出。對於更多的明確資訊,可以使用 -heap,-histo,或 -permstat 選項。
-heap 選項用來獲取一些資訊包含了垃圾回收器的名字,具體的演算法細節(例如 並行垃圾回收器使用的執行緒數量),堆的配置資訊,和堆的簡單使用情況。
-histo 選項可以用來獲取堆上的類的直方圖,對於每一個類,它會列印出堆中該類的例項數量,這些物件所佔用的單位為位元組的記憶體總數,和全合格的類名。當你試圖理解堆的佔用情況的時候,這個直方圖會很有用。
配置永久代的大小對於應用程式來說是很重要的,特別是動態載入一個很大資料量的類的時候(比如 java Server Pages(JSP)和web containers(web 容器))。如果一個應用程式載入了過多的類,那麼將會丟擲OutOfMemoryError。Jmap的 -permastat 選項可以用來獲取永久代上的物件統計資訊。
jstat
jstat工具使用HotSpot JVM提供的內建儀器效能和執行應用程式的資源消耗資訊。可以使用該工具在診斷效能問題,特別是與堆大小和垃圾收集有關的問題。它的一些關於垃圾收集許多選項可以列印統計行為和能力和使用不同的一代。
HPROF: Heap Profiler
HPROF是一個簡單的效能分析代理,附帶在JDK 5.0中。它是一個動態連結庫介面JVM使用Java虛擬機器(JVM TI)工具介面。它寫出概要資訊到一個檔案或一個套接字ASCII和二進位制格式。這些資訊可以進一步由前端工具分析器處理。
HPROF能夠提供CPU使用率,堆分配統計和監控爭用配置檔案。此外,它可以輸出完整的堆轉儲和報告的所有Java虛擬機器監視器和執行緒。HPROF是有用的在分析效能、鎖爭用記憶體洩漏等問題。參見9 HPROF連結文件。
HAT: Heap Analysis Tool
堆分析工具(HAT)用來幫助除錯無意地物件保留。這個術語用來描述一個不再被需要的物件由於被一個存活的物件所引用而保持存活。HAT提供了一個方便的手段來瀏覽物件在堆中的快照。這個工具允許一定數量的查詢,包含“向我提供所有從根集合到物件的引用路徑”,參見章節九的HAT文件連結。
8.與垃圾收集有關的關鍵選項
許多命令列選項可以用來選擇一個垃圾收集器,指定堆或代大小,修改垃圾收集行為,獲得垃圾收集統計資料。本節顯示了一些最常用的選項。更完整的列表和詳細資訊可用的各種選項,參見9。注意:你指定的數字可以以“m”或“m”mb,為千位元組“k”或“k”,和“g”或“g”g。
垃圾收集器選項
選項 | 垃圾收集器選擇 |
---|---|
–XX:+UseSerialGC | 序列 |
–XX:+UseParallelGC | 並行 |
–XX:+UseParallelOldGC | 並行壓縮 |
–XX:+UseConcMarkSweepGC | 並行標記清除(CMS) |
垃圾回收器的統計分析選項
選項 | 描述 |
---|---|
–XX:+PrintGC | 輸出每次垃圾收集的基礎資訊 |
–XX:+PrintGCDetails | 輸出每次垃圾回收的更多額外的資訊 |
–XX:+PrintGCTimeStamps | 輸出每次垃圾回收事件開始的時間戳。使用–XX:+PrintGC 或者 –XX:+PrintGCDetails 來輸出更多資訊 |
堆和代大小
選項 | 預設 | 描述 |
---|---|---|
–Xmsn | 第五節 | 初始堆化大小,byte計數 |
–Xmxn | 參見第五節 | 最大的堆大小,byte技術 |
–XX:MinHeapFreeRatio=minimum and –XX:MaxHeapFreeRatio=maximum | 40最小,70最大 | 空閒空間佔總空間比例的目標。這會運用於任何一代上。例如,如果最小值是30,並且空閒空間佔該代上的空間比例小於30%,那麼這個代空間就會擴充套件直到滿足30%的空閒空間。近似的,如果最大值是60並且自由空間的比例已經超過60%,代空間的大小就會收縮直到自由空間只佔到60%。 |
–XX:NewSize=n | 平臺依賴 | 預設年輕代的大小,byte計算 |
–XX:NewRatio=n | Client JVM 為2,8為Server JVM | 年輕代和老年代之間的比例。例如,如果n是3,那麼Eden區的比例是1:3,合併後的大小和倖存者空間總大小的佔年輕代和老年代四分之一。 |
–XX:SurvivorRatio=n | 32 | survivor區與Edon區的比例。例如,如果n是7,每個倖存者空間是年輕一代的九分之一(八分之一,因為有兩個survivor空間)。 |
–XX:MaxPermSize=n | 平臺相關 | 永久帶最大空間 |
併發或者併發壓縮收集器的選項
選項 | 預設值 | 描述 |
---|---|---|
–XX:ParallelGCThreads=n | CPU的數量 | 垃圾收集器執行緒的數量 |
–XX:MaxGCPauseMillis=n | 沒有預設值 | 表明期望暫停時間少於n毫秒 |
–XX:GCTimeRatio=n | 99 | 垃圾收集花在總時間的比例(1/(n+1)) |
CMS收集器選項
選項 | 預設值 | 描述 |
---|---|---|
–XX:+CMSIncrementalMode | 禁止 | 支援併發模式階段逐步完成,併發階段定期停止回收以便應用程式繼續執行 |
–XX:+CMSIncrementalPacing | 沒有預設值 | 表明期望暫停時間少於n毫秒 |
–XX:ParallelGCThreads=n | CPU的數量 | 年輕代的垃圾收集器和執行緒數和老年代併發收集器併發部分的執行緒數。 |
9.更多資訊
Hotspot垃圾回收和效能調優
- Java HotSpot中的垃圾收集
- Java 5.0 虛擬機器的調優
(java.sun.com/docs/hotspo…)
人體工程學
Server型別的偵探
垃圾收集器的人體工程學
(java.sun.com/j2se/1.5.0/…)
Java 5.0 虛擬機器的人體工程學
(java.sun.com/docs/hotspo…)
選項
- Java Hotspot VM 選項
- Solaris 和 Linux 選項
- windows 選項
(java.sun.com/j2se/1.5.0/…)
工具和問題診斷
Java2平臺,5.0版本-問題定位和診斷指南
(java.sun.com/j2se/1.5/pd… )
HPROF: A Heap/CPU Profiling Tool in J2SE 5.0
(java.sun.com/developer/t…)
HAT:堆分析工具
(hat.dev.java.net/)
析構
如何處理JAVA析構中的記憶體留用問題:
雜項
• J2SE 5.0 發行版註解
• JavaTM 虛擬機器
• Sun JavaTM 實時 System (Java RTS)
• 關於垃圾收集的通用書籍:
Garbage Collection: Algorithms for Automatic Dynamic Memory Management by Richard Jones and Rafael Lins, John Wiley & Sons, 1996.
額外閱讀-垃圾回收演算法:
任何一種垃圾收集演算法一般要做2件基本的事情:(1)發現無用資訊物件;(2)回收被無用物件佔用的記憶體空間,使該空間可被程式再次使用。
HotSpot使用的垃圾回收演算法為分代回收演算法(Generational Collector)
大多數垃圾回收演算法使用了根集(rootset)這個概念(有了這個概念應該就能解決面試中被問到的互為引用的孤獨島的情況);所謂根集就是正在執行的java程式可以訪問的引用變數的集合(包括區域性變數、引數、類變數),程式可以使用引用變數訪問物件的屬性和呼叫物件的方法。垃圾收集首選需要確定從根開始哪些是可達的和哪些是不可達的,從根集可達的物件都是活動物件,它們不能作為垃圾被回收,這也包括從根集間接可達的物件。而根集通過任意路徑不可達的物件符合垃圾收集的條件,應該被回收。下面介紹幾個常用的演算法。
1、引用計數法(referencecountingcollector)
引用計數法是唯一沒有使用根集的垃圾回收得法,該演算法使用引用計數器來區分存活物件和不再使用的物件。一般來說,堆中的每個物件對應一個引用計數器。當每一次建立一個物件並賦給一個變數時,引用計數器置為1。當物件被賦給任意變數時,引用計數器每次加1。當物件出了作用域後(該物件丟棄不再使用),引用計數器減1,一旦引用計數器為0,物件就滿足了垃圾收集的條件。
基於引用計數器的垃圾收集器執行較快,不會長時間中斷程式執行,適宜地必須實時執行的程式。但引用計數器增加了程式執行的開銷,因為每次物件賦給新的變數,計數器加1,而每次現有物件出了作用域生,計數器減1。
2、tracing演算法(tracingcollector)
tracing演算法是為了解決引用計數法的問題而提出,它使用了根集的概念。基於tracing演算法的垃圾收集器從根集開始掃描,識別出哪些物件可達,哪些物件不可達,並用某種方式標記可達物件,例如對每個可達物件設定一個或多個位。在掃描識別過程中,基於tracing演算法的垃圾收集也稱為標記和清除(mark-and-sweep)垃圾收集器.
3、compacting演算法(compactingcollector)
為了解決堆碎片問題,基於tracing的垃圾回收吸收了compacting演算法的思想,在清除的過程中,演算法將所有的物件移到堆的一端,堆的另一端就變成了一個相鄰的空閒記憶體區,收集器會對它移動的所有物件的所有引用進行更新,使得這些引用在新的位置能識別原來的物件。在基於compacting演算法的收集器的實現中,一般增加控制程式碼和控制程式碼表。
4、coping演算法(copingcollector)
該演算法的提出是為了克服控制程式碼的開銷和解決堆碎片的垃圾回收。它開始時把堆分成一個物件面和多個空閒面,程式從物件面為物件分配空間,當物件滿了,基於coping演算法的垃圾收集就從根集中掃描活動物件,並將每個活動物件複製到空閒面(使得活動物件所佔的記憶體之間沒有空閒洞),這樣空閒面變成了物件面,原來的物件面變成了空閒面,程式會在新的物件面中分配記憶體。
一種典型的基於coping演算法的垃圾回收是stop-and-copy演算法,它將堆分成物件面和空閒區域面,在物件面與空閒區域面的切換過程中,程式暫停執行。
5、generation演算法(generationalcollector) 分代回收演算法(Generational Collector)
stop-and-copy垃圾收集器的一個缺陷是收集器必須複製所有的活動物件,這增加了程式等待時間,這是coping演算法低效的原因。在程式設計中有這樣的規律:多數物件存在的時間比較短,少數的存在時間比較長。因此,generation演算法將堆分成兩個或多個,每個子堆作為物件的一代(generation)。由於多數物件存在的時間比較短,隨著程式丟棄不使用的物件,垃圾收集器將從最年輕的子堆中收集這些物件。在分代式的垃圾收集器執行後,上次執行存活下來的物件移到下一最高代的子堆中,由於老一代的子堆不會經常被回收,因而節省了時間。
6、adaptive演算法(adaptivecollector)
在特定的情況下,一些垃圾收集演算法會優於其它演算法。基於adaptive演算法的垃圾收集器就是監控當前堆的使用情況,並將選擇適當演算法的垃圾收集器。
java 8 中的垃圾收集
java8從Hotspot JVM中刪除了永久代,所以我們不再需要為永久代設定大小,也就是不用設定PermSize和MaxPermSize。
在java8之前方法區是作為堆的永久代來實現的,啟動JVM時我們需要設定永久代的大小,垃圾回收器也要回收這部分割槽域,而且會丟擲記憶體溢位異常。借鑑於JRockit虛擬機器,java8之後 Hotspot 虛擬機器從堆中徹底刪除了永久代。
—把方法區中的String和靜態變數移到了堆中。
—把其他的東西(比如類結構)放到了本地記憶體中,JVM會直接負責這部分的記憶體回收。
總之,我們不再需要設定PermSize和MaxPermSize;方法區的記憶體溢位將不再出現,除非本地記憶體耗光。