Java 虛擬機 其他相關博客
引用資料地址
垃圾收集算法與垃圾收集器
Java常見面試題—GC垃圾收集器
7種垃圾收集器
《 深入理解 Java 虛擬機:JVM高級特性與最佳實踐》
文章目錄
GC
介紹
GC垃圾收集,Java提供的GC可以自動監測對象是否超過作用域從而達到自動回收內存的目的。
垃圾回收可有效使用內存和防止內存泄露。垃圾回收器通常是作爲一個單獨的低優先級線程運行,不可預知的情況下對內存堆中已死亡或長久無使用的對象進行清除和回收。回收機制:分代複製垃圾回收、標記垃圾回收、增量垃圾回收等方式。
1.GC是如何判斷對象是否存活
-
引用計數算法
介紹:給每一個對象添加一個引用計數器,當有引用指向對象時,計數器加一,引用移除時,計數器減一,當計數器爲0時,說明對象未被引用,可以回收
存在問題:兩個對象互相引用,此時不會被回收。
使用:java虛擬機不採用 -
根搜索算法
介紹:通過一系列的名爲“GC Roots”的對象作爲起點,從這些節點向下搜索,經過的路徑稱爲引用鏈,當一個對象沒有引用鏈即是可回收狀態。
java語言中的GC Roots 的對象包括:1.虛擬機棧中的引用對象
2.方法區中的類靜態屬性引用的對象
3.方法區常量引用的對象
4.本地方法棧中JNI的引用對象使用:java虛擬機採用
2.引用
- 介紹:如果 reference 類型的數據中存儲的數值代表的是另外一塊內存的起始地址,就稱這塊內存代表着一個引用
- 分類:強引用,軟引用,弱引用,虛引用
強引用:強引用就是指在程序代碼中普遍存在的,類似 Object obj = new Object() 這類的引用,只要強引用還存在,垃圾收集器永遠都不會回收掉被引用的對象
軟引用:軟引用用來描述一些還有用,但非必需的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中並進行第二次回收,如果這次回收還是沒有足夠的內存,纔會拋出內存溢出異常。
弱引用:弱引用也是用來描述非必須對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾手機發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉被弱引用關聯的對象。
虛引用:虛引用也稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的唯一目的就是希望能在這個對象被收集器回收時收到的一個系統通知。
3.GC是如何判斷對象是否存活
一個對象真正死亡至少要經過兩次標記
- 第一次標記:經過根搜索發現沒有與 GC Roots 相鏈接的引用鏈
標記條件:是否有必要執行 finalize() 方法,對象沒有覆蓋 finalize() 方法,或 finalize() 方法已經被虛擬機調用過,虛擬機將這兩種情況視爲 沒有必要執行。被標記的對象將會放入一個 F-Queue 隊列中等待第二次標記 - 第二次標記:GC 對 F-Queue 進行第二次小規模標記,這個過程中對象重新與引用鏈上的任何一個對象建立關聯,獲得生存,否則死亡。
注意:所有對象的finalize()方法只會執行一次,如果對象面臨下一次回收,它的 finalize() 方法不會被再次執行,自救行動失敗
4.垃圾收集算法
4.1 標記 - 清除算法(Mark-Sweep)
介紹:標記-清除算法分爲兩個階段爲標記階段和清除階段。標記階段的任務是標記出所有需要被回收的對象,清除階段就是回收被標記的對象所佔用的空間
缺點:
1.效率問題:標記和清楚過程的效率都不高
2.空間問題:標記清除之後會產生大量不連續的內存碎片,導致當程序在以後的運行過程中需要分配較大對象時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
4.2 複製算法(Copying)
介紹:爲了解決標記 - 清除算法的缺陷,複製算法就被提了出來。它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用的內存空間一次清理掉,這樣一來就不容易出現內存碎片的問題
優點:每次都是對其中的一塊進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效
缺點:這種算法的代價是將內存縮小爲原來的一半,代價高昂
4.3 標記 - 整理算法(Mark-Compact)
介紹:爲了解決複製算法的缺陷,充分利用內存空間,提出了標記-整理算法。該算法標記階段和標記-清除算法一樣,但是在完成標記之後,它不是直接清理可回收對象,而是將存活對象都向一端移動,然後清理掉端邊界以外的內存。
4.4 分代收集算法(Generational Collection)
介紹:當前商業虛擬機的垃圾收集都採用分代收集算法,根據對象的存活週期的不同將內存劃分爲幾塊。一般是把 Java 堆分爲新生代和老年代,根據各個年代的特點採用最適合的收集算法。
備註:在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,選用複製算法。在老年代中因爲對象存活率搞,沒有額外空間對他進行分配擔保,就必須使用標記-清除算法或標記-整理算法來進行回收
注意:在堆區之外還有一個代就是永久代(Permanet Generation),它用來存儲class類、常量、方法描述等。對永久代的回收主要回收兩部分內容:廢棄常量和無用的類。
5.吞吐量
介紹:吞吐量就是 CPU 用於運行用戶代碼的時間與 CPU 總消耗時間的比例,而高吞吐量可以高效率地利用 CPU 時間,儘快地完成程序的運算任務,主要適合在後臺運算而不需要太多交互的任務
公式:吞吐量就是 CPU 用於運行用戶代碼的時間與 CPU 總消耗時間的比例,即 吞吐量 = 運行用戶代碼時間 / (運行用戶代碼時間 + 垃圾收集時間),虛擬機總共運行了 100 分鐘 ,其中垃圾收集花費 1 分鐘,那吞吐量就是 99%
6.垃圾收集器
介紹:GC收集算法是內存回收的方法論,垃圾收集器是內存回收的具體實現。Java虛擬機規範中對垃圾收集器應該如何實現並沒有任何規定,因此不同的廠商、不同版本的虛擬機所提供的垃圾收集器都可能會有很大差別,並且一般都會提供參數供用戶根據自己應用的特點和要求組合出各個年代所使用的收集器。
注意:如果兩個收集器之間存在連線,就說明它們可以搭配使用
1.Serial 收集器
介紹:新生代(年輕代)收集器,單線程進行垃圾回收,回收時會導致Stop The World,用戶進程停止,可以和Serial Old、CMS組合使用
算法:複製算法
線程:單線程收集器
優點:簡單而高效
缺點:它在進行垃圾收集時,必須暫停其他所有工作線程,直到它收集結束。比如電腦運行一小時就會暫停響應五分鐘
2.ParNew 收集器
介紹:ParNew 收集器就是 Serial 收集器的多線程版本,它也是一個新生代收集器。除了使用多線程進行垃圾收集外,其餘行爲包括 Serial 收集器可用的所有控制參數、收集算法(複製算法)、Stop The World、對象分配規則、回收策略等與 Serial 收集器完全相同,兩者共用了相當多的代碼。
算法:收集算法(複製算法)
線程:多線程收集器
優點:除了 Serial 收集器外,目前只有它能和CMS收集器(Concurrent Mark Sweep)配合工作。CPU 的數量增加時,它對於 GC 時系統資源的有效利用是很有好處的,它默認開啓的收集線程數與 CPU 的數量相同,在 CPU 非常多的情況下可使用 -XX:ParallerGCThreads 參數設置
缺點:ParNew 收集器在單CPU的環境中絕對不會有比 Serial 收集器有更好的效果,甚至由於存在線程交互的開銷,該收集器在通過超線程技術實現的兩個CPU的環境中都不能百分之百地保證可以超越
3.Parallel Scavenge 收集器
介紹:Parallel Scavenge 也是一個新生代收集器,看上去和 ParNew 收集器差不多,區別在於 Parallel Scavenge 的 目標是達到一個可控制的吞吐量,Parallel Scavenge 收集器提供了兩個參數用於精確控制吞吐量,分別是控制最大垃圾收集停頓時間的 -XX:MaxGCPauseMillis 參數及直接設置吞吐量大小的 -XX:GCTimeRatio 參數
算法:複製算法
線程:多線程收集器
關於吞吐量:
- -XX:MaxGCPauseMillis:設置大於 0 的毫秒數,收集器儘可能在該時間內完成垃圾回收
- -XX:GCTimeRatio:大於 0 小於 100 的整數,即垃圾回收時間佔總時間的比率,設置越小則希望垃圾回收所佔時間越小,CPU 能花更多的時間進行系統操作,提高吞吐量
- -XX:UseAdaptiveSizePolicy:參數開關,啓動後系統動態自適應調節各參數,如 -Xmn、-XX:SurvivorRatio 等參數,這是和 ParNew 收集器重要的區別
優點:Parallel Scavenge 通過控制吞吐量,從而達到可以高效率地利用 CPU 時間,儘快地完成程序的運算任務
缺點:使用多線程進行垃圾回收,回收時會導致Stop The World
4.Serial Old 收集器
介紹:Serial Old 收集器是 Serial 收集器的老年代版本,這個收集器主要意義也就是被 Client 模式下的虛擬機使用
線程:單線程收集器
算法:標記 - 整理算法,會對垃圾回收導致的內存碎片進行整理
兩大用途:
- 是在 JDK 1.5 及之前的版本中與 Parallel Scavenge 收集器搭配使用
- 作爲 CMS 收集器的後備預案,在併發收集發生 Concurrent Mode Failure 的時候使用
5.Parallel Old 收集器
介紹:Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,這個收集器是在 JDK 1.6 中才開始提供的,在此之前,新生代的 Parallel Scavenge 收集器一直處於比較尷尬的狀態。原因是,如果新生代選擇了 Parallel Scavenge 收集器,老年代除了 Serial Old 收集器外別無選擇。所以在 Parallel Old 誕生以後,“吞吐量優先”收集器終於有了比較名副其實的應用組合,在注重吞吐量以及CPU資源敏感的場合,都可以優先考慮Parallel Scavenge加Parallel Old收集器。
線程:多線程收集器
算法:標記 - 整理算法,會對垃圾回收導致的內存碎片進行整理
6.CMS 收集器
介紹:CMS 收集器是 一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的 Java 應用都集中在互聯網站或 B/S 系統的服務端上,這些應用都非常重視服務的響應速度
線程:
算法:標記 - 清除,可以通過設置參數在垃圾回收時進行內存碎片的整理
運作過程:
- 初始標記:仍然需要 Stop the World 用戶進程停頓,僅僅標記一下 GC Roots 能直接關聯到的對象,速度快
- 併發標記:進行 GC Roots Tracing 的過程,而重新標記階段則是爲了修改併發標記時間,因爲用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,也就是時間長。不發生用戶進程停頓
- 重新標記:仍然需要 Stop the World 用戶進程停頓,修正併發標記期間因用戶程序繼續運行導致標記變動的那一部分對象的標記記錄,停頓時間較長,但遠比並發標記時間短
- 併發清除:清除的同時用戶進程會導致新的垃圾,時間長,不發生用戶進程停頓
優點:併發收集、低停頓,因此CMS收集器也被稱爲併發低停頓收集器 。適合於對響應時間要求高的系統
缺點:
- 對CPU資源非常敏感
- CMS收集器無法處理浮動垃圾,即清除時用戶進程同時產生的垃圾,只能等到下次GC時回收
- 因爲是使用“標記-清除”算法,所以會產生大量碎片
6.G1 收集器
介紹:G1(Garbage-First)收集器是當今收集器技術發展最前沿的成果之一,它是一款面向服務端應用的垃圾收集器,HotSpot開發團隊賦予它的使命是(在比較長期的)未來可以替換掉JDK 1.5中發佈的CMS收集器
算法:標記 - 整理算法,也就是說不會產生空間碎片,對長時間運行的應用系統來說非常重要
特點:
- 因爲基於標記 - 整理算法實現的收集器,也就是說它不產生空間碎片,對於長時間運行的應用系統來說異常的重要 。
- 可以非常精確的控制停頓,技能讓使用者明確指定在一個長度爲 M 毫秒的時間片段內,消耗在垃圾收集上的時間也不得超過 N 毫秒。
- G1 收集器可以實現在基本不犧牲吞吐量的前提下完成低停頓的內存回收,這是由於它能夠極力地避免全區域的垃圾收集,之前收集器進行收集的範圍都是整個新生代或老年代,而 G1 將整個 Java 堆(包括新生代,老年代)劃分成多個大小固定的獨立區域,並且跟蹤這些區域裏面的垃圾堆積成度,在後臺維護一個優先級列表,酶促根據允許的收集時間,優先回收垃圾最多的區域。
總結
收集器 | 線程 | 新生代/老年代 | 算法 | 優先 | 適用場景 |
---|---|---|---|---|---|
Serial 收集器 | 串行 | 新生代 | 複製算法 | 響應速度優先 | 單CPU環境下的Client模式 |
ParNew 收集器 | 並行 | 新生代 | 複製算法 | 響應速度優先 | 多CPU環境時在Server模式下與CMS配合 |
Parallel Scavenge 收集器 | 並行 | 新生代 | 複製算法 | 吞吐量優先 | 在後臺運算而不需要太多交互的任務 |
Serial Old 收集器 | 串行 | 老年代 | 標記-整理 | 響應速度優先 | 單CPU環境下的Client模式、CMS的後備預案 |
Parallel Old 收集器 | 並行 | 老年代 | 標記-整理 | 吞吐量優先 | 在後臺運算而不需要太多交互的任務 |
CMS 收集器 | 併發 | 老年代 | 標記-清除 | 響應速度優先 | 集中在互聯網站或B/S系統服務端上的Java應用 |
G1 收集器 | 併發 | All | 標記-整理+複製算法 | 響應速度優先 | 面向服務端應用,將來替換CMS |