bilibili-JVM學習筆記22 垃圾回收理論知識
The Java Virtual Machine Specification - Java SE 8 Edition
JVM學習筆記18 字節碼知識總結
JVM學習筆記19 JVM內存空間
JVM學習筆記20 jvisualvm
JVM學習筆記21 java 工具
基於 java 1.8.0
JVM垃圾回收重要理論剖析 74
- 運行時區域
- 程序計數器 PC
- 本地方法棧
- Java虛擬機棧 (JVM Stack)
- Java 虛擬機棧描述的是 Java 方法的執行模型:
- 每個方法執行的時候都會創建一個棧幀(Frame)用於存放局部變量表、操作數棧、動態鏈接、方法出口地址等信息。
- 一個方法的執行過程,就是這個方法對於棧幀的入棧和出棧過程;
- 線程隔離
- 堆
- 存放對象的實例數據
- JVM 管理內存中最大的一塊
- GC 主要的工作區域,爲了更高效的 GC,會把堆細分更多的子區域;
- 線程共享
- 方法區域
- 存放每個 Class 的結構信息,包括:常量池、字段描述、方法描述
- GC 的非主要工作區域
案例分析:
public void method1(){
Object obj = new Object();
}
- 生成了兩部分的內存區域:
- 1、 obj 這個引用變量,因爲是方法內的變量,放到虛擬機棧區域;
- 2、 真正 Object class 的實例對象,放到堆空間區域;
- 上述的 new 語句一共需要消耗 12 byte 內存空間:
- JVM 規定引用佔 4 byte (在 JVM Stack 中),而空對象需要 8 byte (在 Heap)
方法結束後,對應方法的 Stack 中的變量馬上回收,但是 Heap 重點中的對象要等到 GC 來回收
;
- 垃圾判斷算法
- 引用計數算法(Reference Counting)
- 給對象添加一個引用計數器,當有一個地方引用它時,計數器加1;當引用失效時,計數器減1;計數器爲 0 的對象就是不可能再被使用的;
- 算法簡單,效率高效;
- 存在對象循環引用的問題;
- 現代垃圾收集器基本不採用;
- 根搜索算法(Root Tracing)
- 在實際的生產語言中(Java、C#等),都是使用根搜索算法判定對象是否存活
- 算法基本思路就是通過一系列的稱爲
“GC Roots”
的點作爲起始點進行向下搜索,當一個對象到 GC Roots 沒有任何引用鏈(Reference Chain)相連,則證明此對象不可達
,即是不可用對象; - java 語言中的 GC Roots 包括:
- 在虛擬機棧(棧幀中的本地變量)中的引用
- 方法區中的靜態引用
- JNI(即一般說的 Native 方法) 中的引用
方法區
- Java 虛擬機規範表示不要求虛擬機實現在方法區中實現 GC ,因爲在方法區中進行 GC 性價比一般比較低;
- 當前的商業 JVM 都有實現方法區的 GC ,主要回收兩部分內容:
- 廢棄常量
- 無用類(滿足以下三個要求):
- 該類的所有實例都已經被 GC ,也就是 JVM 中不存在該 Class 的任何實例;
- 加載該類的 ClassLoader 已經被 GC ;
- 該類對應的 java.lang.Class 對象沒有在任何地方被引用,如不能在任何地方通過反射訪問此類的方法;
- 在大量使用反射、動態代理、CGLib 等字節碼框架、動態生成 JSP 以及 OSGi 這類頻繁自定義 ClassLoader 的場景都需要 JVM 具備類卸載的支持以保證方法區不會發生
內存溢出
JVM垃圾回收算法分析與演示 75
- 垃圾回收算法
- 標記-清除算法(Mark-Sweep)
- 算法分爲
標記
和清除
兩個階段,首先標記出所有需要回收的對象,然後回收所有需要回收的對象; - 缺點
- 效率問題:標記和清理兩個過程的效率都不高;
- 需要掃描所有對象,堆越大,GC 越慢;
- 空間問題:標記清理之後會產生大量不連續的
內存碎片
,空間碎片太多可能會導致後續使用中無法找到足夠大的連續內存
而提前觸發另一次的垃圾蒐集動作;- GC 次數越多,碎片越嚴重;
- 效率問題:標記和清理兩個過程的效率都不高;
- 算法分爲
- 標記-整理算法(Mark-Compact)
- 標記過程和上述一樣,但後續步驟不是進行直接清理,而是令所有存活的對象一端移動,然後直接清理掉這端邊界以外的內存;
- 沒有內存碎片;
- 比 Mark-Sweep 耗費更多的時間進行 Compact(整理);
- 複製算法(Copying)
- 將可用內存劃分爲兩塊,每次只使用其中的一塊,當其中一半的內存用完時,將這一區域中存活的對象複製到另一半內存區域中去,然後就把此區域內存空間一次性清理掉;
- 每次回收都是對整個半區進行回收,內存分配時也就不用考慮內存碎片的情況了,實現簡單,運行高效;
- 內存
空間浪費極其嚴重
,只能使用內存空間的一半。 - 現代的商業虛擬機中都是使用這一收集算法來回收
新生代區域
複製收集算法在對象存活率高的時候,效率有所下降
- 如果不想浪費一半的空間,就需要有額外的空間進行分配擔保用於應付另一半區域內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用複製算法;
只需要掃描存活的對象
,效率更高;- 不會產生碎片
- 需要浪費額外的內存作爲複製區
- 複製算法非常適合生命週期比較短的對象,因爲每次 GC 總能回收大部分的對象,複製的開銷比較小;
- 根據 IBM 的專門研究,98% 的 Java 對象只會存活 1 個 GC 週期,對這些對象很適合使用複製算法。而且不用 1 : 1 的劃分工作區和複製區的空間;
- 分代收集算法(Generational Collecting)
- 當前商業虛擬機的垃圾收集都是採用分代收集算法,根據對象不同的存活週期將內存劃分爲幾塊;
- 一般是把 Java 堆分作
新生代和老年代
,這樣就可以根據各個年代的特點採用最適合的收集算法
; - 譬如新生代每次 GC 都有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。
標記-清除算法
–>
–>
分代複製算法:分代算法和複製算法 結合使用
- HotSpot VM
- HotSpot JVM 6 中共劃分爲三個代:
- 年輕代(Young Generation)
- 新生成的對象
一般
都放在新生代;(理論上,年輕代對象的生命週期非常短,所以使用複製算法) - 分爲三個子區域:一個 Eden 區,兩個 Survivor 區(可以通過參數設置 Survivor 個數);
- Eden區 和 兩個 Survivor 區的缺省比例是 8 : 1 : 1 ;
- 可以根據 GC log 的信息調整此比例的大小;
- 新生成的對象
- 老年代(Old Generation)
- 存放經過多次 GC 還存活的對象
- 一般採用 Mark-Sweep 或者 Mark-Compact 算法進行 GC
- 有多種垃圾收集器可以選擇,可以根據具體應用的需求選用合適的垃圾收集器;(吞吐量,響應時間)
- 永久代(Permanent Generation)
- 並不屬於堆空間,但是 GC 也會涉及到這個區域;
- 存放了每個 Class 的結構信息,包括:常量池、字段描述、方法描述;
- 年輕代(Young Generation)
JVM垃圾回收器理論分析與講解 76
內存結構
內存分配
- 堆上分配
- 大多數情況在 eden 區分配,偶爾直接在 old 上分配;
- 細節取決於 GC 的實現;
- 棧上分配
- 原子類型的局部變量
- byte boolean char short int float long double
- 原子類型的局部變量
內存回收
- GC 要做的是將那些 dead 的對象所佔用的內存回收掉
- HotSpot 認爲沒有引用的對象是 dead 的;
- HotSpot 將引用分爲四種:
Strong
、Soft
、Weak
、Phantom
- Strong Reference 強引用:默認通過 new 對象進行賦值的引用都是強引用;
- Soft(軟引用)、Weak(弱引用)、Phantom(虛引用) 都繼承
Reference 類
;
- 在 Full GC 時會對 Reference 類型的引用進行特殊處理;
- Soft(軟引用):
- 內存不夠時會被 GC
- 長期不用也會被 GC
- Weak(弱引用)
- 無論內存夠不夠用,一定會被 GC
- 當被 mark 爲 dead ,會在 ReferenceQueue 中通知
- Phantom(虛引用)
- 本來就沒引用,當從 jvm heap 中釋放時會通知
- Soft(軟引用):
GC 的時機
- 在分代模型的基礎上,GC 從時機上分爲兩種:
- Scavenge GC (又稱 Minor GC)
- 觸發時機:
- 新對象生成時
- Eden 區空間滿了
- 理論上 Eden 區大多數對象會在 Minor GC 回收掉,複製算法的執行效率很高,Minor GC 時間比較短;
- 觸發時機:
- Full GC
- 對整個 JVM 進行整理,包括 Young、Old、Perm(jdk8移除)
- 主要觸發時機:
- Old 區滿了
- Perm 區滿了
- 手動執行 System.gc()
- 效率很低,儘量減少 Full GC 的次數;(STW)
- Scavenge GC (又稱 Minor GC)
- 垃圾回收器 Garbage Collector
- 分代模型 :GC 的宏觀願景
- 垃圾回收器 : GC 的具體實現
- HotSpot VM 提供了多種垃圾回收器,我們需要根據具體應用場景採用不同的垃圾回收器;
- 每種垃圾回收器都有自己的使用場景
- 並行(Parallel)
- 指多個收集器的線程同時工作,但是用戶線程處於等待狀態;
- 併發(Concurrent)
- 指收集器在工作的同時,可以允許用戶線程工作;
- 不代表解決了 GC 停頓的問題,在關鍵的步驟還是要停頓的;如在標記階段必須停頓,而清除階段用戶線程和GC線程併發執行;
Serial
垃圾收集器- 新生代和老年代
均可
使用 - 在新生代使用複製收集算法,在老年代採用 Mark-Compact 算法
- 最早的收集器,
單線程
收集器,GC 時會暫停所有工作線程(Stop The WorldSTW
),沒有多線程切換的開銷,簡單實用 - HotSpot 虛擬機在
Client 模式
下的默認新生代收集器
- 新生代和老年代
ParNew
垃圾收集器- Serial 的多線程版本,除了多個收集線程外,其餘行爲包括收集算法,STW,對象分配規則,回收策略等都與 Serial 收集器一樣;
- 使用複製算法(因爲主要針對新生代)
- HotSpot 虛擬機在
Server 模式
下的默認新生代收集器
- 只有在多 CPU 的環境下,效率纔會比 Serial 收集器高,在單 CPU 的環境下,並不會比 Serial 收集器有更好的效果;
- 可以通過
-XX:ParallelGCThreads=3
來控制 GC 線程數的多少,需要結合具體 CPU 的個數
Parallel Scavenge
垃圾收集器- 多線程收集器
- 使用複製算法
- 對象分配規則,回收策略都與 ParNew 有所不同;
- 以吞吐量最大化爲目標的收集器實現(即 GC 時間佔總運行時間最小),它
允許較長時間的 STW
換取總吞吐量最大化;
CMS
垃圾收集器 (Concurrent Mark Sweep)- 以最短停頓時間爲目標的收集器(捨棄吞吐量)
- 追求最短停頓時間,非常適合 Web 應用
- 採用標記-清除算法
- 老年代收集器,一般結合
ParNew
使用 - Concurrent ,GC 線程和用戶線程併發工作
- 只有在多 CPU 環境下才有意義;
- 使用
-XX:+UseConcMarkSweepGC
打開 - 缺點
- CMS 已犧牲 CPU 資源的代價來減少用戶線程的停頓,當 CPU 個數少於 4 的時候,有可能對吞吐量影響很大;
- CMS 在併發清理的過程中,用戶線程還在工作,需要預留一部分空間給用戶線程
- 因爲用 Mark-Sweep 算法,會帶來碎片化問題,碎片化過多時會容易頻繁觸發 Full GC
Serial Old
垃圾收集器- 老年代收集器
單線程
收集器- 採用 Mark-Compact 算法
Parallel Old
垃圾收集器老年代
版本吞吐量優先
的收集器多線程
收集器- 採用 Mark-Compact 算法
- JVM 1.6 提供,在此之前,如果新生代使用 Parallel Scavenge 收集器,老年代除 Serial Old 外別無選擇;(因爲 Parallel Scavenge 無法與 CMS 配合使用)
- Parallel Scavenge + Parallel Old = 高吞吐量,但 GC 停頓時間可能不理想
Serial
垃圾收集器
Parallel Old
垃圾收集器
關於 GC 的 JVM 參數定義
Java HotSpot 7 VM Options
Java Platform, Standard Edition Tools Reference 8 – Solaris 平臺
參數 | 描述 |
---|---|
-XX:+UseSerialGC | VM 運行在 Client 下的默認值,打開開關後,使用 Serial + Serial Old 的收集器組合進行內存回收 |
-XX:+UseParNewGC | 打開開關後,使用 ParNew + Serial Old 的收集器組合進行內存回收 (??) |
-XX:+UseConcMarkSweepGC | 打開開關後,使用 ParNew + CMS + Serial Old 的收集器組合進行內存回收,Serial Old 作爲 CMS 出現 Concurrent Mode Failure 失敗後的後備收集器使用 |
-XX:+UseParallelGC | VM 運行在 Server 下的默認值,打開開關後,使用 Parallel Scavenge + Serial Old 的收集器組合進行內存回收 |
-XX:+UseParallelOldGC | 打開開關後,使用 Parallel Scavenge + Parallel Old 的收集器組合進行內存回收 |
-XX:SurvivorRatio=ratio | 新生代中 Eden 區與 Survivor 區的比值,默認爲 8,表示 Eden:S0:S1 = 8:1:1 |
-XX:MaxTenuringThreshold=threshold | 晉升到老年代的對象年齡。當前最大值爲15。並行收集器的默認值爲15,而CMS的默認值爲4。 |
-XX:ParallelGCThreads=threads | 設置新舊並行垃圾收集器中的垃圾收集線程數。缺省值隨運行JVM的平臺而異。 |