回顧JVM的內存分配包括程序計數器,虛擬機棧,本地方法棧,JAVA堆和方法區5部分,其中前三是屬于線程私有的,內存分配和回收有確定性,不多考慮回收的問題,因為線程或者方法結束時,內存自然回收。相對的,在JAVA堆和方法區里,需要注意內存回收的問題。
-
對象存活判斷方法
1.1 引用計數法,非Java
給對象添加一個引用計數器,當有引用時,給之加一,引用減少,給之減一,當計數器為0代表不再存在引用。
?無法解決循環引用
1.2 可達性分析
通過定義GC Roots作為起始點,從這些節點往下搜索,路徑稱為引用鏈,當一個GC roots沒有任何引用鏈相連,則證明對象不可用。如下o5,o6和GC roots是不可達的,如圖:
GC roots
可以作為GC Roots的對象:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧中JNI引用的對象,即Native方法
java引用類型
2.1 強引用
普遍存在的類似Object o = new Object()的引用
2.2 軟引用
描述一些還有用但是非必需的對象,系統在發生內存溢出之前,會把這些對象列入回收范圍做第二次回收,如果還是沒有足夠內存,才會拋出異常。提供SoftReference類實現軟引用。
2.3 弱引用
只能生存到下一次垃圾回收之前,即處在回收邊緣。當垃圾回收時,無論當前內存是否足夠,都會回收只被弱引用關聯的對象。提供WeakReference類實現弱引用。
2.4 虛引用
對象的虛引用關系不會對其生存時間構成影響,也無法通過虛引用取得一個實例,設置虛引用的唯一目的是能在這個對象被回收內存時收到一個系統通知。提供一個PhantomReference來實現虛引用。方法區回收
3.1 回收廢棄常量
常量沒有被引用,或不存在對應字面值
3.2 無用的類
?是否回收通過參數控制,可以進行回收的條件如下:
- 類所有實例已經被回收,java堆中不存在該類的任何實例。
- 加載類的ClassLoader被回收
- 類對應的java.lang.class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
垃圾回收算法
4.1 標記-清除
基于之前的可達性分析,標記出所有需要回收的對象,在標記完成后統一回收。存在兩個問題:1,效率,2,空間,會產生很多內存碎片。如果之后再分配大對象時,無法找到足夠內存,而觸發另一次新的垃圾收集動作。
4.2 復制-算法
將內存按容量分為大小相等的兩塊,每次只使用其中一塊,當用完以后將還存活的對象復制到另一塊上面,再把已經使用過的內存空間一次性清掉。每次只堆半個區進行回收,分配時也不用考慮內存碎片的問題,只要移動堆頂指針,按順序分配內存即可。缺點是將內存分為兩塊,縮小了一半。
細節是將堆分為eden區和兩個survivor區,一般比例是8:1,在survivor空間之間進行復制清理,也就只有10%的內存浪費。
?如果另一個survivor區沒有足夠空間存放上一次新生代收集下來的存活對象,這些對象通過分配擔保機制進入老年代。
4.3 標記-整理
復制算法在對象存活率較高時,需要很多的復制操作,效率較低。如果不想類似分配survivor區,需要額外的內存擔保,所以老年代不能直接選用復制算法。根據其特性,使用標記-整理方法。先進行內存標記,標記可回收對象,和標記清除不同,標記-整理讓這些所有存活對象都向一端移動,然后直接清理掉端邊界以外的內存。
4.4 分代收集
分為新生代和老年代。新生代每次垃圾回收都有大批對象死去,選用復制算法。老年代對象存活率高,沒有額外空間進行分配擔保,必需使用標記-清除或者標記-整理進行回收。垃圾收集器
5.1 Serial串行收集器
單獨一個線程,新生代復制算法,老年代標記整理,都暫停所有用戶線程,有很高的但線程效率,常用在client模式的服務上。
5.2 ParNew收集器
多個GC線程,并行多線程版本的Serial收集器,server模式下,首選的新生代收集器,與老年代收集器CMS配合。
5.3 Parallel Scavenge收集器
新生代收集器,采用復制算法,并行多線程,與ParNew不同在于通過參數設置吞吐量,GCTimeRatio-收集時間占比和MaxGCPauseMillis-最大GC時間。補充:通過UseAdaptiveSizePolicy設置根據監控數據自適應提供最大吞吐量。
5.4 Serial Old收集器
Serial收集器的老年代版本,單線程,主要用在client模式下。server模式主要和Parallel Scavenge配合,以及作為CMS的后備方案。
5.5 Parallel Old收集器
Parallel Scavenge的老年代版本,多線程,與Parallel Scavenge配合,保證吞吐量。
5.6 CMS收集器
低停頓,并發,最常用的收集器。收集過程有:初始標記,并發標記,重新標記,并發清除。缺點有以下3個:
- 并發的緣故,CPU資源敏感
- 無法清理浮動垃圾,垃圾不斷新產生。如果導致CMS預留的內存無法滿足需要,出現一次“Concurrent Model Failure” , 臨時會啟動Serial Old 收集器,關聯參數,設置觸發閾值,CMSInitiatingOccupancyFraction
- 標記-清除,導致內存碎片,分配大對象沒有連續空間,導致提前FGC。關聯參數:
UseCMSCompactAtFullCollection 在CMSfull GC時開啟內存碎片整理過程。
CMSFullGCsBeforeCompaction 執行參數設置的次數FGC后,進行壓縮的碎片整理。
5.7 G1
以下4個特性: - 并行和并發:利用多CPU,多核優勢,縮短停頓時間。
- 分代收集:保留分代收集,但不再隔離。
- 空間整合:分成region區域,整體看類似標記-整理,region間類似復制,不會產生內存碎片,收集后有規整的可用內存。
- 可預測的停頓:有可預測的時間模型,指定停頓時間。因為可以有計劃的進行全區域的垃圾收集。跟蹤Region里的垃圾堆積的價值大小(回收后的空間和垃圾收集所需的時間經驗值),后臺維護一個優先列表,優先回收價值最大的Region,也是其名稱G1(Garbage First)的由來。
?可能的問題:region區域之間對象的引用,是否需要掃描整個堆?
引申閱讀:remember Set 在創建引用時,先檢查引用關系,寫入對應的region的remember set中,之后GC過程方便取到,避免全堆掃描。(個人理解)