根可达算法的根_我的JVM(六):GC的基础概念以及GC算法
一、概述
垃圾收集Garbage Collection通常被稱為GC,但是GC一般也指Garbage Collecting(垃圾回收這個動作)或Garbage Collector(垃圾回收器),這些都是是JVM知識體系中非常重要的知識,也是程序員必須要掌握的技能,本文將詳細講述Java垃圾回收的概念機制以及核心算法。
二、分析
1. 什么是垃圾
我們所說的垃圾是指沒有任何引用的一個對象或者多個對象(這多個對象相互引用,但是沒有一個與主對象掛鉤,也就是根可達算法(下文會講)無法找到這其中任何一個對象)。
我們再來來熟悉兩個概念:
(1) 內存泄露:內存泄露是指有的內存地址太過碎片化而無法被利用,我們都知道一個對象創建的時候開辟的內存空間是連續的,所以太過碎片化的內存空間就沒辦法利用。內存泄露多了也會導致內存溢出。
(2) 內存溢出:內存溢出是指內存已經裝滿了,無法再裝下更多的對象了。
C和C++都是需要開發者用代碼手動回收內存的:C語言用free關鍵字來回收內存,C++用的是delete。但是手動回收內存容易出現兩種類型問題:忘記回收(容易引發OOM內存泄露)和多次回收。
后來誕生的java、python等都是自帶了垃圾回收器的語音,開發者只管創建對象,對象的銷毀不需要手動處理,由專門的垃圾回收器進行回收。
2. 如何定位垃圾
常見的方式有兩種:
(1) 引用計數(Reference Count):每當一塊內存被一個對象引用,那么計數就+1,當沒有對象指向時,計數為0,就表示這塊內存可以被回收了,如下圖:
但是引用計數沒辦法解決垃圾之間互相引用的情況,當幾塊內存都沒有外部引用,但是這幾塊內存之間相互引用的時候,這幾塊內存也應該視為垃圾,但是引用計數卻不為0,如下圖:
(2) 根可達算法(Root Searching):當程序運行時,將根對象取出,由根對象出發往下查找,最終找不到的對象,都視為無法由根對象找到,也就是說找不到的對象就都視為垃圾。如下圖:
那么哪些對象是根對象呢?主要包含:JVM stack,native method stack,run-time constant pool(運行常量池里的對象),static references in method area(方法區里的靜態引用),Clazz等。
3. 常見的垃圾回收算法
主要包含以下3種:
(1) 標記清除(mark sweep):就是將找到的垃圾標記出來,然后直接清除掉。但是這種方式有一個嚴重的毛病,會使得內存變得碎片化,也就是有多個不連續的內存。
(2) 拷貝算法(copying):這種方式的做法就是將內存平分成兩塊,在使用的過程中只能在其中一塊內存里創建對象,當需要垃圾回收時,將有對象的內存全部復制到另一邊,并且將當前區域全部清除。這種方式解決了內存碎片化的問題,但是卻浪費了空間,因為每次只能利用一半。
(3) 標記壓縮(mark compact):這種方式就是在清理垃圾的同時,將同類型的內存空間放置在一起,也就是說在清理的同時進行空間整理,并且多線程時還需要進行線程同步,所以這種方式明顯的缺點就是效率偏低。
常用的垃圾回收算法就是這3種或者這3種方式的組合。
4. JVM內存分代模型(用于分代垃圾回收算法)
JVM的內存模型是由垃圾回收器決定的,一般分為分代模型和不分代模型,兩種內存模型不一樣。分代垃圾回收的內存模型如下圖:
分代模型在邏輯上分代,在物理層面也就是內存中也是分成了new(新生代)和old(老年代)兩個大區域。新生代區又詳細分為eden(伊甸園)、survivor1和survivor2。new(年輕代)的對象有兩大特點:大量產生;大量回收(大多數情況下,一次回收90%的對象)。所以根據new年輕代的特點,采用的算法是Copying算法;而Old老年代則是采用標記壓縮(mark compact)算法,以此保證內存的連續性 。
值得一提的是,new新生代和old老年代的比例默認是1:2。但是這個比例也是JVM調優中可以調節的參數,所以上圖寫的1:3。eden和survivor1,survivor2的默認比例是8:1:1,也是可以調整的。
為了方便對于分區的理解,我們由一個對象的創建到回收進行分析,分區內變化如下:
對象分配過程如下:
過程分析:
(1) 當我們new出一個對象,JVM會首先嘗試往棧上分配,如果能夠分配得下,就分配到棧上分配到棧上的對象有好處就是不需要GC進行管理,什么時候不需要用到此對象了,將對象出棧就可以了。但是分配到棧上的對象是有要求的:第一,對象比較小,因為棧空間本來就不夠大;第二,對象比較簡答。
(2) 如果棧上分配不下,我們就判斷這個對象是不是夠大,如果足夠大就直接放在老年代區,在老年代區的對象經過一次全量垃圾回收FGC后,才有可能被回收掉。
(3) 如果如果棧上分配不下并且對象不大,就會判斷對象能否被存在線程本地分配緩沖區-TLAB(Thread Local Allocation Buffer)。但是不管放不放得下,都是放在新生代區的伊甸區eden。 但是因為堆是共享的,多個線程可以同時創建對象就可能會爭奪同一塊內存區域,所以為了保證線程安全,Eden區又被分配成一個個線程本地分配緩沖區,這個TLAB是線程私有的,每個線程都有自己的TLAB,避免了多線程環境下使用同步技術帶來的性能損耗。
(4) 伊甸區eden的對象在經過一次GC后,如果被回收掉了,那就結束了生命周期。
(5) 伊甸區eden的對象在經過一次GC后,如果沒有被回收掉,JVM在整個new新生代區都采用Copying(拷貝算法),將不是垃圾的對象拷貝到幸存者區survivor1,對比上面的堆內存邏輯分區圖。幸存者區survivor1中的對象再經過一次GC后如果對象還存活,那么就拷貝到幸存者區survivor2并且清理掉幸存者區survivor1中的所有對象,再有GC就反復這個操作,直到對象的分代年齡達到了移到老年代的界限(一般分代垃圾回收器默認是15,CMS默認是6),就會被移到老年代中,老年代采用標記壓縮(mark compact)算法,保證內存的連續性 。
5. 常見的垃圾回收器
jdk從1.0到14.0一共誕生了10種垃圾回收器,如下圖:
分類如下:
(1) 分代模型:Serial,Serial Old,Parallel Scavenge,Parallel Old,ParNew,CMS
(2) 不分代模型:G1(雖然物理模型上沒分代,但是邏輯層面上是分代的,jdk1.8及以上的版本建議使用G1,響應時間很快,但是1.8默認是PSPO<Parallel Scavenge和Parallel Old>),ZGC(Oracle官方支持),Shenandoah(小紅帽公司開發)
(3) 特殊模型:Epsilon(這種垃圾回收器不回收垃圾,只是跟蹤垃圾的產生和回收,但是這個回收只是動作,其實沒真正回收。Epsilon有兩個用途:<1>用于調試;<2>內存很大,程序很小很快就能運行完成。)
6. 常見垃圾回收器組合參數設定
(1) -XX:+UseSerialGC = Serial New (DefNew) + Serial Old
小型程序默認情況下不會是這種選項,HotSpot會根據計算及配置和JDK版本自動選中收集器。
(2) -XX:+UseParallelGC = Parallel Scavenge + Parallel Old (jdk1.8默認)【PS+Serial Old】
(3) -XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
我們可以用命令行-XX:+PrintCommandLineFlags查看我們所使用的是哪種垃圾回收器,如下圖:
三、總結
通過本文,我們了解了GC的基礎概念、常用的垃圾回收算法、以及JVM內存分代模型和所有的垃圾回收器的特點,下一文我們將著重講解不同垃圾回收器所采用的底層算法及原理,請期待《我的JVM(二):十種垃圾回收器所采用的底層算法及原理》。
更多精彩內容,敬請掃描下方二維碼,關注我的微信公眾號【Java覺淺】,獲取第一時間更新哦!
http://weixin.qq.com/r/xx3v9_7EY7McraqU90jV (二維碼自動識別)
總結
以上是生活随笔為你收集整理的根可达算法的根_我的JVM(六):GC的基础概念以及GC算法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux vg备份还原,Oracle
- 下一篇: java 把图片插入窗体,JAVA JF