Java JVM詳解--通俗易懂教程
JVM:Java虛擬機器的簡稱。
談到JVM,通常會聊到三個問題:
1. 什麼時候觸發Java GC?
2. 對什麼東西進行Java GC?
3. 如何進行Java GC?
首先解決第一個問題:
1. 什麼時候觸發Java GC?
GC分為minor GC和Full GC。
Full GC:
- 年老代被寫滿;
- 持久代被寫滿
- System.gc被顯示呼叫
- 當有新物件生成的時候,如果申請eden空間失敗的話,就會觸發mino gc,對eden去進行gc處理,清除非存活的物件,並且把尚且存活的物件移到survival區裡面。經過一定次數的物件進入老年代的時候,如果老年代的剩餘空間放不下升到老年代的物件的時候,就會觸發full gc,利用
2. 對什麼東西進行GC ?對GC root搜尋不到,並且經過第一次標記,清除之後,仍然沒有復活的物件進行GC。
3. 做了什麼工作,怎麼進行GC?
主要做了清理物件,整理記憶體的工作,Java堆中主要是有新生代和年老代,他們採用不同的回收方式,例如新生代採用了標記複製的演算法(因為其生存時間比較短),新生代進行gc的時候,會把eden區存活的物件放到另外一個survival區域裡面,然後把eden區和另外一個survival區清除。而年老代採用了
垃圾收集有哪些演算法?
標記清除演算法:標記出需要回收的物件,標記完成後,統一回收所有被標記的物件。(兩個不足:效率不高:標記和清除都不高;空間問題:產生大量不連續的記憶體碎片,可能導致以後在分配較大物件的時候因無法找到連續的記憶體而觸發GC)
複製演算法:(新生代)比較適合物件存活率比較低的場景。應用於新生代,它主要是把記憶體分成兩個塊,每次只使用一塊,就像新生代一樣,有兩個區域:一個是eden區,一個是survival區。這樣也就解決了記憶體碎片化的問題。
標記整理演算法:(老年代)比較適合物件存活率比較高的場景,應用於老年代,將所有存活物件都移向另一端,然後直接清除掉端邊界外的記憶體。
如何判斷物件是否存活?
引用計數法:每個物件都有一個引用計數器,當物件被引用一次的時候,計數器+1,當物件引用失效的時候,計數值-1,實時性,當物件的引用計數器的值為0,則立刻回收,不能解決迴圈引用的問題。
可達性演算法分析:從GC Root作為起點開始搜尋,,那麼整個連通圖的物件都是存活的物件,對於GC Root無法到達的物件便成了垃圾回收的物件。(解決迴圈引用的問題)
常用的引用:
強引用>軟引用>弱引用>虛引用
強引用:GC永遠都不會回收的物件。記憶體空間不足時,寧願丟擲OutOfMemoryError。
軟引用:記憶體空間不足時會考慮回收它,空間足夠的時候不會
弱引用:不管記憶體空間夠不夠,都會回收它。
虛引用:不會影響生存時間,目的是能在這個物件被收集器回收時收到一個系統通知。
- 哪些物件可以作為GC Root物件
虛擬機器棧中的物件
方法區的靜態變數
方法區常量池的物件
- JVM垃圾收集器:
分為新生代收集器,老年代收集器。
新生代收集器:
在執行機制上JVM主要提供了序列GC(serial GC),並行GC(ParNew),並行回收GC(parallel scavenge)
Serial GC:在整個GC的過程中採用單執行緒的方式來進行垃圾回收,在回收過程中,必須停止其他所有的工作執行緒。 適用於單CPU,是client模式下預設的GC方式。
parNew :其實就是serialGC的多執行緒版本,除了使用多條執行緒來進行垃圾收集之外,其他行為跟serialGC差不多。,是server模式下預設使用的GC方式。能夠與CMS收集器配合工作。
Parallel scavenge:它跟parNew差不多,也是一個並行的多執行緒收集器。不過他的關注點跟其他收集器不一樣。Cms等收集器的關注點是儘可能的縮短垃圾收集是使用者執行緒的停頓時間,而parallel scavenge的目的是為了達到一個可控制的吞吐量。(也就是CPU用於執行使用者程式碼的時間與CPU總的消耗時間的比值)還有一個就是parallel scavenge有GC的自適應調節策略:我們可以通過一個引數,這個引數叫做userAdaptiveSizePolicy來讓虛擬機器幫我們動態的調整這些引數以提供最合適的停頓時間或者最大的吞吐量。
老年代收集器:
序列GC(serial old):他是serial的老年代版本,也是一個單執行緒收集器,使用標記整理演算法。
並行GC(parallel old):是parallel scavenge 的老年代版本。使用多執行緒和“標記-整理”演算法。
併發GC(CMS):獲取最短回收停頓時間為目標的收集器。
優點是:併發收集,低停頓
總結如下:
- 初始標記:標記一下GC root能夠直接關聯到的物件,速度很快
- 併發標記:進行GC root tracing的過程,耗時比較長,併發,
- 重新標記:為了修正併發標記期間因為使用者程式繼續執行而導致標記產生變動的那一部分物件的的標記記錄。
- 併發清除:耗時較長,併發。
- 這四個過程中,耗時最長的併發標記和併發清除過程收集器執行緒都可以與使用者執行緒一起工作,所以,整體上說:cms收集器的回收過程時與使用者執行緒一起工作的。
缺點有三:
- 對cpu資源敏感,因為併發標記和併發清除過程中會佔用cpu,公式是:
(cpu數量+3)/4,當cpu<4的時候,佔用了不少於25%的cpu資源。(增量式併發收集器:讓他們交替執行)
- 無法處理浮動垃圾:在第四個階段併發清理的時候,因為使用者執行緒還在執行,所以就會有新的垃圾產生,這些就是浮動垃圾,cms是無法在當次收集中處理掉他們的。(執行期間要預留記憶體給他們,不然會出現concurrent mode failure)
- 因為他是基於標記-整理演算法進行清除的,所以將會產生記憶體碎片化的問題,這將給大物件分配帶來麻煩。
成熟的收集器:
G1:G1收集器是當今收集器技術發展最前沿的成果,他是一款面向服務端應用的收集器。
過程如下:
- 初始標記:標記一下GC root能直接關聯到的物件。,需要停頓執行緒,耗時短。
- 併發標記:從gc root開始對堆中的物件進行分析,找出存活的物件,耗時長,但是可以和使用者執行緒併發執行。
- 最終標記:為了修正在併發標記過程中因使用者程式繼續運作而導致標記產生變動的那一部分標記記錄。這個階段需要停頓使用者執行緒。
- 篩選回收:首先對各個region的回收價值和成本進行排序,根據使用者所期望的停頓時間來指定回收計劃。
優勢:
- 並行與併發:G1能夠充分利用CPU,多核環境下硬體的優勢,使用多個CPU來縮短stop-the-world停頓時間。
- 分代收集:與其他收集器一樣,分代概念在G1仍然儲存。
- 空間整合:與cms的“標記-清除演算法”不同,G1整體上看來是基於“標記-整理的演算法”來實現的收集器,這意味著在G1執行期間不會產生記憶體空間碎片。。
- 可預測停頓:這是G1相對於CMS的一大優勢,降低低停頓時間是cms和G1共同的關注點,但是G1還能建立可預測的停頓時間模型,讓使用者明確指定在一個時間為M的時間片段內,消耗在垃圾收集的時間不得超過N毫秒。
垃圾收集器引數總結:
userSerialGC:虛擬機器執行在client模式下的預設值,開啟此開關之後,使用serial+serial old組合收集器。
userParNewGC:開啟此開關之後,使用parNew+serial Old組合收集器。
userConcMarkSweepGC:開啟此開關之後,使用parNew+CMS+serial Old組合垃圾收集器,serial old作為後備收集器使用。(concurrent mode failure)
userParallelGC:server模式下的預設值,parallel Scavenge+serial old組合垃圾收集
userParallelOldGC:parallel scavenge+parallel old收集器組合收集。
SurvivalRadio:eden:survival= 8
userAdaptiveSizePolicy:GC自適應調節策略,調整Java堆各區域的大小以及進入老年代的年齡。
handlePromotionFailure:是否允許分配擔保失敗
ParallelGCThreads:設定並行GC時進行記憶體回收的執行緒數。