神操边苹!精選JVM垃圾回收機制全面分析,聊聊你眼中的JVM

JVM

JVM是Java Virtual Machine(Java虛擬機)的縮寫吏够,JVM是一種用于計算設(shè)備的規(guī)范勾给,它是一個虛構(gòu)出來的計算機滩报,是通過在實際的計算機上仿真模擬各種計算機功能來實現(xiàn)的。Java虛擬機包括一套字節(jié)碼指令集播急、一組寄存器脓钾、一個棧、一個垃圾回收堆和一個存儲方法域桩警。 JVM屏蔽了與具體操作系統(tǒng)平臺相關(guān)的信息可训,使Java程序只需生成在Java虛擬機上運行的目標代碼(字節(jié)碼),就可以在多種平臺上不加修改地運行。JVM在執(zhí)行字節(jié)碼時捶枢,實際上最終還是把字節(jié)碼解釋成具體平臺上的機器指令執(zhí)行握截。

神操!精選JVM垃圾回收機制全面分析烂叔,聊聊你眼中的JVM

1. 概述

垃圾回收(Garbage Collection谨胞,GC),顧名思義就是釋放垃圾占用的空間蒜鸡,防止內(nèi)存泄露胯努。有效的使用可以使用的內(nèi)存,對內(nèi)存堆中已經(jīng)死亡的或者長時間沒有使用的對象進行清除和回收逢防。

2. 垃圾判斷算法

2.1 引用計數(shù)法

給每個對象添加一個計數(shù)器叶沛,當有地方引用該對象時計數(shù)器加1,當引用失效時計數(shù)器減1忘朝。用對象計數(shù)器是否為0來判斷對象是否可被回收灰署。缺點:無法解決循環(huán)引用的問題。

神操局嘁!精選JVM垃圾回收機制全面分析溉箕,聊聊你眼中的JVM

先創(chuàng)建一個字符串,String m = new String("jack");导狡,這時候 "jack" 有一個引用约巷,就是m。然后將m設(shè)置為null旱捧,這時候 "jack" 的引用次數(shù)就等于 0 了独郎,在引用計數(shù)算法中,意味著這塊內(nèi)容就需要被回收了枚赡。

[圖片上傳失敗...(image-977668-1615294672628)]

引用計數(shù)算法是將垃圾回收分攤到整個應用程序的運行當中了氓癌,而不是在進行垃圾收集時,要掛起整個應用的運行贫橙,直到對堆中所有對象的處理都結(jié)束贪婉。因此,采用引用計數(shù)的垃圾收集不屬于嚴格意義上的Stop-The-World的垃圾收集機制卢肃。

看似很美好疲迂,但我們知道JVM的垃圾回收就是Stop-The-World的才顿,那是什么原因?qū)е挛覀冏罱K放棄了引用計數(shù)算法呢?看下面的例子尤蒿。

public class ReferenceCountingGC {

  public Object instance;

  public ReferenceCountingGC(String name) {
  }

  public static void testGC(){

    ReferenceCountingGC a = new ReferenceCountingGC("objA");
    ReferenceCountingGC b = new ReferenceCountingGC("objB");

    a.instance = b;
    b.instance = a;

    a = null;
    b = null;
  }
}
復制代碼

我們可以看到郑气,最后這2個對象已經(jīng)不可能再被訪問了,但由于他們相互引用著對方腰池,導致它們的引用計數(shù)永遠都不會為0尾组,通過引用計數(shù)算法,也就永遠無法通知GC收集器回收它們示弓。

2.2 可達性分析算法

通過GC ROOT的對象作為搜索起始點讳侨,通過引用向下搜索,所走過的路徑稱為引用鏈奏属。通過對象是否有到達引用鏈的路徑來判斷對象是否可被回收(可作為GC ROOT的對象:虛擬機棧中引用的對象跨跨,方法區(qū)中類靜態(tài)屬性引用的對象,方法區(qū)中常量引用的對象囱皿,本地方法棧中JNI引用的對象)

[圖片上傳失敗...(image-f7f891-1615294672628)]

通過可達性算法歹叮,成功解決了引用計數(shù)所無法解決的循環(huán)依賴問題,只要你無法與GC Root建立直接或間接的連接铆帽,系統(tǒng)就會判定你為可回收對象。那這樣就引申出了另一個問題德谅,哪些屬于GC Root爹橱。

Java內(nèi)存區(qū)域中可以作為GC ROOT的對象:

虛擬機棧中引用的對象

public class StackLocalParameter {

  public StackLocalParameter(String name) {}

  public static void testGC() {
    StackLocalParameter s = new StackLocalParameter("localParameter");
    s = null;
  }
}
復制代碼

此時的s,即為GC Root窄做,當s置空時愧驱,localParameter對象也斷掉了與GC Root的引用鏈,將被回收椭盏。

方法區(qū)中類靜態(tài)屬性引用的對象

public class MethodAreaStaicProperties {

  public static MethodAreaStaicProperties m;

  public MethodAreaStaicProperties(String name) {}

  public static void testGC(){
    MethodAreaStaicProperties s = new MethodAreaStaicProperties("properties");
    s.m = new MethodAreaStaicProperties("parameter");
    s = null;
  }
}
復制代碼

此時的s组砚,即為GC Root,s置為null掏颊,經(jīng)過GC后糟红,s所指向的properties對象由于無法與GC Root建立關(guān)系被回收。而m作為類的靜態(tài)屬性乌叶,也屬于GC Root盆偿,parameter 對象依然與GC root建立著連接,所以此時parameter對象并不會被回收准浴。

方法區(qū)中常量引用的對象

public class MethodAreaStaicProperties {

  public static final MethodAreaStaicProperties m = MethodAreaStaicProperties("final");

  public MethodAreaStaicProperties(String name) {}

  public static void testGC() {
    MethodAreaStaicProperties s = new MethodAreaStaicProperties("staticProperties");
    s = null;
  }
}
復制代碼

m即為方法區(qū)中的常量引用事扭,也為GC Root,s置為null后乐横,final對象也不會因沒有與GC Root建立聯(lián)系而被回收求橄。

本地方法棧中引用的對象

[圖片上傳失敗...(image-5d9560-1615294672627)]

任何native接口都會使用某種本地方法棧今野,實現(xiàn)的本地方法接口是使用C連接模型的話,那么它的本地方法棧就是C棧罐农。當線程調(diào)用Java方法時条霜,虛擬機會創(chuàng)建一個新的棧幀并壓入Java棧。然而當它調(diào)用的是本地方法時啃匿,虛擬機會保持Java棧不變蛔外,不再在線程的Java棧中壓入新的幀,虛擬機只是簡單地動態(tài)連接并直接調(diào)用指定的本地方法溯乒。

神操夹厌!精選JVM垃圾回收機制全面分析,聊聊你眼中的JVM

3. 垃圾回收算法

在確定了哪些垃圾可以被回收后裆悄,垃圾收集器要做的事情就是開始進行垃圾回收矛纹,但是這里面涉及到一個問題是:如何高效地進行垃圾回收。這里我們討論幾種常見的垃圾收集算法的核心思想光稼。

3.1 標記-清除算法

神操或南!精選JVM垃圾回收機制全面分析,聊聊你眼中的JVM

標記清除算法(Mark-Sweep)是最基礎(chǔ)的一種垃圾回收算法艾君,它分為2部分采够,先把內(nèi)存區(qū)域中的這些對象進行標記,哪些屬于可回收標記出來冰垄,然后把這些垃圾拎出來清理掉蹬癌。就像上圖一樣,清理掉的垃圾就變成未使用的內(nèi)存區(qū)域虹茶,等待被再次使用逝薪。但它存在一個很大的問題,那就是內(nèi)存碎片蝴罪。

上圖中等方塊的假設(shè)是2M董济,小一些的是1M,大一些的是4M要门。等我們回收完虏肾,內(nèi)存就會切成了很多段。我們知道開辟內(nèi)存空間時暂衡,需要的是連續(xù)的內(nèi)存區(qū)域询微,這時候我們需要一個2M的內(nèi)存區(qū)域,其中有2個1M是沒法用的狂巢。這樣就導致撑毛,其實我們本身還有這么多的內(nèi)存的,但卻用不了。

3.2 復制算法

神操藻雌!精選JVM垃圾回收機制全面分析雌续,聊聊你眼中的JVM

復制算法(Copying)是在標記清除算法基礎(chǔ)上演化而來,解決標記清除算法的內(nèi)存碎片問題胯杭。它將可用內(nèi)存按容量劃分為大小相等的兩塊驯杜,每次只使用其中的一塊。當這一塊的內(nèi)存用完了做个,就將還存活著的對象復制到另外一塊上面鸽心,然后再把已使用過的內(nèi)存空間一次清理掉。保證了內(nèi)存的連續(xù)可用居暖,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復雜情況顽频。復制算法暴露了另一個問題,例如硬盤本來有500G太闺,但卻只能用200G糯景,代價實在太高。

3.3 標記-整理算法

神操省骂!精選JVM垃圾回收機制全面分析蟀淮,聊聊你眼中的JVM

標記-整理算法標記過程仍然與標記-清除算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理钞澳,而是讓所有存活的對象都向一端移動怠惶,再清理掉端邊界以外的內(nèi)存區(qū)域。

標記整理算法解決了內(nèi)存碎片的問題轧粟,也規(guī)避了復制算法只能利用一半內(nèi)存區(qū)域的弊端甚疟。標記整理算法對內(nèi)存變動更頻繁,需要整理所有存活對象的引用地址逃延,在效率上比復制算法要差很多。一般是把Java堆分為新生代和老年代轧拄,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴ā?/p>

3.4 分代收集算法

分代收集算法分代收集算法嚴格來說并不是一種思想或理論揽祥,而是融合上述3種基礎(chǔ)的算法思想,而產(chǎn)生的針對不同情況所采用不同算法的一套組合拳檩电,根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊拄丰。

在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去俐末,只有少量存活料按,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集卓箫。

在老年代中载矿,因為對象存活率高、沒有額外空間對它進行分配擔保烹卒,就必須使用標記-清理算法或者標記-整理算法來進行回收岖圈。

4. 內(nèi)存區(qū)域與回收策略

對象的內(nèi)存分配喂饥,往大方向講雏节,就是在堆上分配(但也可能經(jīng)過JIT編譯后被拆散為標量類型并間接地棧上分配),對象主要分配在新生代的Eden區(qū)上牡整,如果啟動了本地線程分配緩沖,將按線程優(yōu)先在TLAB上分配溺拱。少數(shù)情況下也可能會直接分配在老年代中(大對象直接分到老年代)逃贝,分配的規(guī)則并不是百分百固定的,其細節(jié)取決于當前使用的是哪一種垃圾收集器組合迫摔,還有虛擬機中與內(nèi)存相關(guān)的參數(shù)的設(shè)置沐扳。

4.1 對象優(yōu)先在Eden分配

大多數(shù)情況下,對象會在新生代Eden區(qū)中分配攒菠。當Eden區(qū)沒有足夠空間進行分配時迫皱,虛擬機會發(fā)起一次 Minor GC。Minor GC相比Major GC更頻繁辖众,回收速度也更快卓起。通過Minor GC之后,Eden區(qū)中絕大部分對象會被回收凹炸,而那些存活對象戏阅,將會送到Survivor的From區(qū)(若From區(qū)空間不夠,則直接進入Old區(qū)) 啤它。

4.2 Survivor區(qū)

Survivor區(qū)相當于是Eden區(qū)和Old區(qū)的一個緩沖奕筐,類似于我們交通燈中的黃燈。Survivor又分為2個區(qū)变骡,一個是From區(qū)离赫,一個是To區(qū)。每次執(zhí)行Minor GC塌碌,會將Eden區(qū)中存活的對象放到Survivor的From區(qū)渊胸,而在From區(qū)中,仍存活的對象會根據(jù)他們的年齡值來決定去向台妆。(From Survivor和To Survivor的邏輯關(guān)系會發(fā)生顛倒: From變To 翎猛, To變From,目的是保證有連續(xù)的空間存放對方接剩,避免碎片化的發(fā)生)

4.2.1 Survivor區(qū)存在的意義

如果沒有Survivor區(qū)切厘,Eden區(qū)每進行一次Minor GC,存活的對象就會被送到老年代懊缺,老年代很快就會被填滿疫稿。而有很多對象雖然一次Minor GC沒有消滅,但其實也并不會蹦跶多久,或許第二次而克,第三次就需要被清除靶壮。這時候移入老年區(qū),很明顯不是一個明智的決定员萍。所以腾降,Survivor的存在意義就是減少被送到老年代的對象,進而減少Major GC的發(fā)生碎绎。Survivor的預篩選保證螃壤,只有經(jīng)歷16次Minor GC還能在新生代中存活的對象,才會被送到老年代筋帖。

4.3 大對象直接進入老年代

所謂大對象是指奸晴,需要大量連續(xù)內(nèi)存空間的Java對象,最典型的大對象就是那種很長的字符串以及數(shù)組日麸。大對象對虛擬機的內(nèi)存分配來說就是一個壞消息寄啼,經(jīng)常出現(xiàn)大對象容易導致內(nèi)存還有不少空間時就提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間來 “安置” 它們。

虛擬機提供了一個XX:PretenureSizeThreshold參數(shù)代箭,令大于這個設(shè)置值的對象直接在老年代分配墩划,這樣做的目的是避免在Eden區(qū)及兩個Survivor區(qū)之間發(fā)生大量的內(nèi)存復制(新生代采用的是復制算法)。

4.4 長期存活的對象將進入老年代

虛擬機給每個對象定義了一個對象年齡(Age)計數(shù)器嗡综,如果對象在Eden出生并經(jīng)過第一次Minor GC后仍然存活乙帮,并且能被Survivor容納的話,將被移動到Survivor空間中(正常情況下對象會不斷地在Survivor的From與To區(qū)之間移動)极景,并且對象年齡設(shè)為1察净。對象在Survivor區(qū)中每經(jīng)歷一次Minor GC,年齡就增加1歲盼樟,當它的年齡增加到一定程度(默認15歲)氢卡,就將會晉升到老年代中。對象晉升老年代的年齡閾值晨缴,可以通過參數(shù) XX:MaxPretenuringThreshold 設(shè)置异吻。

4.5 動態(tài)對象年齡判定

為了能更好地適應不同程度的內(nèi)存狀況,虛擬機并不是永遠地要求對象的年齡必須達到 MaxPretenuringThreshold才能晉升老年代喜庞,如果Survivor空間中相同年齡所有對象大小的總和大于Survivor空間的一半,年齡大于或等于改年齡的對象就可以直接進入老年代棋返,無需等到MaxPretenuringThreshold中要求的年齡延都。

這其實有點類似于負載均衡,輪詢是負載均衡的一種睛竣,保證每臺機器都分得同樣的請求晰房。看似很均衡,但每臺機的硬件不同殊者,健康狀況不同与境,我們還可以基于每臺機接受的請求數(shù),或每臺機的響應時間等猖吴,來調(diào)整我們的負載均衡算法摔刁。

以上就是有關(guān)JVM的學習筆記,希望可以對大家有所幫助海蔽,喜歡的小伙伴可以幫忙轉(zhuǎn)發(fā)+關(guān)注共屈,感謝大家!LZ也會每天不定時更新干貨党窜!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拗引,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子幌衣,更是在濱河造成了極大的恐慌矾削,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件豁护,死亡現(xiàn)場離奇詭異哼凯,居然都是意外死亡,警方通過查閱死者的電腦和手機择镇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門挡逼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腻豌,你說我怎么就攤上這事家坎。” “怎么了吝梅?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵虱疏,是天一觀的道長。 經(jīng)常有香客問我苏携,道長做瞪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任右冻,我火速辦了婚禮装蓬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘纱扭。我一直安慰自己牍帚,他們只是感情好,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布乳蛾。 她就那樣靜靜地躺著暗赶,像睡著了一般鄙币。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蹂随,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天十嘿,我揣著相機與錄音,去河邊找鬼岳锁。 笑死绩衷,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的浸锨。 我是一名探鬼主播唇聘,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼柱搜!你這毒婦竟也來了迟郎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤聪蘸,失蹤者是張志新(化名)和其女友劉穎宪肖,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體健爬,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡控乾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了娜遵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蜕衡。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖设拟,靈堂內(nèi)的尸體忽然破棺而出慨仿,到底是詐尸還是另有隱情,我是刑警寧澤纳胧,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布镰吆,位于F島的核電站,受9級特大地震影響跑慕,放射性物質(zhì)發(fā)生泄漏万皿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一核行、第九天 我趴在偏房一處隱蔽的房頂上張望牢硅。 院中可真熱鬧,春花似錦芝雪、人聲如沸减余。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佳励。三九已至,卻和暖如春蛆挫,著一層夾襖步出監(jiān)牢的瞬間赃承,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工悴侵, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留瞧剖,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓可免,卻偏偏與公主長得像抓于,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子浇借,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345

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