Java GC原理简单讲解
了解GC,首先需要了解jvm,之前寫過關于虛擬機的文章《Java虛擬機簡單介紹》
其次就是了解設置jvm內存參數和設置方法,這也寫過文章《jvm 常用調試工具和設置jvm GC方法和指令》?PermSize 永久代 NewSize ?新生代 Xms Xmx 堆 Xss 棧
再者就是了解jvm中有幾種GC以及啟動方法,也有寫過文章《java 基礎知識部分提煉》;但是為什么jvm中有好幾款GC呢?你可以這樣思考:如果你是jvm的開發者,jvm中要嵌入自己的GC算法。你開發出來的jvm可能需要跑在單cpu的PC上,也有可能跑在多CPU集群的服務器上,也有可能被用來開發大型的應用,也有可能被用來開發單線程的命令行程序等等。那么不同的平臺或者開發的軟件類別不能一概而論的使用同一款,或者同一種算法的GC。那么這樣太粗糙了!所以JVM中嵌入了幾款不同GC,用戶可以根據自己的需要強行設置自己的jvm GC方式。注意:安裝了jre,跑了好幾款不同的Java應用,那么就對應好幾個jvm,那么要指定JVM指配GC方式。
在pc上cmd中設置GC類型:
爆出沒有啟動的jvm,所以需要有運行的jvm,指定jvm設置GC類型。
在Tomcat 服務器上設置GC類型:?在tomcat的catalina.bat中設置啟動參數
-XX:+/- 解釋:-XX: 是設置jvm命令符,后面的加號或者減號是表示增加或者去掉的意思。
今天的文章就是說說不同GC的工作大致原理。
基本的GC回收算法:
1. 引用計數(Reference Counting)?
比較古老的回收算法。原理是此對象有一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只用收集計數為0的對象。此算法最致命的是無法處理循環引用的問題。?
2. 標記-清除(Mark-Sweep)?
此算法執行分兩階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法需要暫停整個應用,同時,會產生內存碎片。?
3. 復制(Copying)?
此算法把內存空間劃為兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象復制到另外一個區域中。次算法每次只處理正在使用中的對象,因此復制成本比較小,同時復制過去以后還能進行相應的內存整理,不過出現“碎片”問題。當然,此算法的缺點也是很明顯的,就是需要兩倍內存空間。?
4. 標記-整理(Mark-Compact)?
此算法結合了 “標記-清除”和“復制”兩個算法的優點。也是分兩階段,第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個堆,把清除未標記對象并且把存活對象 “壓縮”到堆的其中一塊,按順序排放。此算法避免了“標記-清除”的碎片問題,同時也避免了“復制”算法的空間問題。?
5. 增量收集(Incremental Collecting)?
實施垃圾回收算法,即:在應用進行的同時進行垃圾回收。不知道什么原因JDK5.0中的收集器沒有使用這種算法的。?
6. 分代(Generational Collecting)?
基于對對象生命周期分析后得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同生命周期的對象使用不同的算法(上述方式中的一個)進行回收。現在的垃圾回收器(從J2SE1.2開始)都是使用此算法的。?
1. Young(年輕代)?
年輕代分三個區。一個Eden區,兩個 Survivor區。大部分對象在Eden區中生成。當Eden區滿時,還存活的對象將被復制到Survivor區(兩個中的一個),當這個 Survivor區滿時,此區的存活對象將被復制到另外一個Survivor區,當這個Survivor去也滿了的時候,從第一個Survivor區復制過來的并且此時還存活的對象,將被復制“年老區(Tenured)”。需要注意,Survivor的兩個區是對稱的,沒先后關系,所以同一個區中可能同時存在從Eden復制過來對象,和從前一個Survivor復制過來的對象,而復制到年老區的只有從第一個Survivor去過來的對象。而且,Survivor區總有一個是空的。?
2. Tenured(年老代)?
年老代存放從年輕代存活的對象。一般來說年老代存放的都是生命期較長的對象。?
3. Perm(持久代)?
用于存放靜態文件,如今Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=<N>進行設置。?
試想,如果是你寫一個GC回收算法,你會思考怎么是實現呢?對于我,我認為我肯定是和“標記-清除”算法思維一樣。純粹的計數法太過于簡單,搞不好數據就清除掉了。那么為什么會和“標記-清除”思維一樣呢?Java里面所有的類都是繼承于object,依次類推開發者每一次在內存中創建對象都是在以object的為根節點的父節點上創建。那么整個內存中所有的對象在數據關聯方面就是一個很大的樹結構。唯一做到的就是記錄對象生存周期,那么只要記錄比較一個父節點不可到達,或者聲明結束,那么下面的子樹結構都標記刪除。但是這樣做貌似在記錄上很復雜。當然也可以在某一個子樹結構執行完,不可到達的時候,將樹的其他整體分支,copy出來,新建的整體樹相當于砍去不可到達的結構之后的大樹,每一次都發現有不可到達的就copy一次整體的樹形結構。當內存不足的時候除了最后創建的整體樹結構保留之外其他的都刪除。但是這樣貌似加大了內存消耗,內存消耗極快呀!你有好的思維發散嗎?
對于不同的jvm(JVM種類多達幾十種),GC一般大同小異,下面介紹的幾種GC是傳統較為標準的OracleJVM -- hotspot VM垃圾回收器。要知道很多公司只做jvm,有的jvm支持iOS系統,.net框架等(對的,有的jvm是針對支持iOS和.net)。針對不同平臺如android、pc、服務器等有不同種類的jvm。
GC的種類:
新生代的GC:
新生代通常存活時間較短,因此基于Copying算法來進行回收,所謂Copying算法就是掃描出存活的對象,并復制到一塊新的完全未使用的空間中,對應于新生代,就是在Eden和FromSpace或ToSpace之間copy。新生代采用空閑指針的方式來控制GC觸發,指針保持最后一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用于檢查空間是否足夠,不夠就觸發GC。當連續分配對象時,對象會逐漸從eden到survivor,最后到舊生代,
用javavisualVM來查看,能明顯觀察到新生代滿了后,會把對象轉移到舊生代,然后清空繼續裝載,當舊生代也滿了后,就會報outofmemory的異常,如下圖所示:
在執行機制上JVM提供了串行GC(SerialGC)、并行回收GC(ParallelScavenge)和并行GC(ParNew)
1)串行GC
在整個掃描和復制過程采用單線程的方式來進行,適用于單CPU、新生代空間較小及對暫停時間要求不是非常高的應用上,是client級別默認的GC方式,可以通過-XX:+UseSerialGC來強制指定
2)并行回收GC
在整個掃描和復制過程采用多線程的方式來進行,適用于多CPU、對暫停時間要求較短的應用上,是server級別默認采用的GC方式,可用-XX:+UseParallelGC來強制指定,用-XX:ParallelGCThreads=4來指定線程數
3)并行GC
與舊生代的并發GC配合使用
舊生代的GC:
舊生代與新生代不同,對象存活的時間比較長,比較穩定,因此采用標記(Mark)算法來進行回收,所謂標記就是掃描出存活的對象,然后再進行回收未被標記的對象,回收后對用空出的空間要么進行合并,要么標記出來便于下次進行分配,總之就是要減少內存碎片帶來的效率損耗。在執行機制上JVM提供了串行GC(SerialMSC)、并行GC(parallelMSC)和并發GC(CMS),具體算法細節還有待進一步深入研究。
? ? ? ? ?
? ? ? ?G1回收器:
1、并行于并發:G1能充分利用CPU、多核環境下的硬件優勢,使用多個CPU(CPU或者CPU核心)來縮短stop-The-World停頓時間。部分其他收集器原本需要停頓Java線程執行的GC動作,G1收集器仍然可以通過并發的方式讓java程序繼續執行。
2、分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但是還是保留了分代的概念。它能夠采用不同的方式去處理新創建的對象和已經存活了一段時間,熬過多次GC的舊對象以獲取更好的收集效果。
3、空間整合:與CMS的“標記--清理”算法不同,G1從整體來看是基于“標記整理”算法實現的收集器;從局部上來看是基于“復制”算法實現的。
4、可預測的停頓:這是G1相對于CMS的另一個大優勢,降低停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度為M毫秒的時間片段內,
5、G1運作步驟:1、初始標記;2、并發標記;3、最終標記;4、篩選回收
總結
以上是生活随笔為你收集整理的Java GC原理简单讲解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 苹果8plus是oled屏吗
- 下一篇: java 线程池 -- (Java并发)