JVM GC 垃圾收集梳理總結
什么是垃圾?
對于程序匯總分配的內存,當使用完成后,這部分內存就會成為垃圾,需要對其進行釋放,否則,這部分內存將無法被重復利用,最終造成內存泄漏。
什么是GC?
GC是一種自動的存儲管理機制。當一些被占用的內存不再需要時,就應該予以釋放。這種存儲資源管理,稱為垃圾回收。
對于java而言,是自動進行垃圾回收的。
如何發(fā)現(xiàn)垃圾?
既然要實現(xiàn)垃圾的自動回收,那么第一件事就是找到垃圾,那么如何發(fā)現(xiàn)垃圾呢?其實就是判斷這個對象是否存活。
常見的兩種方式判斷:
- 1)引用計數(shù)法(reference count)
- 2)根可達性算法(root searching)
名稱 | 實現(xiàn)思想 | 優(yōu)點 | 缺點 |
---|---|---|---|
引用計數(shù)法 | 給每個對象添加一個引用計數(shù)器,當存在一個引用時,就加1,當一個引用失效時,就減1。 | 判定效率高 | 1、無法解決相互引用、循環(huán)引用的問題。 2、存儲空間開銷:需要空間存儲計數(shù)器。 3、時間開銷:需要處理計數(shù)器的增加和減少。 |
根可達性算法 | 通過一系列名為”GC Roots”的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。 | 解決了循環(huán)引用的問題 | 實現(xiàn)較復雜,增加了計算成本。 |
引用計數(shù)法(reference count)的循環(huán)引用、相互引用: 沒有外部引用,但是本身的計數(shù)器又不為0。
根可達性算法 由于引用計數(shù)法存在的問題,所有主流的jvm都不使用引用計數(shù)法,而是采用根可達性算法。
如上圖,帶有GCRoots的對象表示正在被引用,而其他的對象雖然相互間有引用,但是沒有根節(jié)點,仍然會被刪除。
GCRoots對象: 哪些對象可以成為GCRoots呢?jvm中主要針對堆內的內存進行垃圾回收,而在虛擬機棧、本地方法棧和方法區(qū)內的對象則不會被回收,通常選擇這三個區(qū)域的對象作為GCRoots。
在jvm中主要有以下四種,在方法區(qū)存在兩種:
- 1)虛擬機棧中引用的對象:虛擬機棧幀中的局部變量表所引用的對象。
- 2)本地方法棧中引用的對象:JNI (Native方法)引用的對象。
- 3)方法區(qū)中類靜態(tài)和常量對象:靜態(tài)變量和常量引用的對象。
以下圖來展示在JVM內存模型(JMM)的GCRoots:
在根可達性算法中,所有的引用都是強引用,下面具體分析下jvm中的四種引用。
四種引用: 參考:分享JVM 的四種引用方式
名稱 | 定義 | 特點 | 回收 |
---|---|---|---|
強引用 | 強引用就是引用了通過new 的方式創(chuàng)建的對象。是指創(chuàng)建一個對象并把這個對象賦給一個引用變量;在root搜索算法的里面,說的引用都指的是強引用關系。 | GC時,永遠不會被回收,導致OOM的主要原因 | 1、引用消失(比如方法執(zhí)行完) 2、將引用變量設置為null |
軟引用 | 如果一個對象具有軟引用,內存空間足夠,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。軟引用可用來實現(xiàn)內存敏感的高速緩存,比如網頁緩存、圖片緩存等。使用軟引用能防止內存泄露,增強程序的健壯性。Java中,用SoftRefrence表示弱引用。 | 內存不足時(自動觸發(fā)GC),會被回收 | 內存不足時,觸發(fā)自動回收 |
弱引用 | 引用也是用來描述非必需對象的,當JVM進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯(lián)的對象。在java中,用java.lang.ref.WeakReference類來表示 | 無論內存是否充足,只要進行GC,都會被回收 | 只要進行GC,都會被回收 |
虛引用 | 虛引用和前面的軟引用、弱引用不同,它并不影響對象的生命周期。在java中用java.lang.ref.PhantomReference類表示。如果一個對象與虛引用關聯(lián),則跟沒有引用與之關聯(lián)一樣,在任何時候都可能被垃圾回收器回收。 要注意的是,虛引用必須和引用隊列關聯(lián)使用,設置虛引用關聯(lián)的唯一目的,就是在這個對象被收集器回收的時候收到一個系統(tǒng)通知或者后續(xù)添加進一步的處理。Java技術允許使用finalize()方法在垃圾收集器將對象從內存中清除之前做必要的清理工作。 | 如同虛設,和沒有引用沒什么區(qū)別 | 任何時候都可能被回收 |
垃圾如何處理?
我們通過上面學到的根可達性算法可以發(fā)現(xiàn)垃圾的所在,那么jvm是如何進行垃圾回收的呢?通過jvm提供的垃圾收集器(GC) 。
目前有以下種類的垃圾收集器,其中虛線表示垃圾收集器可以進行組合使用:
常見的垃圾收集算法
標記清除(mark sweep) :位置不連續(xù) 產生碎片 效率偏低(兩遍掃描) 拷貝算法 (copying) :沒有碎片,浪費空間 標記壓縮(mark compact) :沒有碎片,效率偏低(兩遍掃描,指針需要調整)
標記清除(mark sweep)
顧名思義,標記清除算法分為兩個階段標記(mark) 和清除(sweep) 。
標記: Collector從引用根結點開始遍歷,標記所有被引用的對象。一般是在對象的Header中記錄為可達對象。
清除: Collector對堆內存從頭到尾進行線性的遍歷,如果發(fā)現(xiàn)某個對象在其Header中沒有標記為可達對象,則將其回收。
對所有能找到根節(jié)點引用的內存空間進行標記,清除沒有找到根節(jié)點的內存空間,其大概實現(xiàn)過程如下:
缺點 :
- 1)STW(stop the word),回收時,應用掛起。
- 2)內存越大,效率越多,需要掃描的時間越長。
- 3)內存碎片化,會導致無法裝下新申請的對象,整體內存是足夠的,但并非連續(xù)的。
拷貝算法 (copying)
拷貝算法將內存空間劃分為兩個區(qū)間,在任意時間點,所有動態(tài)分配的對象都只能分配在其中一個區(qū)間(稱為活動區(qū)間),而另外一個區(qū)間(稱為空閑區(qū)間)則是空閑的。
當活動區(qū)間的內存占滿時,接下來GC線程會將活動區(qū)間內的存活對象,全部復制到空閑區(qū)間,且嚴格按照內存地址依次排列,與此同時,GC線程將更新存活對象的內存引用地址指向新的內存地址。
其大概過程如下圖所示:
缺點 浪費內存,并且存活對象越多的情況下,效率越低。
標記壓縮/標記整理(mark compact)
標記過程仍然和標記-清除一樣,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理端邊界以外的內存。
實現(xiàn)過程大概如下:
缺點 效率不高,除了要標記存活對象,還要整理存活對象的引用地址,效率低于復制算法。
總結 以上三種算法都是根據根可達性算法實現(xiàn)的。當開始GC時,三種算法都會造成STW(stop the world)。
JVM的內存模型如何實現(xiàn)垃圾回收?分代模型
文章前面介紹了簡單很多種垃圾收集器,不同的垃圾收集器有不同的分代模型:
- 1)除Epsilon、ZGC、Shenandoah之外的GC都是使用邏輯分代模型
- 2)G1是邏輯分代,物理不分代
- 3)除1)2)之外不僅邏輯分代,而且物理分代
分代模型:
上圖中的分代模型有些需要特別關注的點:
- 1)整個分代模型的組成:新生代 + 老年代 + 永久代(jdk1.7)/元空間(jdk1.8) 永久代、元空間:Class 永久代:必須指定大小限制 元空間:可以設置大小,也可以不設置,無上限(受限于物理內存) 字符串常量池: jdk1.7 - 永久代,jdk1.8 - 堆 MethodArea(方法區(qū))邏輯概念:永久代、元數(shù)據
- 2)新生代: Eden + 2個suvivor區(qū) YGC回收之后,大多數(shù)的對象會被回收,活著的進入s0 再次YGC,活著的對象eden + s0 -> s1 再次YGC,活著的對象eden + s1 -> s0 年齡足夠 -> 老年代 (通常15,對象頭Mark Word 的age只有4bit,最大是15 、CMS 6) suvivor區(qū)裝不下 -> 老年代
- 3)老年代 老年代滿了FGC Full GC(STW)
GC Tuning 盡量減少FGC MinorGC = YGC MajorGC = FGC
到此這篇關于JVM GC 垃圾收集梳理總結的文章就介紹到這了,更多相關JVM GC 垃圾收集 內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
mybatis中association和collection的使用與區(qū)別
在 MyBatis 中,<association>?和?<collection>?是用于配置結果映射中關聯(lián)關系的兩個元素,本文主要介紹了mybatis中<association>和<collection>的使用與區(qū)別,具有一定的參考價值,感興趣的可以了解一下2024-01-01SpringBoot整合Redis正確的實現(xiàn)分布式鎖的示例代碼
這篇文章主要介紹了SpringBoot整合Redis正確的實現(xiàn)分布式鎖的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07Java實現(xiàn)企業(yè)發(fā)放的獎金根據利潤提成問題
這篇文章主要介紹了請利用數(shù)軸來分界,定位。注意定義時需把獎金定義成長整型,需要的朋友可以參考下2017-02-02搜索一文入門ElasticSearch(節(jié)點 分片 CRUD 倒排索引 分詞)
這篇文章主要為大家介紹了搜索一文入門ElasticSearch(節(jié)點 分片 CRUD 倒排索引 分詞)的基礎詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03解決問題:Failed to execute goal org.apache.m
這篇文章主要給大家介紹了關于解決問題:Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources的相關資料,文中將解決的辦法介紹的非常詳細,需要的朋友可以參考下2023-03-03