自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Stop The World 是何時(shí)發(fā)生的?

開(kāi)發(fā) 架構(gòu)
Python判斷對(duì)象存活的算法用的是引用計(jì)數(shù)法,而Java則使用的是可達(dá)性分析法。

[[416431]]

垃圾回收流程的一些流程

哪些對(duì)象是垃圾?

當(dāng)我們進(jìn)行垃圾回收的時(shí)候,首先需要判斷哪些對(duì)象是存活的?

常用的方法有如下兩種

  • 引用計(jì)數(shù)法
  • 可達(dá)性分析法

Python判斷對(duì)象存活的算法用的是引用計(jì)數(shù)法,而Java則使用的是可達(dá)性分析法。

「通過(guò)GC ROOT可達(dá)的對(duì)象,不能被回收,不可達(dá)的對(duì)象則可以被回收,搜索走過(guò)的路徑叫做引用鏈」

不可達(dá)對(duì)象會(huì)進(jìn)行2次標(biāo)記的過(guò)程,通過(guò)GC ROOT不可達(dá),會(huì)被第一次標(biāo)記。如果需要執(zhí)行finalize()方法,則這個(gè)對(duì)象會(huì)被放入一個(gè)隊(duì)列中執(zhí)行finalize(),如果在finalize()方法中成功和引用鏈上的其他對(duì)象關(guān)聯(lián),則會(huì)被移除可回收對(duì)象集合(「一般你不建議你使用finalize方法」),否則被回收

「常見(jiàn)的GC ROOT有如下幾種」

  1. 虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象
  2. 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
  3. 方法區(qū)中常量引用的對(duì)象
  4. 本地方法棧中JNI(Native方法)引用的對(duì)象

「照這樣看,程序中的GC ROOT有很多,每次垃圾回收都要對(duì)GC ROOT的引用鏈分析一遍,感覺(jué)耗費(fèi)的時(shí)間很長(zhǎng)啊,有沒(méi)有可能減少每次掃描的GC ROOT?」

分代和跨代引用

其實(shí)當(dāng)前虛擬機(jī)大多數(shù)都遵循了“分代收集”理論進(jìn)行設(shè)計(jì),它的實(shí)現(xiàn)基于2個(gè)分代假說(shuō)之上

  1. 絕大多數(shù)對(duì)象都是朝生夕滅的
  2. 熬過(guò)多次垃圾收集過(guò)程的對(duì)象就越難以消亡

因此堆一般被分為新生代和老年代,針對(duì)新生代的GC叫MinorGC,針對(duì)老年代的GC叫OldGC。但是分代后有一個(gè)問(wèn)題,為了找到新生代的存活對(duì)象,不得不遍歷老年代,反過(guò)來(lái)也一樣

當(dāng)進(jìn)行MinorGC的時(shí)候,如果我們只遍歷新生代,那么可以判定ABCD為存活對(duì)象。但是E不會(huì)被判斷為存活對(duì)象,所以就會(huì)有問(wèn)題。

為了解決這種跨代引用的對(duì)象,最笨的辦法就是遍歷老年代的對(duì)象,找出這些跨代引用的對(duì)象。但這種方式對(duì)性能影響較大

這時(shí)就不得不提到第三個(gè)假說(shuō)

「跨代引用相對(duì)于同代引用來(lái)說(shuō)僅占極少數(shù)?!?/p>

根據(jù)這條假說(shuō),我們就不需要為了少量的跨代引用去掃描整個(gè)老年代?!笧榱吮苊獗闅v老年代的性能開(kāi)銷,垃圾回收器會(huì)引入一種記憶集的技術(shù),記憶集就是用來(lái)記錄跨代引用的表」

如新生代的記憶集就保存了老年代持有新生代的引用關(guān)系

所以在進(jìn)行MinorGC的時(shí)候,只需要將包含跨代引用的內(nèi)存區(qū)域加入GC ROOT一起掃描就行了

卡表

前面我們說(shuō)到垃圾收集器用記憶集來(lái)記錄跨代引用。其實(shí)你可以把記憶集理解為接口,卡表理解為實(shí)現(xiàn),類比Map和HashMap。

卡表最簡(jiǎn)單的形式可以只是一個(gè)字節(jié)數(shù)組, 而HotSpot虛擬機(jī)確實(shí)也是這樣做的。以下這行代碼是HotSpot默認(rèn)的卡表標(biāo)記邏輯:

  1. CARD_TABLE [this address >> 9] = 0; 

HotSpot用一個(gè)數(shù)組元素來(lái)保存對(duì)應(yīng)的內(nèi)存地址是有有跨代引用對(duì)象(從this address右移9位可以看出每個(gè)元素映射了512字節(jié)的內(nèi)存)

當(dāng)數(shù)組元素值為0時(shí)表明對(duì)應(yīng)的內(nèi)存地址不存在跨代引用對(duì)象,否則存在(稱為卡表中這個(gè)元素變臟)

如何更新卡表?

「將卡表元素變臟的過(guò)程,HotSpot是通過(guò)寫(xiě)屏障來(lái)實(shí)現(xiàn)的」,即當(dāng)其他代對(duì)象引用當(dāng)前分代對(duì)象的時(shí)候,在引用賦值階段更新卡表,具體實(shí)現(xiàn)方式類似于AOP

  1. void oop_field_store(oop* field, oop new_value) {  
  2. // 引用字段賦值操作 
  3. *field = new_value; 
  4. // 寫(xiě)后屏障,在這里完成卡表狀態(tài)更新  
  5. post_write_barrier(field, new_value); 

三色標(biāo)記法

執(zhí)行思路

「如何判斷一個(gè)對(duì)象可達(dá)呢?這就不得不提到三色標(biāo)記法」

白色:剛開(kāi)始遍歷的時(shí)候所有對(duì)象都是白色的 灰色:被垃圾回收器訪問(wèn)過(guò),但至少還有一個(gè)引用未被訪問(wèn) 黑色:被垃圾回收器訪問(wèn)過(guò),并且這個(gè)對(duì)象的所有引用都被訪問(wèn)過(guò),是安全存活的對(duì)象(GC ROOT會(huì)被標(biāo)記為黑色)

以上圖為例,三色標(biāo)記法的執(zhí)行流程如下

先將GC ROOT引用的對(duì)象B和E標(biāo)記為灰色

接著將B和E引用的對(duì)象A,C和F標(biāo)記為灰色,此時(shí)B和E標(biāo)記為黑色

依次類推,最終被標(biāo)記為白色的對(duì)象需要被回收

三色標(biāo)記法問(wèn)題

可達(dá)性分析算法根節(jié)點(diǎn)枚舉這一步必須要在一個(gè)能保障一致性的快照中分析,所以要暫停用戶線程(Stop The World ,STW),在各種優(yōu)化技巧的加持下,停頓時(shí)間已經(jīng)非常短了。

在從根節(jié)點(diǎn)掃描的過(guò)程則不需要STW,但是也會(huì)發(fā)生一些問(wèn)題。由于此時(shí)垃圾回收線程和用戶線程一直運(yùn)行,所以引用關(guān)系會(huì)發(fā)生變化

  1. 應(yīng)該被回收的對(duì)象被標(biāo)記為不被回收
  2. 不應(yīng)該被回收的對(duì)象標(biāo)記為應(yīng)該回收

第一種情況影響不大,大不了后續(xù)回收即可。但是第二種情況則會(huì)造成致命錯(cuò)誤

所以經(jīng)過(guò)研究表明,只有同時(shí)滿足兩個(gè)條件才會(huì)發(fā)生第二種情況

插入了一條或者多條黑色到白色對(duì)象的引用

刪除了全部從灰色到白色對(duì)象的引用

為了解決這個(gè)問(wèn)題,我們破壞2個(gè)條件中任意一個(gè)不就行了,由此產(chǎn)生了2中解決方案,「增量更新」和「原始快照」。CMS使用的是增量更新,G1使用的是原始快照

「增量更新要破壞的是第一個(gè)條件」, 當(dāng)黑色對(duì)象插入新的指向白色對(duì)象的引用關(guān)系時(shí), 就將這個(gè)新插入的引用記錄下來(lái), 等并發(fā)掃描結(jié)束之后, 再將這些記錄過(guò)的引用關(guān)系中的黑色對(duì)象為根, 重新掃描一次。這可以簡(jiǎn)化理解為, 黑色對(duì)象一旦新插入了指向白色對(duì)象的引用之后, 它就變回灰色對(duì)象了

「原始快照要破壞的是第二個(gè)條件」, 當(dāng)灰色對(duì)象要?jiǎng)h除指向白色對(duì)象的引用關(guān)系時(shí), 就將這個(gè)要?jiǎng)h除的引用記錄下來(lái), 在并發(fā)掃描結(jié)束之后, 再將這些記錄過(guò)的引用關(guān)系中的灰色對(duì)象為根, 重新掃描一次。這也可以簡(jiǎn)化理解為, 無(wú)論引用關(guān)系刪除與否, 都會(huì)按照剛剛開(kāi)始掃描那一刻的對(duì)象圖快照來(lái)進(jìn)行搜索。

參考自《深入理解Java虛擬機(jī)》

垃圾收集器

圖中展示了七種作用于不同分代的收集器,如果兩個(gè)收集器之間存在連線,就說(shuō)明它們可以搭配使用。在JDK8時(shí)將Serial+CMS,ParNew+Serial Old這兩個(gè)組合聲明為廢棄,并在JDK9中完全取消了這些組合的支持

并行和并發(fā)都是并發(fā)編程中的專業(yè)名詞,在談?wù)摾占鞯纳舷挛恼Z(yǔ)境中, 它們可以理解為

「并行(Parallel)」:指多條垃圾收集線程并行工作,但此時(shí)用戶線程仍然處于等待狀態(tài)

「并發(fā)(Concurrent」):指用戶線程與垃圾收集線程同時(shí)執(zhí)行

Serial收集器

「新生代,標(biāo)記-復(fù)制算法,單線程。進(jìn)行垃圾收集時(shí),必須暫停其他所有工作線程,直到它收集結(jié)束」

ParNew收集器

「ParNew本質(zhì)上是Serial收集器的多線程并行版本」

Parallel Scavenge收集器

「新生代,標(biāo)記復(fù)制算法,多線程,主要關(guān)注吞吐量」

吞吐量=運(yùn)行用戶代碼時(shí)間/(運(yùn)行用戶代碼時(shí)間+運(yùn)行垃圾收集時(shí)間)

Serial Old收集器

「老年代,標(biāo)記-整理算法,單線程,是Serial收集器的老年代版本」

用處有如下2個(gè)

  1. 在JDK5以及之前的版本中與Parallel Scavenge收集器搭配使用
  2. 作為CMS收集器發(fā)生失敗時(shí)的后備預(yù)案,在并發(fā)收集發(fā)生Concurrent Mode Failure時(shí)使用

Parallel Old收集器

「老年代,標(biāo)記-整理算法,多線程,是Parallel Scavenge收集器的老年代版本」

在注重吞吐量或者處理器資源較為稀缺的場(chǎng)合,都可以優(yōu)先考慮Parallel Scavenge加Parallel Old收集器這個(gè)組合

CMS收集器

「老年代,標(biāo)記-清除算法,多線程,主要關(guān)注延遲」

運(yùn)作過(guò)程分為4個(gè)步驟

  1. 初始標(biāo)記(CMS initial mark)
  2. 并發(fā)標(biāo)記(CMS concurrent mark)
  3. 重新標(biāo)記(CMS remark)
  4. 并發(fā)清除(CMS concurrent sweep)

  1. 初始標(biāo)記:標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,速度很快(這一步會(huì)發(fā)生STW)
  2. 并發(fā)標(biāo)記:從GC Roots的直接關(guān)聯(lián)對(duì)象開(kāi)始遍歷整個(gè)對(duì)象圖的過(guò)程,這個(gè)過(guò)程耗時(shí)較長(zhǎng)但是不需要停頓用戶線程,可以與垃圾收集一起并發(fā)運(yùn)行
  3. 重新標(biāo)記:為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運(yùn)作而導(dǎo)致標(biāo)記產(chǎn)生變動(dòng)的那一部分對(duì)象的標(biāo)記記錄(「就是三色標(biāo)記法中的增量更新」,這一步也會(huì)發(fā)生STW)
  4. 并發(fā)清除:清理刪除掉標(biāo)記階段判斷的已經(jīng)死亡的對(duì)象,由于不需要移動(dòng)存活對(duì)象,所以看這個(gè)階段也是可以與用戶線程同時(shí)并發(fā)的

總結(jié)

收集器 收集對(duì)象和算法 收集器類型 說(shuō)明 適用場(chǎng)景
Serial 新生代,復(fù)制算法 單線程   簡(jiǎn)單高效;適合內(nèi)存不大的情況
ParNew 新生代,復(fù)制算法 并行的多線程收集器 ParNew垃圾收集器是Serial收集器的多線程版本 搭配CMS垃圾回收器的首選
Parallel Scavenge吞吐量?jī)?yōu)先收集器 新生代,復(fù)制算法 并行的多線程收集器 類似ParNew,更加關(guān)注吞吐量,達(dá)到一個(gè)可控制的吞吐量 本身是Server級(jí)別多CPU機(jī)器上的默認(rèn)GC方式,主要適合后臺(tái)運(yùn)算不需要太多交互的任務(wù)
收集器 收集對(duì)象和算法 收集器類型 說(shuō)明 適用場(chǎng)景
Serial Old 老年代,標(biāo)記整理算法 單線程   Client模式下虛擬機(jī)使用
Parallel Old 老年代,標(biāo)記整理算法 并行的多線程收集器 Paraller Scavenge收集器的老年代版本,為了配置Parallel Svavenge的面向吞吐量的特性而開(kāi)發(fā)的對(duì)應(yīng)組合 在注重吞吐量以及CPU資源敏感的場(chǎng)合采用
CMS 老年代,標(biāo)記清除算法 并行與并發(fā)收集器 盡可能的縮短垃圾收集時(shí)用戶線程停止時(shí)間;缺點(diǎn)在于,1.內(nèi)存碎片,2.需要更多CPU資源,3.浮動(dòng)垃圾問(wèn)題,需要更大的堆空間 重視服務(wù)的相應(yīng)速度,系統(tǒng)停頓時(shí)間和用戶體驗(yàn)的互聯(lián)網(wǎng)網(wǎng)站或者B/S系統(tǒng)?;ヂ?lián)網(wǎng)后端目前cms是主流的垃圾回收器
G1 跨新生代和老年代;標(biāo)記整理+化整為零   并行與并發(fā)收集器 JDK1.7才正式引入,采用分區(qū)回收的思維,基本不犧牲吞吐量的前提下完成低停頓的內(nèi)存回收;可預(yù)測(cè)的停頓是其最大的優(yōu)勢(shì)

本文轉(zhuǎn)載自微信公眾號(hào)「Java識(shí)堂」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java識(shí)堂公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: Java識(shí)堂
相關(guān)推薦

2020-03-17 10:24:12

Go語(yǔ)言停止寫(xiě)障礙

2023-08-29 16:26:20

Linux命令行

2011-04-11 09:39:55

對(duì)象實(shí)例

2009-08-11 10:32:23

什么是Groovy

2012-08-27 09:10:05

JVMJava

2009-09-24 18:29:12

2021-09-16 14:26:32

網(wǎng)絡(luò)9.11網(wǎng)絡(luò)攻擊網(wǎng)絡(luò)安全

2010-12-11 23:17:00

開(kāi)源陷阱MeeGo

2024-02-22 10:17:39

AI模型

2014-09-17 11:45:20

iOS編程App運(yùn)作

2020-10-21 14:54:02

RustGolang開(kāi)發(fā)

2021-06-30 14:11:01

JVM對(duì)象池Java

2022-01-24 16:56:47

數(shù)字盧布數(shù)字錢包貨幣

2011-03-14 09:33:35

Mono

2017-11-28 16:57:18

2021-07-30 15:48:42

Wi-Fi 6EWi-Fi網(wǎng)絡(luò)

2011-08-08 15:43:01

MySQL索引

2013-12-11 09:50:55

金融大數(shù)據(jù)大數(shù)據(jù)

2021-11-26 09:00:00

數(shù)據(jù)庫(kù)數(shù)據(jù)集工具

2021-04-12 07:34:03

Java集合框架
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)