JVM相關 : 2.垃圾回收

1. 如何判斷對象可以回收?

1.1 引用計數(shù)法

算法:在對象中添加一個引用計數(shù)器,每當有一個地方引用它時罢杉,計數(shù)器值加一;引用失效時贡歧,計數(shù)器值減一滩租。計數(shù)器為0 的對象時不被使用的。

缺點:無法回收兩個相互引用的對象

引用計數(shù).png

1.2 可達性分析算法

  • java虛擬機中的垃圾回收器采用可達性分析算法來探索所有存活的對象
  • 掃描堆中的對象利朵,看是否能夠沿著 GC Root 對象為起點的引用鏈找到該對象律想,找不到,表示可以回收
  • 哪些對象可以作為 GC Root 绍弟?

可作為 GC Root 對象:

  • 在虛擬機棧(棧中的本地變量表)中引用的對象技即。各個線程被調(diào)用的方法棧中用到的參數(shù)、局部變量等
  • 在方法區(qū)中類靜態(tài)屬性引用的對象
  • 在方法區(qū)中常量的引用對象樟遣,例如 字符串常量池(StringTable)里的引用
  • 在本地方法棧中JNI引用的對象
  • Java虛擬機內(nèi)部的引用而叼,例如Class對象,系統(tǒng)類加載器等
  • 所有被同步鎖(synchronized 關鍵字)持有的對象

1.3 四種引用

1.強引用

  • 只有所有的 GC Root 都不引用該對象豹悬,該對象才能被回收

2.軟引用

  • 僅有軟引用引用該對象時葵陵,在垃圾回收后,內(nèi)存仍不足時會再次觸發(fā)垃圾回收瞻佛,回收軟引用對象
  • 可以配合引用隊列來釋放軟引用自身 (如果軟引用所引用的對象被垃圾回收脱篙,JAVA虛擬機就會把這個軟引用加入到與之關聯(lián)的引用隊列 ReferenceQueue 中)
public class SoftReferenceTest {

    public static final int _4MB = 4 * 1024 * 1024;

    public static void main(String[] args) {
        List<SoftReference<byte[]>> list = new ArrayList<>();

        //引用隊列
        ReferenceQueue<byte[]> queue = new ReferenceQueue<>();
        for (int i = 0; i < 5; i++) {
            //關聯(lián)軟引用和引用隊列 當軟引用關聯(lián)的byte[] 被回收時,軟引用自身被加入到引用隊列中
            SoftReference<byte[]> ref = new SoftReference<>(new byte[_4MB], queue);
            list.add(ref);
        }
    }
}

3.弱引用

  • 僅有弱引用引用該對象時伤柄,在垃圾回收時绊困,無論內(nèi)存是否足夠,都會回收弱引用對象
  • 可以配合引用隊列 ReferenceQueue 來釋放弱引用自身

虛引用 和 終結器引用必須配合引用隊列

4.虛引用

  • 必須配合引用隊列使用适刀,主要配合 ByteBuffer 使用秤朗。被引用對象回收時,會將虛引用入隊蔗彤,由ReferenceHandler 線程調(diào)用虛引用相關方法釋放直接內(nèi)存(虛引用對象cleaner 實際上調(diào)用Unsafe.freeMemory() 川梅, 來釋放直接內(nèi)存)

5.終結器引用

  • 無需手動編碼。但其內(nèi)部配合引用隊列使用然遏,在垃圾回收時贫途,終結器引用入隊(被引用對象暫時沒有被回收),再由 Finalizer線程(優(yōu)先級很低)查看引用隊列中的終結器引用待侵,找到被引用對象并調(diào)用它的 finalize() 方法丢早。第二次 GC時,才能回收被引用對象。

java 編程時不要復寫finalize()方法來釋放內(nèi)存怨酝,因為效率很低

2. 垃圾回收算法

2.1 標記-清除 Mark-Sweep

算法:首先標記出所有需要回收的對象傀缩,標記完成后,統(tǒng)一回收掉所有被標記的對象农猬。
或者赡艰,標記出所有存活的對象。清除掉未標記的對象斤葱。

優(yōu)點:

  • 速度比較高
    • 只需要把需要清除的內(nèi)存起始地址和結束地址記錄到一個表中慷垮。

缺點:

  • 執(zhí)行效率不穩(wěn)定
    • 如果堆中包含大量對象,大部分都是需要被回收的揍堕,這時必須進行大量標記和清除動作料身,導致標記和清除過程的執(zhí)行效率都隨著對象增長而降低。
  • 會造成內(nèi)存碎片衩茸∏垩可能造成以后需要 分配較大對象時無法找到足夠的連續(xù)內(nèi)存而提前觸發(fā)一次垃圾收集
標記-清除 (1).png

2.2 標記-整理 Mark-Compack

算法:標記需要回收的對象,讓所有存活的對象都向內(nèi)存空間的一端移動楞慈,然后直接清理掉邊界以外的內(nèi)存幔烛。

缺點:

  • 速度慢
    • 移動存活對象并更新所有引用這些對象的地方將會是一種極為負重的操作

優(yōu)點:

  • 沒有內(nèi)存碎片
標記-整理.png

2.3 標記-復制 Mark-Copy

算法:將可用內(nèi)存按容量劃分大小相等的兩塊,每次只使用其中一塊抖部。當一塊內(nèi)存用完了说贝,就將存活的對象復制到另一塊上面,然后再把已使用的內(nèi)存空間一次清理掉慎颗。

現(xiàn)在的商用java虛擬機大多都優(yōu)先采用了這種收集算法去回收新生代乡恕。
新生代中的對象有98%熬不過第一輪收集,因此并不需要按照1:1的比例來劃分新生代的內(nèi)存空間俯萎。

優(yōu)點:

  • 不會有內(nèi)存碎片

缺點:

  • 需要占用雙倍內(nèi)存空間
    • 將可用的空間縮小為原來的一半傲宜,空間浪費大


      復制.png

3. 分代垃圾回收

分代垃圾回收.png

特點:

  • 對象優(yōu)先分配在伊甸區(qū)

  • 大對象直接晉升老年代(新生代空間不足,老年代空間足夠夫啊,直接放到老年代函卒,不會引起Minor GC)

  • 新生代空間不足時,首次觸發(fā) Minor GC, 伊甸區(qū)和 FROM 存活的對象會復制到 TO 中撇眯。存活的對象年齡加1报嵌,并且交換 FROM 和 TO.

  • Minor GC會引發(fā) stop the world. 暫停其他用戶線程,等待垃圾回收完成熊榛,用戶線程才恢復運行锚国。

  • 當對象年齡超過閾值時,會晉升至老年代(最大壽命是15)

  • 當老年代空間不足玄坦,會先嘗試觸發(fā) Minor GC, 如果之后空間仍不足血筑,那么觸發(fā)Full GC, 那么 stop the world 的時間更長绘沉。

3.1 相關 VM 參數(shù)

含義 參數(shù)
堆初始大小 -Xms
堆最大大小 -Xmx 或 -XX:MaxHeapSize=size
新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size)
幸存區(qū)比例(動態(tài)) -XX:InitialSurvivorRatio=ratio + -XX:UserAdaptiveSizePolicy
幸存區(qū)比例 -XX:SurvivorRatio=ratio
晉升閾值 -XX:MaxTenuringThreshold=threshold
晉升詳情 -XX:+PrintTenuringDistribution
GC詳情 -XX:+PrintGCDetails -verbose:gc
Full GC 前 Minor GC -XX:+ScavengeBeforeFullGC

示例:

//-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose gc
public static void main(String[] args) {
     
}

堆信息

Heap
 def new generation   total 9216K, used 1147K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  14% used [0x00000000fec00000, 0x00000000fed1edf0, 0x00000000ff400000)
  from space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
  to   space 1024K,   0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000)
 tenured generation   total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,   0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000)
 Metaspace       used 2871K, capacity 4480K, committed 4480K, reserved 1056768K
  class space    used 317K, capacity 384K, committed 384K, reserved 1048576K

3.2 小結

堆內(nèi)存空間分為較大的 Eden 和 兩塊較小的 Survivor 區(qū)。每次只使用 Eden 和 survivor 中的一塊豺总。

這種情況下標記-復制 算法 減少了內(nèi)存空間的浪費车伞。

復制算法-現(xiàn)作為主流的YGC算法進行新生代垃圾回收

4. 垃圾回收器

垃圾回收器分類:

1.串行

  • 單線程
  • 適用堆內(nèi)存較小,個人電腦

2.吞吐量優(yōu)先

  • 多線程
  • 堆內(nèi)存較大喻喳,需要多核 CPU 支持
  • 單位時間內(nèi)另玖,stop the world的時間最短 (垃圾回收時間占總時間占比低)

3.響應時間優(yōu)先

  • 多線程
  • 堆內(nèi)存較大,需要多核 CPU 支持
  • 垃圾回收時沸枯,(單次的)盡可能讓stop the world時間最短

4.1 串行

開啟串行垃圾回收器:

-XX:+UseSerialGC=Serial + SerialOld

  • Serial:工作在新生代日矫,采用復制算法
  • SerialOld:工作在老年代赂弓,采用標記-整理算法

為什么工作線程要在安全點停下來绑榴?

因為垃圾回收過程中對象的地址可能發(fā)生改變,為了保證安全的使用這些對象地址

串行.png

4.2 吞吐量優(yōu)先

開啟吞吐量優(yōu)先開關

  • -XX:+UseParallelGC 工作在新生代 復制算法
  • -XX:+UseParallelOldGC 工作在老年代 標記-整理算法

與之有關的一些參數(shù):

-XX:+UseAdaptiveSizePolicy

  • 采用自適應大小調(diào)整 盈魁,動態(tài)調(diào)整新生代eden 和 survivor 的大小 翔怎,包括晉升年齡等

-XX:GCTimeRatio=ratio

  • 用來調(diào)整垃圾回收時間和總時間占比 ratio默認值99 1/(1+ratio)

-XX:MaxGCPauseMillis=ms

  • 最大暫停毫秒數(shù) 默認200ms

上面兩個設置是沖突的

-XX:ParallelGCTheads=n

  • 設置可以開啟的垃圾回收線程

垃圾回收線程跟CPU核數(shù)相關 ,也可以人為設置最大線程數(shù)-XX:ParallelGCTheads=n

吞吐量優(yōu)先.png

4.3 響應時間優(yōu)先

開啟響應時間優(yōu)先的開關:CMS

-XX:+UseConcMarkSweepGC~SerialOld -XX:+UseParNewGC

  • -XX:+UseConcMarkSweepGC 工作在老年代 標記-清除算法 并發(fā):垃圾回收線程工作的同時用戶線程也能工作杨耙。有可能并發(fā)失敵嗵住(由于內(nèi)存碎片過多),導致退化到 SerialOld垃圾回收器

  • -XX:+UseParNewGC 工作在新生代 復制算法

與之有關的一些參數(shù):

-XX:ParallelGCTheads=n -XX:ConcGCThread=threads

  • 這兩個參數(shù)珊膜,影響初始標記

-XX:CMSInitiatingOccupancyFraction=percent

  • 控制何時來進行CMS垃圾回收時機(例如老年代內(nèi)存占用達到80% 觸發(fā)一次內(nèi)存回收)

-XX:+CMSScavengeBeforeRemark

  • 重新標記之前對新生代做一次垃圾回收

初始標記時(找到那些跟對象)容握,stop the world

并發(fā)標記:從根對象出發(fā),順著引用鏈標記其他對象

重新標記時车柠,stop the world

響應時間優(yōu)先.png

4.4 G1 Garbage First

取代了之前的CMS垃圾回收器

適用場景

  • 同時注重吞吐量(Throughput)和低延遲(Low Latency),默認暫停目標是 200ms
  • 超大堆內(nèi)存剔氏,會將堆劃分為多個大小相等的Region
  • 整體上是標記-整理算法,兩個區(qū)域之間是復制算法

相關JVM參數(shù)

-XX:+UseG1GC

-XX:G1HeapRegionSize=size

-XX:MaxGCPauseMillis=ms

4.4.1 G1 垃圾回收階段

G1垃圾回收階段.png

這是一個循環(huán)的過程

4.4.2 Young Collection

劃分成一個個大小相等的區(qū)域竹祷,每個區(qū)域都可以獨立作為 Eden 谈跛、幸存區(qū)、老年代

  • 會stop the world

經(jīng)歷一次young collection 伊甸中幸存的對象拷貝到survivor區(qū)

幸存區(qū)內(nèi)存不足時拷貝到老年代

G1_youngCollection.png
4.4.3 Young Collection + CM
  • 在young GC 時會進行 GC Root 的初始標記

  • 老年代占用堆空間比例達到閾值時塑陵,進行并發(fā)標記(不會STW)立砸,由下面的JVM 參數(shù)決定:

    -XX:InitiatingHeapOccupancyPercent=percent (默認 45%)

4.4.4 Mixed Collection

會對E S O 進行全面垃圾回收

  • 最終標記(Remark)(標記并發(fā)標記漏掉的對象)伏穆,會STW
  • 拷貝存活(Evacuation),會STW (對老年代來說,并不會回收所有區(qū)域盖腿。會回收垃圾最多的老年區(qū))

-XX:MaxGCPauseMillis:ms


G1MixedCollection.png
4.4.5 Full GC
  • Serial GC
    • 新生代內(nèi)存不足發(fā)生的垃圾收集 Minor GC
    • 老年代內(nèi)存不足發(fā)生的垃圾收集 Full GC
  • Parallel GC
    • 新生代內(nèi)存不足發(fā)生的垃圾收集 Minor GC
    • 老年代內(nèi)存不足發(fā)生的垃圾收集 Full GC
  • CMS
    • 新生代內(nèi)存不足發(fā)生的垃圾收集 Minor GC
    • 老年代內(nèi)存不足----當垃圾回收的速度跟不上垃圾產(chǎn)生的速度會并發(fā)失敗退化為 SerialGC收集器 Full GC
  • G1
  • 新生代內(nèi)存不足發(fā)生的垃圾收集 Minor GC
  • 老年代內(nèi)存不足(首先:Mixed Collection)----當垃圾回收的速度跟不上垃圾產(chǎn)生的速度會并發(fā)失敗退化為 SerialGC收集器 Full GC
4.4.6 Young Collection 跨代引用

減少GC Root 的標記時間

  • 卡表與 Remembered Set
  • 在引用變量時通過post-writer barrier + dirty card queue
  • concurrent refinement threads 更新 Remembered Set
G1跨代引用.png
4.4.7 Remark

重新標記階段

4.4.8 JDK 8u20 字符串去重
  • 優(yōu)點:節(jié)省大量內(nèi)存
  • 缺點:略微多占用了 CPU 時間, 新生代回收時間略微增加

-XX:+UseStringDeduplication

String s1 = new String("hello");
String s2 = new String("hello");
  • 將所有新分配的字符串放入一個隊列
  • 當新生代回收時旨涝,G1并發(fā)檢查是否有字符串重復
  • 如果它們值一樣鱼的,讓他們引用char[]
  • 注意,與String.intern() 不一樣
    • String.intern() 關注的是字符串對象
    • 而字符串去重關注的是char[]
    • 在JVM內(nèi)部俯抖,使用了不同的字符串表
4.4.9 JDK 8u40 并發(fā)標記類卸載

所有對象都經(jīng)過并發(fā)標記后输瓜,就知道哪些類不再被使用。當一個類加載器(針對自定義的類加載器)的所有類都不再使用,則卸載它所加載的所有類尤揣。

-XX:+ClassUnloadingWithConcurrentMark 默認開啟

4.4.10 JDK 8u60 回收巨型對象
  • 一個對象大于region 的一半時稱為巨型對象
  • G1 不會對巨型對象進行拷貝
  • 回收時被優(yōu)先考慮
  • G1會跟蹤老年代所有的incoming 引用搔啊,這樣老年代 incoming 為 0的巨型對象就可以在新生代回收時處理掉
4.4.11 JDK9 并發(fā)標記起始時間調(diào)整
  • 并發(fā)標記必須在堆空間占滿前完成,否則退化為Full GC
  • JDK 9 之前需要使用 -XX:InitiatingHeapOccupancyPercent=percent
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末北戏,一起剝皮案震驚了整個濱河市负芋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗜愈,老刑警劉巖旧蛾,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蠕嫁,居然都是意外死亡锨天,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門剃毒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來病袄,“玉大人,你說我怎么就攤上這事赘阀∫娌” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵基公,是天一觀的道長幅慌。 經(jīng)常有香客問我,道長轰豆,這世上最難降的妖魔是什么胰伍? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮秒咨,結果婚禮上喇辽,老公的妹妹穿的比我還像新娘。我一直安慰自己雨席,他們只是感情好菩咨,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著陡厘,像睡著了一般抽米。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糙置,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天云茸,我揣著相機與錄音,去河邊找鬼谤饭。 笑死标捺,一個胖子當著我的面吹牛懊纳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播亡容,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼嗤疯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了闺兢?” 一聲冷哼從身側響起茂缚,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屋谭,沒想到半個月后脚囊,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡桐磁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年悔耘,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片所意。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡淮逊,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出扶踊,到底是詐尸還是另有隱情,我是刑警寧澤郎任,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布秧耗,位于F島的核電站,受9級特大地震影響舶治,放射性物質(zhì)發(fā)生泄漏分井。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一霉猛、第九天 我趴在偏房一處隱蔽的房頂上張望尺锚。 院中可真熱鬧,春花似錦惜浅、人聲如沸瘫辩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伐厌。三九已至,卻和暖如春裸影,著一層夾襖步出監(jiān)牢的瞬間挣轨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工轩猩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留卷扮,地道東北人荡澎。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像晤锹,于是被迫代替她去往敵國和親衔瓮。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容