注:本文部份內容搜集整理了網上的資料。
1. 內存結構
1.1. 分代結構圖
注意:
在JVM中,非堆內存,根據模式不同分為不同的幾個部份。
-Server下:非堆包括:持久代和代碼緩存(Code cache)
-client下:非堆包括:持久代、代碼緩存(Code cache)、Perm rw和perm ro。
2. 性能指標
對垃圾搜集的性能,主要有兩個指標。
吞吐量(throughput)指用來進行垃圾搜集以外工作所用的時間占總時間的百分比,1般要通太長時間的視察和丈量。吞吐量包括了分配內存所花費的時間在內(1般來講無需對分配進行調優)。
Throughput is the percentage of total time not spent in garbage collection,considered over long periods of time. Throughput includes time spent inallocation (but tuning for speed of allocation is generally not needed.) (from《Tuning GC with jdk 1.5》)
暫停(Pause)指由于進行垃圾搜集而致使利用沒法響應的時間。
Pauses are the times when an application appears unresponsive becausegarbage collection is occurring.
Footprint is the working set of a process, measured in pages and cachelines. On systems with limited physical memory or many processes, footprint maydictate scalability. Promptnessis the time between when an object becomes dead and when the memory becomesavailable, an important consideration for distributed systems, including remotemethod invocation (RMI). (from《Tuning GC with jdk 1.5》)
用戶對垃圾搜集有不同的需求,例如,對Web服務器利用來講,吞吐量是要側重斟酌的,而暫停時間可能由于網絡的反應時間而不那末明顯;而對1個交互式圖形界面的利用來講,即便是短暫的暫停都會帶來非常不好的用戶體驗。
In general, aparticular generation sizing chooses a trade-off between these considerations.For example, a very largeyoung generation may maximize throughput,but does so at the expense of footprint, promptness, and pause times.younggeneration pauses can be minimized by using a smallyoung generationat the expense of throughput. To a first approximation, the sizing of onegeneration does not affect the collection frequency and pause times for anothergeneration .(from《Tuning GC with jdk 1.5》)
通常來講,如何設置代的大小是在這些斟酌因素之間作出的1個權衡。例如,將新生代設置得很大會得到很好的吞吐性能,但是會增加暫停時間;反之,較小的新生代設置會減小暫停時間,但是下降了吞吐量。
1個代的大小不應當影響在其他代上進行垃圾搜集的頻率和暫停時間。
3. 援用類型
對象援用類型分為強援用、軟援用、弱援用和虛援用。
3.1. 強援用
就是我們1般聲明對象是時虛擬機生成的援用,強援用環境下,垃圾回收時需要嚴格判斷當前對象是不是被強援用,如果被強援用,則不會被垃圾回收。
3.2. 軟援用
軟援用1般被做為緩存來使用。與強援用的區分是,軟援用在垃圾回收時,虛擬機會根據當前系統的剩余內存來決定是不是對軟援用進行回收。如果剩余內存比較緊張,則虛擬機會回收軟援用所援用的空間;如果剩余內存相對富裕,則不會進行回收。換句話說,虛擬機在產生OutOfMemory時,肯定是沒有軟援用存在的。
3.3. 弱援用
弱援用與軟援用類似,都是作為緩存來使用。但與軟援用不同,弱援用在進行垃圾回收時,是1定會被回收掉的,因此其生命周期只存在于1個垃圾回收周期內。
3.4. 虛援用
4. 內存分配回收次序
新生代是類的誕生、成長、滅亡的區域,1個類在這里產生,利用,最后被垃圾回收器搜集,結束生命。
新生區又分為兩部份:伊甸區(Eden space)和幸存者區(Survivorpace),所有的類都是在enden被new出來的。幸存區有兩個: 0區(Survivor 0 space)和1區(Survivor 1 space)。
當Eden的空間用完時,程序又需要創建對象,JVM的垃圾回收器將對enden區進行垃圾回收,將enden區中的不再被其他對象所援用的對象進行燒毀(次搜集)。
然后將伊甸園中的剩余對象移動到幸存0區。若幸存0區也滿了,再對該區進行垃圾回收(次搜集),然后移動到1區。那如果1區也滿了呢?(次搜集)
再移動到養老區。
新生代采取復制算法,old區采取標記清除算法。
不管是復制算法還是標記清除算法,在垃圾搜集期間都要暫停客戶的利用程序(cms垃圾搜集器除外,它在初始標記和重新標記時暫停,并發標記和并發清除時,客戶線程不暫停)。
不管是復制算法還是標記清除算法,在最開始,倒要標記活動的對象,即對象的可達性分析,在這里,必須要求對象是1致的(便可達性分析期間,內存狀態是凍結的);在cms搜集器是,在初始標記(對root對象標記時,要求凍結)凍結,但此時會產生浮動垃圾,即在并發標記時,由分配了新的對象(由于沒有凍結)。
綜述,基本進程是,復制算法:標記(暫停)-->復制(暫停,因對象地址產生變化);標記清除整理算法:標記(暫停)-->清除整理(消除碎片,暫停,因對象地址產生變化)。
對root對象標記很快,對內存掃描分析,可達性分析進程很慢。清除很慢(內存回收),內存整理、復制(慢)。
內存分配需要時間。
5. 對象標記算法(Object Marking Algorithms)
5.1. 援用計數法(ReferenceCounting)
堆中每個對象都有1個援用計數。當新創建1個對象,或有變量被賦值為這個對象的援用,則這個對象的援用計數加1;當1個對象的援用超過生存期或被設置1個新的值時,這個對象的援用計數減1。當對象的援用計數變成0時,就能夠被當作垃圾搜集。
這類方法的好處是垃圾搜集較快,適用于實時環境。缺點是這類方法沒法監測出循環援用。例如對象A援用對象B,對象B也援用對象A,則這兩個對象可能沒法被垃圾搜集器搜集。因此這類方法是垃圾搜集的初期策略,現在很少使用。
5.2. 根搜索算法(Garbage Collection Roots Tracing)
5.2.1. 基本思想
這類方法把每一個對象看做圖中1個節點,對象之間的援用關系為圖中各節點的鄰接關系。垃圾搜集器從1個或數個根結點遍歷對象圖,如果有些對象節點永久沒法到達,則這個對象可以被當作垃圾回收。
容易發現,這類方法可以檢測出循環援用,避免了援用計數法的缺點,較為經常使用。步驟以下:
選定1些對象,作為 GC Roots,組成基對象集;
由基對象集內的對象動身,搜索所有可達的對象;
其余的不可達的對象,就是可以被回收的對象。
這里的“可達”與“不可達”與圖論中的定義1樣,所有的對象被看作點,援用被看作有向連接,全部援用關系就是1個有向圖。在“援用計數法”中提到的循環援用,其實就是有向圖中有環的情況,即構成“有向有環圖”。援用計數法不適用于“有向有環圖”,而根搜索算法適用于所有“有向圖”,包括有環的和無環的。
5.2.2. GCRoots
如果你的邏輯思惟夠清晰,你會說“1定與選取基對象集的方法有關”。是的,沒錯。選取 GC Roots 組成基對象集,其實就是選取以下這些對象:
《深入理解 Java 虛擬機:JVM高級特性與最好實踐》1書中提到的 GC Roots 為:
1. 方法區(Method Area,即 Non-Heap)中的類的 static 成員援用的對象,和 final成員援用的對象;
2. Java方法棧(Java Method Stack)的局部變量表(Local Variable Table)中援用的對象;
3. 原生方法棧(Native Method Stack)中 JNI中援用的對象。
但明顯不夠全面,[參考2]中提到的要更全面:(March 6th,2012 update)
1. 由系統類加載器加載的類相應的對象:這些類永久不會被卸載,且這些類創建的對象都是 static 的。注意用戶使用的類加載器加載的類創建的對象,不屬于 GC Roots,除非是 java.lang.Class 的相應實例有可能會稱為其他類的 GC Roots。
2. 正在運行的線程。
3. Java方法棧(Java Method Stack)的局部變量表(Local Variable Table)中援用的對象。
4. 原生方法棧(Native Method Stack)的局部變量表(Local Variable Table)中援用的對象。
5. JNI中援用的對象。
6. 同步監控器使用的對象。
7. 由 JVM 的 GC控制的對象:這些對象是用于 JVM內部的,是實現相干的。1般情況下,可能包括系統類加載器(注意與“1”不1樣,“1”中是 objects created by the classes loaded bysystem class loaders,這里是 theobjects, corresponding instances of system class loaders)、JVM內部的1些重要的異常類的對象、異常句柄的預分配對象和在類加載進程中自定義的類加載器。不幸的是,JVM其實不提供這些對象的任何額外的詳細信息。因此這些實現相干的內容,需要依托分析來判定。
所以這個算法實行起來有兩部份,第1部份就是到 JVM 的幾個內存區域中“找對象”,第2部份就是應用圖論算法
6. 垃圾回收算法
6.1. 標記-清除(Mark-Sweep)
此算法履行分兩階段。第1階段從援用根節點開始標記所有被援用的對象,第2階段遍歷全部堆,把未標記的對象清除。此算法需要暫停全部利用,同時,會產生內存碎片。
6.2. 標記-整理(Mark-Compact)
此算法結合了“標記-清除”和“復制”兩個算法的優點。也是分兩階段,第1階段從根節點開始標記所有被援用對象,第2階段遍歷全部堆,把清除未標記對象并且把存活對象“緊縮”到堆的其中1塊,按順序排放。此算法避免了“標記-清除”的碎片問題,同時也避免了“復制”算法的空間問題。
6.3. 復制(Copying)
此算法把內存空間劃為兩個相等的區域,每次只使用其中1個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象復制到另外1個區域中。次算法每次只處理正在使用中的對象,因此復制本錢比較小,同時復制過去以后還能進行相應的內存整理,不會出現“碎片”問題。固然,此算法的缺點也是很明顯的,就是需要兩倍內存空間。
6.4. 增量搜集算法
增量搜集器把堆棧分為多個域,每次僅從1個域搜集垃圾。這會造成較小的利用程序中斷。
6.5. 分代搜集算法
這類搜集器把堆棧分為兩個或多個域,用以寄存不同壽命的對象。虛擬機生成的新對象1般放在其中的某個域中。過1段時間,繼續存在的對象將取得使用期并轉入更長壽命的域中。分代搜集器對不同的域使用不同的算法以優化性能。這樣可以減少復制對象的時間。
6.6. 并發搜集算法
并發搜集器與利用程序同時運行。這些搜集器在某點上(比如緊縮時)1般都不能不停止其他操作以完成特定的任務,但是由于其他利用程序可進行其他的后臺操作,所以中斷其他處理的實際時間大大下降。
6.7. 并行搜集器
并行搜集器使用某種傳統的算法并使用多線程并行的履行它們的工作。在多CPU機器上使用多線程技術可以顯著的提高java利用程序的可擴大性。
6.8. 自適應搜集器
根據程序運行狀態和堆的使用狀態,自動選1種適合的垃圾回收算法。這樣可以不局限與1種垃圾回收算法。
6.9. 火車增量算法
垃圾搜集算法1個很大的缺點就是難以控制垃圾回收所占用的CPU時間,和什么時候需要進行垃圾回收。火車算法是分代搜集器所用的算法,目的是在成熟對象空間中提供限定時間的漸進搜集。目前利用于SUN公司的Hotspot虛擬機上。