【JVM】一文掌握JVM垃圾回收机制
作為Java程序員,除了業務邏輯以外,隨著更深入的了解,都無法避免的會接觸到JVM以及垃圾回收相關知識。JVM調優是一個聽起來很可怕,實際上很簡單的事。
感到可怕,是因為垃圾回收相關機制都在JVM的C++層實現,我們在Java開發中看不見摸不著;而實際很簡單,是因為它說到底,也只是JVM替我們實現的垃圾對象回收機制,也是普通的程序代碼,只要理解了垃圾回收器的底層設計思想,掌握JVM調優并非難事!
一、JVM內存模型
元數據區:JDK8之前是方法區。存放虛擬機加載的:類型信息,域(Field)信息,方法(Method)信息,常量,靜態變量,即時編譯器編譯后的代碼緩存
虛擬機棧:虛擬機棧中保存了每一次方法調用的棧幀信息,棧幀中包含以下信息:
- 局部變量表:保存函數 (即方法) 的局部變量
- 操作數棧:保存計算過程中的結果,即臨時變量
- 動態鏈接:指向方法區的運行時常量池。字節碼中的方法調用指令以常量池中指向方法的符號引用為參數。
- 方法的返回地址
本地方法棧:和虛擬機棧功能上類似,它管理了native方法的一些執行細節,而虛擬機棧管理的是Java方法的執行細節。
程序計數器:程序計數器記錄線程執行的字節碼行號,如果當前線程正在運行native方法則為空。每個線程都有自己的計數器
堆:JVM中產生的實例對象的存儲位置
所謂的垃圾回收,主要就是回收JVM中堆內存的區域
二、垃圾定義
- 引用計數(ReferenceCount):存在循環引用的問題,漏掉循環引用的垃圾
- 根可達算法(RootSearching):判斷對象是否可通過引用尋到JVM的根節點,不能則是垃圾
三、垃圾回收算法
- 標記清除(mark sweep) - 位置不連續 產生碎片 效率偏低(兩遍掃描)
- 拷貝算法 (copying) - 沒有碎片,浪費空間
- 標記壓縮(mark compact) - 沒有碎片,效率偏低(兩遍掃描,指針需要調整)
四、垃圾回收器
通過以上三種算法的排列組合,產生了各種各樣的垃圾回收器
堆內存邏輯分區
常見垃圾回收器
? Serial:單線程STW垃圾回收器,工作在年輕代。采用拷貝算法
? Serial Old:單線程STW垃圾回收器,工作在老年代。采用標記清除加壓縮算法
? Parallel Scavenge:并行垃圾回收,工作在年輕代。采用拷貝算法
? Parallel Old:并行垃圾回收,工作在老年代。采用標記清除加壓縮算法
? ParNew:并行垃圾回收,工作在年輕代。專門配合CMS使用
? CMS(Concurrent Mark-Sweep):并發標記清除,工作在老年代,采用標記清除算法。
? G1(Garbage First):垃圾優先算法,采用拷貝算法
? ZGC(Z Garbage Collector):一種可伸縮的低延遲垃圾回收器,旨在處理TB級別的堆,同時保持低毫秒級別的停頓時間。它通過使用讀屏障和染色指針來實現這一點,并且在垃圾回收過程中幾乎不需要暫停應用線程
? Shenandoah GC:是一種旨在實現低停頓時間的垃圾回收器,它通過并發的方式來回收內存。Shenandoah的目標是減少停頓時間,而不是優化吞吐量,適用于需要大內存和低延遲的應用
垃圾升級過程
- 新創建對象產生在eden區
- ygc觸發,把eden區和s0區不是垃圾的對象復制到s1區,并對非垃圾對象的頭部的分代年齡加一,然后清除eden區和s0區
- ygc觸發,把eden區和s1區不是垃圾的對象復制到s0區,并對非垃圾對象的頭部的分代年齡加一,然后清除eden區和s1區
- 當對象頭記錄的分代年齡達到15(默認最大分代年齡)時,jvm將把他從年輕代升級到老年代
eden區和s0、s1的默認比例是8:1:1,可通過參數-XX:SurvivorRatio配置
對象頭的年齡可通過-XX:MaxTenuringThreshold參數配置,但由于對象頭中只用4個比特位存儲分代年齡,因此它的區間是0-15
CMS垃圾回收器
CMS是用于回收老年代的垃圾回收器,它采用的是標記清除算法。CMS的誕生的目的在于提供在多核環境下的并發處理中大型堆(MB~GB)垃圾的能力
特點
- 并發收集:CMS的主要特點是它允許垃圾回收線程與應用程序線程同時運行。這減少了應用程序的停頓時間,特別是在長時間運行的老年代垃圾回收過程中。
- 低停頓時間:由于并發執行,CMS旨在減少垃圾回收引起的停頓時間,這對于延遲敏感的應用程序非常重要。
- 存在內存碎片:CMS通常不執行堆壓縮,這意味著它不會重新安排存活對象來消除空閑空間之間的碎片。這可以減少停頓時間,但可能導致更多的內存碎片。
- CPU資源密集型:CMS需要更多的CPU資源來執行并發的垃圾回收。在多核處理器上,這通常不是問題,但在CPU資源受限的環境中,可能會影響應用程序的性能。
- 并發模式失敗:在極端情況下,如果老年代在CMS回收過程中被填滿,會發生“并發模式失敗”。這時,JVM會退回到傳統的完全停頓式垃圾回收,以清理老年代。
- 適用于中到大型堆:CMS適合于中到大型的堆,尤其是在有足夠CPU資源和對停頓時間敏感的應用場景中。
- 需要調優:為了獲得最佳性能,CMS可能需要通過JVM參數進行調優,如設置堆的大小、老年代的大小、并發線程數等。
垃圾回收過程
- 初始標記:找到根上的垃圾,會有非常短暫的STW
- 并發標記:標記垃圾,這一步可能產生漏標(掃描完不是垃圾之后,突然失去引用變成了垃圾),也可能產生多標(掃描完事垃圾之后,突然重新被引用變成不是垃圾)
- 重新標記:重新標記的目的是糾正上一步所產生的錯誤標記,會有時間不算很長的STW
- 并發清理:清理前面步驟所標記出來的垃圾
三色標記算法
作用于并發標記階段
對象標記為黑白灰三個顏色,記錄當前掃描標記的位置。
- 黑色:自己已經被標記,自己的子引用也都標記完成
- 白色:沒有遍歷到的節點
- 灰色:自己已經被標記,自己的子引用還沒被全部標記完成
三色標記的bug
由于并發標記是與用戶線程并行的,所以在并發標記的過程中對象的引用是可能發生變化的,所以可能會產生多標和漏標。并且重新標記為了減少STW的時間不會再標記黑色對象,而是掃描灰色對象的直接引用
- 多標:會導致產生浮動垃圾,需要在下一次判斷引用再回收,無大礙
- 漏標:會導致不應該被回收的對象被回收,問題嚴重
如上圖:在并發標記的過程中,同時產生這兩種情況時就會發生回收錯誤問題:A和C斷開了引用,A又引用了D。
- 對于對象C:應該回收的對象現在是黑色,留了下來
- 對于對象D:被引用了但還是白色,由于重新標記時不會再掃描黑色對象,這樣會導致對象D被當作垃圾而回收,產生嚴重bug
CMS對于三色標記的錯標處理
CMS的處理方式是Increment Updater(增量更新),即當已經被掃描完的黑色對象如果產生了新的引用,則把自己標記為灰色,等待下次掃描重新標記。
但在上述的多標案例中,CMS存在卻依然并發標記Bug,如下時序圖
當兩個垃圾回收線程m1和m3加上一個業務線程m2同時標記一個對象時,m3認為應該標灰,但m1認為應該標黑,如果最終m1的標記覆蓋了m3的標記,那么對象的顏色標記錯誤,它下面新增的引用也不會被掃描到
CMS對于這個嚴重的bug的解決方案是,在重新標記階段重新掃描時,必須從頭掃描一遍,這樣就增加了STW的時間
G1垃圾回收器
G1(Garbage-First)垃圾回收器是Java虛擬機(JVM)的一個高級垃圾回收器,旨在為具有大內存的多處理器機器提供高吞吐量和低延遲。G1垃圾回收器的主要特點包括:
特點
- 分區堆結構:G1將堆內存分割成多個大小相等的區域(Region),這些區域可以被劃分為Eden區、Survivor區或Old區。這種分區方法有助于更有效地管理堆空間。
- 并發和并行處理:G1結合了并發和并行的垃圾回收機制,以優化性能和減少停頓時間。
- 可預測的停頓時間:G1的一個關鍵目標是提供可預測的停頓時間,允許用戶指定期望的停頓時間目標(例如,不超過50毫秒),G1將盡量在這個時間范圍內完成垃圾回收。
- 增量式清理:G1通過逐步清理堆中的區域來管理垃圾回收,這有助于控制停頓時間。
- 記憶集(RSet):G1使用記憶集來跟蹤跨區域引用的對象,這有助于在垃圾回收時快速確定哪些對象是存活的。
- 混合收集:G1可以同時回收Young和Old區域。在進行混合收集時,G1會根據需要和停頓時間目標選擇性地回收一部分Old區域。
- 高效的大對象處理:G1能夠更有效地處理大對象,因為它可以跨多個區域分配這些對象。
- 自適應調整:G1會根據應用程序的行為和指定的停頓時間目標自動調整堆占用和回收策略。
- 適用于大堆:G1特別適合于大堆(多GB)的應用,因為它能夠更好地管理大內存并保持合理的停頓時間。
分區算法(Region)
G1的物理分區從分代變成了分區(Region),邏輯上分代,物理上則取消了分代,把堆整體劃分成了多個(2048)相同大小的小格子(Region)
其中,每個Region的大小可通過-XX:G1HeapRegionSize設定,取值范圍為1-32MB,且必須為2的N次冪,即只能為2,4,8,16,32這五個數
每一個Region都可以根據需要充當新生代的Eden區、S區(G1取消了S0和S1,只使用一個Survivor區)或者老年代。在一般的垃圾收集中對于堆中的大對象,默認直接會被分配到老年代,但是如果它是一個短期存在的大對象,就會對垃圾收集器造成負面影響。為了解決這個問題,G1劃分了一個Humongous區,它用來專門存放大對象。如果一個H區裝不下一個大對象,那么G1會尋找連續的H區來存儲。為了能找到連續的H區,有時候不得不啟動Full GC。 G1的大多數行為都把H區作為老年代的一部分來看待。當一個對象的大小超過了一個Region容量的一半,即被認為是大對象。
雖然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,而是一系列區域(不需要連續,邏輯連續即可)的動態集合。由于G1這種基于Region回收的方式,可以預測停頓時間。G1會根據每個Region里面垃圾“價值”的大小,在后臺維護一個優先級列表,每次根據用戶設定的允許收集停頓的時間(-XX:MaxGCPauseMillis,默認為200毫秒)優先處理價值收益最大的Region。
垃圾回收過程
G1采用的復制(copying)算法進行回收
- 初始標記:僅僅只是標記一下GC Roots能直接關聯到的對象,并且修改TAMS指針的值,讓下一階段用戶線程并發運行時,能夠在Region上正確的分配對象。這個階段需要STW,耗時很短,而且是借用MinorGC(上一輪垃圾回收時觸發GC)時候同步完成的。
- 并發標記:從GC Roots 開始對堆中的對象進行可達性分析,遞歸掃描整個堆里的對象,這個過程耗時較長,但是是與用戶線程并發執行的。對象掃描完之后還需要重新處理STAB記錄下的在并發時有引用變動的對象。
- 最終標記:這個階段也需要STW,用于處理并發階段結束后仍然遺留下來的最后少量的STAB記錄。
- 篩選回收:負責更新Region的統計數據,對各個Region的回收價值和成本排序,根據用戶期望的停頓時間來執行回收計劃,然后把決定回收的Region里的存活對象復制到空的Region,然后清空舊Region的空間。由于涉及到對象的移動,所以這個階段也是需要STW的。
從上述可以看出,除了并發標記,其他階段都是需要STW的,G1收集器不單單是追求低延遲的收集器,也衡量了吞吐量,所以在延遲和吞吐量之間做了一個權衡。
G1對于三色標記的錯標處理
從上述過程可以看出G1的處理方式是SATB(snapshot at the begining),即在并發標記中,如果出現引用的變更,G1的垃圾回收器會記錄在SATB中,每次線程切回來進行垃圾回收時,先讀取SATB中的記錄。
RememberedSet
簡稱RSet,記錄了其他Region的對象到本Region的引用,使得垃圾回收器不需要掃描整個堆找到誰引用了當前分區的對象,只需掃描RSet即可
更多技術干貨,歡迎關注我!
總結
以上是生活随笔為你收集整理的【JVM】一文掌握JVM垃圾回收机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Codeforces 918(div4)
- 下一篇: java信息管理系统总结_java实现科