java 內存管理 —— 《Hotspot內存管理白皮書》

說明

  要學習Java或者任意一門技術,我以爲最好的是從官網的資料開始學習。官網所給出的資料老是最權威最知道前因後果的。而Java中間,垃圾回收與內存管理是Java中很是重要的一部分。《Hotspot內存管理白皮書》是瞭解Java垃圾收集器最權威的文檔。相比於其餘的一些所謂翻譯文章,本文的翻譯更加準確,通順和全面。在翻譯的過程當中若是出現一些問題,若是出現問題或者表述不清楚的地方,能夠直接在評論區評論。html

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自動內存管理

  內存管理是一個識別再也不使用的被分配的對象,從新分配(釋放)被這些對象佔用的內存,而且使這一系列的分配可用的過程。在一些程序語言中,內存管理是程序員的責任。這個複雜的任務形成了許多通用的可以形成異常和錯誤的程序執行行爲以及崩潰。結果,大量的開發者把時間花費在調試和力圖糾正這些錯誤上面。

  一個在顯式內存管理編程上面常常出現的問題就是懸空引用。他是一個對象使用空間已經被從新分配可是其餘對象仍在引用。若是一個對象試圖去引用這個對象最初的引用,可是這個對象已經被分配到新的對象,這個結果是未知的而且不是被指望的。

  另一個顯式內存管理的公共問題是內存空間泄露。泄露形成的緣由是對象再也不被引用可是並無被釋放。舉個例子,若是你打算釋放一個鏈表佔用的空間,可是你犯了一個只釋放了這個鏈表頭結點的錯誤,鏈表剩餘的元素再也不引用但他們在程序中不可達,而且不能被從新使用或者從新覆蓋。若是足夠的泄露發生,他們能不斷的消耗內存直到全部內存被耗盡。

  使用叫作垃圾回收器的程序來自動管理內存被認爲是內存管理的一種通用替代方法,尤爲是在如今大多數面向對象的語言中。自動內存管理增長了接口的抽象和代碼的可靠性。

  垃圾收集器避免了懸空引用的問題。由於仍然被引用的對象永遠不會被垃圾會收取回收因此確定不會被釋放。垃圾收集器也解決了上面描述的內存空間泄漏的問題,由於它會自動釋放因此再也不引用的內存。java

3.垃圾收集器的理念

  垃圾收集器有以下職責:

  1.分配內存

  2.確保任何被引用的對象仍然在內存中間

  3.從新覆蓋那些在執行代碼中引用不可達的內存。

  仍然被引用的對象叫作存活的對象。再也不引用的對象被認爲是死亡的對象,術語上叫作垃圾。發現和釋放(或者被稱爲再生)這些對象所佔用對象的過程叫作垃圾回收。

  垃圾回收器解決了大量的但並非全部的內存分配問題。好比你能夠無限期的建立對象和引用它們直到沒有內存可使用。垃圾收回收器自身所佔用的時間和資源也很是複雜。

  垃圾回收器使用精確的算法來組織內存的分配和再分配,而且對程序員屏蔽細節。內存空間一般從叫做「堆」的內存池中分配。

  垃圾收集的時機取決於垃圾收集器。一般是垃圾佔滿了整個堆或者一部分的某個臨界值的時候開始回收垃圾。

  比較使人滿意的內存分配請求包括了在堆中發現一塊大小肯定的未被使用的內存,但這倒是一個複雜的任務。主要是問題是大多數動態內存分配算法都要避免碎片化來保持對象的分配和釋放都很高效。linux

使人滿意的垃圾收集器特質

  一個垃圾回收器必須安全和全面。也就是說,活着的對象不能被錯誤的釋放,而且垃圾對象在經歷超過數個較小數字的回收週期之後不能仍然無人認領。
高效的垃圾收集操做也是比較使人滿意的,它不會在應用運行期間形成長時間的停頓。在與計算機有關的系統中,須要在時間,空間,和回收頻率上尋求平衡。好比,若是堆比較小,收集會很是快可是卻容易被用滿,所以須要更頻繁的回收。相反的,一個比較大的堆填滿須要更長的時間,回收頻率也會變少,可是回收的時間會變長。

  另一個使人滿意指標的垃圾回收器地碎片化的限度。當一個垃圾對象的內存被釋放時,釋放的空間可能會出現一系列連續的小塊,可是這些連續的小塊任何一塊都不足以分配一個較大的對象。一個消除碎片化的方法叫作壓縮,就是下面一系列垃圾回收器所討論的設計思想。

  可擴展性也很重要。內存的分配不該該成爲可擴展性的瓶頸。回收也不該該成爲瓶頸。程序員

設計思想

  在設計或者選擇垃圾回收算法的時候有幾個思想是必須考慮的:web

  • 串行仍是並行

      在串行收集器中,同一個時間只有一件事發生。舉個例子,即便是多個CPU的系統,也只有一個CPU用來執行垃圾回收。當並行的垃圾回收器用的時候,垃圾回收任務被同時地分配在不一樣的CPU,這些同時地操做使得垃圾回收更快地完成,可是增長了複雜性和潛在碎片的風險。面試

  • 併發 VS Stop-the-world

      當「Stop-the-world」類型收集器執行的時候,整個應用程序會被徹底掛起。相對的,一個或者多個垃圾回收器可以併發地執行,也就是說與應用程序同時執行。一般,一個併發的垃圾收集器本身一般自身是併發的工做,可是偶爾也會形成形成一個短暫的「Stop-the-world」停頓。 「Stop-the-world」要比並發的收集器簡單,由於在整個收集期間,堆被凍結而且對象再也不改變。因爲某些應用程序的暫停會形成不良的影響,因此這多是一個劣勢。相應的,並行的垃圾收集暫停的時間更短,可是垃圾收集器卻須要更多當心,由於垃圾收集正在操做的對象可能會同時被應用程序更新。對於併發收集器來講這些額外的開銷會影響性能而且須要更大的堆。算法

  • 壓縮 VS 不壓縮 VS 拷貝

      當一個垃圾收集器決定內存中的哪些對象存活哪些對象須要回收的時候,它能夠壓縮內存,把活着的對象移動到一塊兒而後完整的恢復剩餘的內存。壓縮以後,很是容易快速的釋放和回收內存。使用一個簡單的指針來跟蹤下一個能夠分配對象的內存位置。與壓縮收集器相對應的是,不壓縮的收集器原地釋放被垃圾對象佔用的空間,也就是說,它不會採用與壓縮的垃圾回收器移動全部活着的對象的方式來建立內存區域。這樣作的優點是垃圾回收會很是快,可是缺點就是會有潛在的碎片。一般來講,原地釋放的內存上從新分配對象也比在壓縮的堆上分配內存代價要更昂貴。不壓縮的垃圾回收器必須搜索一個連續的足夠大的區域來容納新對象。第三個能夠選擇的垃圾收集器就是吧或者的對象拷貝或者疏散到不一樣的內存區域。這樣的好處是源區域能夠很快的被清空而且容易的連續分配。缺點是增長了拷貝須要的時間而且可能須要額外的空間。編程

性能指標

  有諸多的指標來計算垃圾收集器的性能,包括:windows

  • 吞吐量:未被垃圾收集的時間佔總時間的百分比,應該被認爲在一個長時間內。
  • 垃圾回收的天花板:吞吐量的倒數,也就是說,垃圾回收佔總時間的百分比。
  • 暫停時間: 當應用程序暫停以執行垃圾回收的時間。
  • 回收頻率:多久垃圾回收發生一次,相對與整個應用程序的執行時間。
  • 內存需求:堆大小的測量,好比堆大小
  • 實時性:在對象變成垃圾到內存變得可用的時間。
      一個交互系統可能須要低暫停時間,然而對於一個非交互系統應用程序而言整個可執行的時間卻更加劇要。一個實時系統在任什麼時候間段下都須要更小的垃圾回收暫停上界和垃圾回收的的佔比。在我的電腦中或者嵌入式系統中,對小內存需求可能比較關心。

分代回收

  當一個叫作「分代回收」的垃圾收集被運用的時候,內存被劃分紅了不一樣的代,也就是劃分出了不一樣的池來持有不一樣年齡的對象。好比,最普遍的使用的配置是分紅兩個代:年輕代和老年代。
  不一樣的算法運用在不一樣的代來執行垃圾回收。每個算法基於各自代的特性來優化。在許多語言寫成的應用程序中,這樣的設計是基於弱年代假設(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中的垃圾收集

((http://www.devx.com/Java/Article/21977))

人體工程學

Server類型的偵探

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/server–class.html)

垃圾收集器的人體工程學

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/gc–ergonomics.html)
Java 5.0 虛擬機的人體工程學
(http://java.sun.com/docs/hotspot/gc5.0/ergo5.html)

選項

  • Java Hotspot VM 選項

(http://java.sun.com/docs/hotspot/VMOptions.html)

  • Solaris 和 Linux 選項

(http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/java.html)

工具和問題診斷

Java2平臺,5.0版本-問題定位和診斷指南
(http://java.sun.com/j2se/1.5/pdf/jdk50_ts_guide.pdf )

HPROF: A Heap/CPU Profiling Tool in J2SE 5.0
(http://java.sun.com/developer/technicalArticles/Programming/HPROF.html)
HAT:堆分析工具
(https://hat.dev.java.net/)

析構

如何處理JAVA析構中的內存留用問題:

(http://www.devx.com/Java/Article/30192)

雜項

• J2SE 5.0 發行版註解

(http://java.sun.com/j2se/1.5.0/relnotes.html)

• JavaTM 虛擬機

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/index.html)

• Sun JavaTM 實時 System (Java RTS)

(http://java.sun.com/j2se/realtime/index.jsp)

• 關於垃圾收集的通用書籍:


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程序能夠訪問的引用變量的集合(包括局部變量、參數、類變量),程序可使用引用變量訪問對象的屬性和調用對象的方法。垃圾收集首選須要肯定從根開始哪些是可達的和哪些是不可達的,從根集可達的對象都是活動對象,它們不能做爲垃圾被回收,這也包括從根集間接可達的對象。而根集經過任意路徑不可達的對象符合垃圾收集的條件,應該被回收。下面介紹幾個經常使用的算法。

一、引用計數法(referencecountingcollector)

引用計數法是惟一沒有使用根集的垃圾回收得法,該算法使用引用計數器來區分存活對象和再也不使用的對象。通常來講,堆中的每一個對象對應一個引用計數器。當每一次建立一個對象並賦給一個變量時,引用計數器置爲1。當對象被賦給任意變量時,引用計數器每次加1。當對象出了做用域後(該對象丟棄再也不使用),引用計數器減1,一旦引用計數器爲0,對象就知足了垃圾收集的條件。

基於引用計數器的垃圾收集器運行較快,不會長時間中斷程序執行,適宜地必須實時運行的程序。但引用計數器增長了程序執行的開銷,由於每次對象賦給新的變量,計數器加1,而每次現有對象出了做用域生,計數器減1。

二、tracing算法(tracingcollector)

   tracing算法是爲了解決引用計數法的問題而提出,它使用了根集的概念。基於tracing算法的垃圾收集器從根集開始掃描,識別出哪些對象可達,哪些對象不可達,並用某種方式標記可達對象,例如對每一個可達對象設置一個或多個位。在掃描識別過程當中,基於tracing算法的垃圾收集也稱爲標記和清除(mark-and-sweep)垃圾收集器.

三、compacting算法(compactingcollector)

   爲了解決堆碎片問題,基於tracing的垃圾回收吸取了compacting算法的思想,在清除的過程當中,算法將全部的對象移到堆的一端,堆的另外一端就變成了一個相鄰的空閒內存區,收集器會對它移動的全部對象的全部引用進行更新,使得這些引用在新的位置能識別原來的對象。在基於compacting算法的收集器的實現中,通常增長句柄和句柄表。

四、coping算法(copingcollector)

   該算法的提出是爲了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時把堆分紅一個對象面和多個空閒面,程序從對象面爲對象分配空間,當對象滿了,基於coping算法的垃圾收集就從根集中掃描活動對象,並將每一個活動對象複製到空閒面(使得活動對象所佔的內存之間沒有空閒洞),這樣空閒面變成了對象面,原來的對象面變成了空閒面,程序會在新的對象面中分配內存。

   一種典型的基於coping算法的垃圾回收是stop-and-copy算法,它將堆分紅對象面和空閒區域面,在對象面與空閒區域面的切換過程當中,程序暫停執行。

五、generation算法(generationalcollector) 分代回收算法(Generational Collector)

stop-and-copy垃圾收集器的一個缺陷是收集器必須複製全部的活動對象,這增長了程序等待時間,這是coping算法低效的緣由。在程序設計中有這樣的規律:多數對象存在的時間比較短,少數的存在時間比較長。所以,generation算法將堆分紅兩個或多個,每一個子堆做爲對象的一代(generation)。因爲多數對象存在的時間比較短,隨着程序丟棄不使用的對象,垃圾收集器將從最年輕的子堆中收集這些對象。在分代式的垃圾收集器運行後,上次運行存活下來的對象移到下一最高代的子堆中,因爲老一代的子堆不會常常被回收,於是節省了時間。

六、adaptive算法(adaptivecollector)

在特定的狀況下,一些垃圾收集算法會優於其它算法。基於adaptive算法的垃圾收集器就是監控當前堆的使用狀況,並將選擇適當算法的垃圾收集器。

java 8 中的垃圾收集

java8從Hotspot JVM中刪除了永久代,因此咱們再也不須要爲永久代設置大小,也就是不用設置PermSize和MaxPermSize。
在java8以前方法區是做爲堆的永久代來實現的,啓動JVM時咱們須要設置永久代的大小,垃圾回收器也要回收這部分區域,並且會拋出內存溢出異常。借鑑於JRockit虛擬機,java8以後 Hotspot 虛擬機從堆中完全刪除了永久代。
—把方法區中的String和靜態變量移到了堆中。
—把其餘的東西(好比類結構)放到了本地內存中,JVM會直接負責這部分的內存回收。

總之,咱們再也不須要設置PermSize和MaxPermSize;方法區的內存溢出將再也不出現,除非本地內存耗光。

本文引用自:https://juejin.im/post/58fca9465c497d00580068ff

相關文章
相關標籤/搜索