JVM框架
Java虛擬機HotSpot的框架:
JVM主要組成部分:Class Loader(類加載器)、Runtime Data Area(運行時數據區)、執行引擎(Execution Engine)。
JVM垃圾回收簡介
Step 1: Marking
GC算法在掃描存活對象時通常需要從Root節點開始,掃描所有存活對象的引用,構建出對象圖。
Root:靜態字段、方法參數、局部變量、CPU寄存器
Step 2: Normal Deletion
刪除沒有被引用的對象,釋放空間。
Step 2a: Deletion with Compacting
刪除垃圾並壓縮存活的引用對象,有利於提高內存分配的效率。
Generational Garbage Collection(分代垃圾回收)
HotSpot堆結構:
Young Generation:從eden區分配新對象,eden區滿後,發生一次minor garbage collection,把eden區和一個survivor區中存活的對象移動到另一個survivor區中,存活的對象age加1,當存活的對象age達到一個阈值時晉升到Old Generation。
Old Generation:保存存活長久對象的地方,Old Generation滿後會發生major garbage collection(full garbage collection)。
Stop the World Event:minor garbage collection和major garbage collection都是Stop the World Event,即垃圾回收的時候會暫停程序中線程的執行。
Permanent generation:保存JVM中用於描述類和方法的元數據信息。
整個GC的流程總結圖:
GC分代的基本假設是:絕大部分對象的生命周期都非常短暫,存活時間短。
分配小對象的開銷負擔小,不要吝啬去創建。
GC最喜歡這種小而短命的對象。
讓對象的生命周期盡可能短,例如在方法體內創建,使其能盡快地在YoungGC中被回收,不會晉升(romote)到年老代(Old Generation)。
對象分配的優化:盡量避免大對象的分配,當對象大到Eden Generation放不下時,JVM只能嘗試去Old Generation分配,這種情況需要盡可能避免,因為一旦在Old Generation分配,這個對象就只能被Old Generation的GC或是FullGC回收了。
不可變對象可以減少GC的壓力:Hotspot JVM為了提高YoungGC的性能,避免每次YoungGC都掃描Old Generation中的對象引用,采用了卡表(Card Table) 的方式。簡單來說,當Old Generation中的對象發生對Young Generation中的對象產生新的引用關系或釋放引用時,都會在卡表中響應的標記上標記為髒(dirty),而YoungGC時,只需要掃描這些dirty的項就可以了。可變對象對其它對象的引用關系可能會頻繁變化,並且有可能在運行過程中持有越來越多的引用,特別是容器。這些都會導致對應的卡表項被頻繁標記為dirty。而不可變對象的引用關系非常穩定,在掃描卡表時就不會掃到它們對應的項了。
指定容器初始化大小可以減少GC的壓力:每次容器擴容分配更大的空間,可能會增加GC的次數。
各類引用:java.lang.ref.Reference有幾個子類,用於處理和GC相關的引用。JVM的引用類型簡單來說有幾種:
Strong Reference,最常見的引用。
Weak Reference,當沒有指向它的強引用時會被GC回收。
Soft Reference,只當臨近OOM時才會被GC回收。
Phantom Reference,主要用於識別對象被GC的時機,通常用於做一些清理工作。
Garbage Collector(垃圾收集器)
JVM中會在以下情況觸發回收:對象沒有被引用,作用域發生未捕捉異常,程序正常執行完畢,程序執行了System.exit(),程序發生意外終止。
JVM中標記垃圾使用的算法是一種根搜索算法。簡單的說,就是從一個叫GC Roots的對象開始,向下搜索,如果一個對象不能達到GC Roots對象的時候,說明它可以被回收了。這種算法比一種叫做引用計數法的垃圾標記算法要好,因為它避免了當兩個對象啊互相引用時無法被回收的現象。
JVM中對於被標記為垃圾的對象進行回收時又分為了一下3種算法:
1.標記清除算法,該算法是從根集合掃描整個空間,標記存活的對象,然後在掃描整個空間對沒有被標記的對象進行回收,這種算法在存活對象較多時比較高效,但會產生內存碎片。
2.復制算法,該算法是從根集合掃描,並將存活的對象復制到新的空間,這種算法在存活對象少時比較高效。
3.標記整理算法,標記整理算法和標記清除算法一樣都會掃描並標記存活對象,在回收未標記對象的同時會整理被標記的對象,解決了內存碎片的問題。
HotSpot 1.6版使用的垃圾收集器如下圖(圖中兩個收集器之間有連線,說明它們可以配合使用):
1.Serial GC
從名字上看,串行GC意味著是一種單線程的,所以它要求收集的時候所有的線程暫停。這對於高性能的應用是不合理的,所以串行GC一般用於Client模式的JVM中。
2.ParNew GC
是在SerialGC的基礎上,增加了多線程機制。但是如果機器是單CPU的,這種收集器是比SerialGC效率低的。
3.Parrallel Scavenge GC
這種收集器又叫吞吐量優先收集器,而吞吐量=程序運行時間/(JVM執行回收的時間+程序運行時間),假設程序運行了100分鐘,JVM的垃圾回收占用1分鐘,那麼吞吐量就是99%。Parallel Scavenge GC由於可以提供比較不錯的吞吐量,所以被作為了server模式JVM的默認配置。
4.ParallelOld
是老生代並行收集器的一種,使用了標記整理算法,是JDK1.6中引進的,在之前老生代只能使用串行回收收集器。
5.Serial Old
是老生代client模式下的默認收集器,單線程執行,同時也作為CMS收集器失敗後的備用收集器。
6.CMS(Concurrent Mark Sweep)
又稱響應時間優先回收器,使用標記清除算法,同時它又是一個使用多線程並發回收的垃圾收集器。他的回收線程數為(CPU核心數+3)/4,所以當CPU核心數為2時比較高效些。CMS分為4個過程:初始標記、並發標記、重新標記、並發清除和並發重置。其中初始標記和重新標記是獨占系統資源的,而並發標記、並發清除和並發重置是可以和用戶線程一起執行的。因此,從整體上來說,CMS 收集不是獨占式的,它可以在應用程序運行過程中進行垃圾回收。
7.Garbage First(G1)
比較特殊的是G1回收器既可以回收Young Generation,也可以回收Tenured Generation。它是在JDK6的某個版本中才引入的,性能比較高,同時注意了吞吐量和響應時間。與CMS收集器相比,G1收集器是基於標記-壓縮算法的。因此,它不會產生空間碎片,也沒有必要在收集完成後,進行一次獨占式的碎片整理工作。
對於垃圾收集器的組合使用可以通過下表中的參數指定: