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

講講 JVM 的內(nèi)存管理『非專業(yè)』

云計算 虛擬化
在 JDK 1.7 及以前,HotSpot 虛擬機(jī)中的方法區(qū)是用永久代實現(xiàn)的,永久代中存放的為一些 Class 的信息、常量、靜態(tài)變量等數(shù)據(jù)。

[[399153]]

 jvm 內(nèi)存布局

一類是每個線程所獨享的:

  1. PC Register:也稱為程序計數(shù)器, 記錄每個線程當(dāng)前執(zhí)行的指令信息(eg:當(dāng)前執(zhí)行到哪一條指令,下一條該取哪條指令)
  2. JVM Stack:也稱為虛擬機(jī)棧,記錄每個棧幀(Frame)中的局部變量、方法返回地址等。線程中每次有方法調(diào)用時,會創(chuàng)建Frame,方法調(diào)用結(jié)束時Frame銷毀。
  3. Native Method Stack: 本地(原生)方法棧,顧名思義就是調(diào)用操作系統(tǒng)原生本地方法時,所需要的內(nèi)存區(qū)域。

上述3類區(qū)域,生命周期與Thread相同,即:線程創(chuàng)建時,相應(yīng)的內(nèi)存區(qū)創(chuàng)建,線程銷毀時,釋放相應(yīng)內(nèi)存。

  • Heap:即鼎鼎大名的堆內(nèi)存區(qū),也是GC垃圾回收的主站場,用于存放類的實例對象及Arrays實例等。

注:Heap被所有線程共享,如果嚴(yán)格意義上摳字眼的話,也不完正確,事實上,由于TLAB的存在,為了防止并發(fā)對象分配時,多個對象分配到同1塊內(nèi)存,heap中的TLAB區(qū)域,在分配時,是被線程獨占寫入的。

  • Method Area:方法區(qū),主要存放類結(jié)構(gòu)、類成員定義,static靜態(tài)成員等。
  • Runtime Constant Pool:運行時常量池,比如:字符串,int -128~127范圍的值等,它是Method Area中的一部分。

Heap、Method Area 都是在虛擬機(jī)啟動時創(chuàng)建,虛擬機(jī)退出時釋放

哪些內(nèi)存區(qū)域需要 GC

thread獨享的區(qū)域:PC Regiester、JVM Stack、Native Method Stack,其生命周期都與線程相同(即:與線程共生死),所以無需GC。線程共享的Heap區(qū)、Method Area則是GC關(guān)注的重點對象。

引用類型

強(qiáng)引用:被強(qiáng)引用關(guān)聯(lián)的對象不會被回收。

軟引用:被軟引用關(guān)聯(lián)的對象只有在內(nèi)存不夠的情況下才會被回收。

弱引用:被弱引用關(guān)聯(lián)的對象一定會被回收,也就是說它只能存活到下一次垃圾回收發(fā)生之前。

虛引用:為一個對象設(shè)置虛引用的唯一目的是能在這個對象被回收時收到一個系統(tǒng)通知。

Minor GC 和 Full GC

Minor GC:回收新生代,因為新生代對象存活時間很短,因此 Minor GC 會頻繁執(zhí)行,執(zhí)行的速度一般也會比較快。

Minor GC,其觸發(fā)條件非常簡單,當(dāng) Eden 空間滿時,就將觸發(fā)一次 Minor GC。

Full GC:回收老年代和新生代,老年代對象其存活時間長,因此 Full GC 很少執(zhí)行,執(zhí)行速度會比 Minor GC 慢很多。

FULL GC 的觸發(fā)條件有以下幾個:

「調(diào)用 System.gc()」

只是建議虛擬機(jī)執(zhí)行 Full GC,但是虛擬機(jī)不一定真正去執(zhí)行。不建議使用這種方式,而是讓虛擬機(jī)管理內(nèi)存。

「老年代空間不足」

老年代空間不足的常見場景為前文所講的大對象直接進(jìn)入老年代、長期存活的對象進(jìn)入老年代等。

為了避免以上原因引起的 Full GC,1.應(yīng)當(dāng)盡量不要創(chuàng)建過大的對象以及數(shù)組。2.除此之外,可以通過 -Xmn 虛擬機(jī)參數(shù)調(diào)大新生代的大小,讓對象盡量在新生代被回收掉,不進(jìn)入老年代。3.還可以通過 -XX:MaxTenuringThreshold 調(diào)大對象進(jìn)入老年代的年齡,讓對象在新生代多存活一段時間。

「空間分配擔(dān)保失敗」

使用復(fù)制算法的 Minor GC 需要老年代的內(nèi)存空間作擔(dān)保,如果擔(dān)保失敗會執(zhí)行一次 Full GC。

「JDK 1.7 及以前的永久代空間不足」

在 JDK 1.7 及以前,HotSpot 虛擬機(jī)中的方法區(qū)是用永久代實現(xiàn)的,永久代中存放的為一些 Class 的信息、常量、靜態(tài)變量等數(shù)據(jù)。

當(dāng)系統(tǒng)中要加載的類、反射的類和調(diào)用的方法較多時,永久代可能會被占滿,在未配置為采用 CMS GC 的情況下也會執(zhí)行 Full GC。如果經(jīng)過 Full GC 仍然回收不了,那么虛擬機(jī)會拋出 java.lang.OutOfMemoryError。

為避免以上原因引起的 Full GC,可采用的方法為增大永久代空間或轉(zhuǎn)為使用 CMS GC。

「Concurrent Mode Failure」

執(zhí)行 CMS GC 的過程中同時有對象要放入老年代,而此時老年代空間不足(可能是 GC 過程中浮動垃圾過多導(dǎo)致暫時性的空間不足),便會報 Concurrent Mode Failure 錯誤,并觸發(fā) Full GC。

如何判斷對象是垃圾

引用計數(shù)算法

在兩個對象出現(xiàn)循環(huán)引用的情況下,此時引用計數(shù)器永遠(yuǎn)不為 0,導(dǎo)致無法對它們進(jìn)行回收。正是因為循環(huán)引用的存在,因此 Java 虛擬機(jī)不使用引用計數(shù)算法。

  • 可達(dá)性分析算法

以 GC Roots 為起始點進(jìn)行搜索,可達(dá)的對象都是存活的,不可達(dá)的對象可被回收。

Java 虛擬機(jī)使用該算法來判斷對象是否可被回收,GC Roots 一般包含以下內(nèi)容:

  • 虛擬機(jī)棧中局部變量表中引用的對象
  • 本地方法棧中 JNI 中引用的對象
  • 方法區(qū)中類靜態(tài)屬性引用的對象
  • 方法區(qū)中的常量引用的對象

除了對象回收之外,還可能會有類的卸載

方法區(qū)主要存放永久代對象,而永久代對象的回收率比新生代低很多,所以在方法區(qū)上進(jìn)行回收性價比不高。方法區(qū)的回收主要是對常量池的回收和對類的卸載。

為了避免內(nèi)存溢出,在大量使用反射和動態(tài)代理的場景都需要虛擬機(jī)具備類卸載功能。類的卸載條件很多,需要滿足以下三個條件,并且滿足了條件也不一定會被卸載:

  • 該類所有的實例都已經(jīng)被回收,此時堆中不存在該類的任何實例。
  • 加載該類的 ClassLoader 已經(jīng)被回收。
  • 該類對應(yīng)的 Class 對象沒有在任何地方被引用,也就無法在任何地方通過反射訪問該類方法。

finalize()

  • 類似 C++ 的析構(gòu)函數(shù),用于關(guān)閉外部資源。但是 try-finally 等方式可以做得更好,并且該方法運行代價很高,不確定性大,無法保證各個對象的調(diào)用順序,因此最好不要使用。
  • 當(dāng)一個對象可被回收時,如果需要執(zhí)行該對象的 finalize() 方法,那么就有可能在該方法中讓對象重新被引用,從而實現(xiàn)自救。自救只能進(jìn)行一次,如果回收的對象之前調(diào)用了 finalize() 方法自救,后面回收時不會再調(diào)用該方法。

常用的 GC 算法

「標(biāo)記清除法」:在標(biāo)記階段,程序會檢查每個對象是否為活動對象,如果是活動對象,則程序會在對象頭部打上標(biāo)記。

在清除階段,會進(jìn)行對象回收并取消標(biāo)志位,另外,還會判斷回收后的分塊與前一個空閑分塊是否連續(xù),若連續(xù),會合并這兩個分塊。回收對象就是把對象作為分塊,連接到被稱為 “空閑鏈表” 的單向鏈表,之后進(jìn)行分配時只需要遍歷這個空閑鏈表,就可以找到分塊。

優(yōu)缺點:

  • 標(biāo)記和清除過程效率都不高;
  • 會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導(dǎo)致無法給大對象分配內(nèi)存。

「標(biāo)記復(fù)制法」:思路也很簡單,將內(nèi)存對半分,總是保留一塊空著(上圖中的右側(cè)),將左側(cè)存活的對象(淺灰色區(qū)域)復(fù)制到右側(cè),然后左側(cè)全部清空。

優(yōu)缺點:

  • 避免了內(nèi)存碎片問題。
  • 內(nèi)存浪費很嚴(yán)重,相當(dāng)于只能使用50%的內(nèi)存。

現(xiàn)在的商業(yè)虛擬機(jī)都采用這種收集算法回收新生代,但是并不是劃分為大小相等的兩塊,而是一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一塊 Survivor。在回收時,將 Eden 和 Survivor 中還存活著的對象全部復(fù)制到另一塊 Survivor 上,最后清理 Eden 和使用過的那一塊 Survivor。

HotSpot 虛擬機(jī)的 Eden 和 Survivor 大小比例默認(rèn)為 8:1,保證了內(nèi)存的利用率達(dá)到 90%。如果每次回收有多于 10% 的對象存活,那么一塊 Survivor 就不夠用了,此時需要依賴于老年代進(jìn)行空間分配擔(dān)保,也就是借用老年代的空間存儲放不下的對象。

「標(biāo)記-整理(也稱標(biāo)記-壓縮)法」:避免了上述二種算法的缺點,將垃圾對象清理掉后,同時將剩下的存活對象進(jìn)行整理挪動(類似于windows的磁盤碎片整理),保證它們占用的空間連續(xù),這樣就避免了內(nèi)存碎片問題,但是整理過程也會降低GC的效率.

「generation-collect 分代收集算法」:經(jīng)過大量實際分析,發(fā)現(xiàn)內(nèi)存中的對象,大致可以分為二類:有些生命周期很短,比如一些局部變量/臨時對象,而另一些則會存活很久(典型的,比如websocket長連接中的connection對象)。基本思想是將內(nèi)存分成了三大塊:年青代(Young Genaration),老年代(Old Generation),永久代(Permanent Generation),其中Young Genaration更是又細(xì)為分eden,S0, S1三個區(qū)。

剛開始時,對象分配在eden區(qū),s0及s1區(qū),幾乎是空著的。當(dāng)eden區(qū)放不下時,就會發(fā)生minor GC(也被稱為young GC),第1步當(dāng)然是要先標(biāo)識出不可達(dá)垃圾對象,然后講可達(dá)對象移到 s0 區(qū)。之后當(dāng) eden 區(qū)又滿了之后,s0 和 eden 區(qū)的可達(dá)對象將會都移到 s1 區(qū)。之后 s0 和 s1 區(qū)的對象會相互移來移去,每移動 1 次,他們的年齡會 +1。所以當(dāng)它們的年齡到達(dá)一定區(qū)域之后,將會移到老年代。如果老年代也滿了,那么將會移到永久代。

  • 新生代使用:復(fù)制算法
  • 老年代使用:標(biāo)記 - 清除 或者 標(biāo)記 - 整理 算法

垃圾收集器

https://www.jianshu.com/p/b572f69a1b93

**新生代垃圾收集器有Serial、ParNew、Parallel Scavenge,G1,屬于老年代的垃圾收集器有CMS、Serial Old、Parallel Old和G1。**其中的G1是一種既可以對新生代對象也可以對老年代對象進(jìn)行回收的垃圾收集器。然而,在所有的垃圾收集器中,并沒有一種普遍使用的垃圾收集器。在不同的場景下,每種垃圾收集器有各自的優(yōu)勢,如下圖:

  • 「Serial收集器」

**單線程垃圾收集器,**這就意味著在其進(jìn)行垃圾收集的時候需要暫停其他的線程。

收集過程:暫停所有線程 算法:復(fù)制算法 優(yōu)點:簡單高效,擁有很高的單線程收集效率 應(yīng)用:Client模式下的默認(rèn)新生代收集器

  • 「ParNew收集器」

理解為**Serial收集器的多線程版本,由于存在線程切換的開銷,**ParNew在單CPU的環(huán)境中比不上Serial(ParNew收集線程數(shù)與CPU的數(shù)量相同, 因此在CPU數(shù)量過大的環(huán)境中, 可用-XX:ParallelGCThreads參數(shù)控制GC線程數(shù))。

收集過程:暫停所有線程 算法:復(fù)制算法 優(yōu)點:在CPU多的情況下,擁有比Serial更好的效果。單CPU環(huán)境下Serial效果更好 應(yīng)用:許多運行在Server模式下的虛擬機(jī)中首選的新生代收集器

  • 「Parallel Scavenge 收集器」

類似ParNew收集器,**Parallel收集器更關(guān)注系統(tǒng)的吞吐量。**區(qū)別在于Parallel Scavenge收集器更關(guān)注可控制的吞吐量(「吞吐量 = 運行用戶代碼的時間/(運行用戶代碼的時間+垃圾收集時間)」)。吞吐量越大,意味著垃圾收集的時間越短,則用戶代碼則可以充分利用CPU資源,盡快完成程序的運算任務(wù)。

-XX:MaxGCPauseMillis 控制最大的垃圾收集停頓時間,-XX:GCRatio 直接設(shè)置吞吐量的大小。

-XX:+UseAdaptiveSizePocily 來動態(tài)調(diào)整停頓時間或者最大的吞吐量,這種方式稱為GC自適應(yīng)調(diào)節(jié)策略,這點是ParNew收集器所沒有的。

  • 「Serial Old收集器」

「Serial Old收集器是Serial收集器的老年代版本」,也是一個單線程收集器,采用“「標(biāo)記-整理算法」”進(jìn)行回收。其運行過程與Serial收集器一樣。

  • 「Parallel Old收集器」

Parallel Old收集器是Parallel Scavenge收集器的老年代版本,使用多線程和“「標(biāo)記-整理」”算法進(jìn)行垃圾回收。

通常與Parallel Scavenge收集器配合使用,“吞吐量優(yōu)先”收集器是這個組合的特點,在注重吞吐量和CPU資源敏感的場合,都可以使用這個組合。

  • 「CMS 收集器」

CMS(Concurrent Mark Sweep)收集器是一種「以獲取最短回收停頓時間為目標(biāo)的收集器」。目前很大一部分的Java應(yīng)用都集中在互聯(lián)網(wǎng)站或B/S系統(tǒng)的服務(wù)端上,這類應(yīng)用尤其重視服務(wù)的響應(yīng)速度,希望系統(tǒng)停頓時間最短,以給用戶帶來較好的體驗。

「基于“標(biāo)記-清除”算法實現(xiàn)的」,它的運作過程相對于前面幾種收集器來說要更復(fù)雜一些,整個過程分為4個步驟,包括:

其中**初始標(biāo)記、重新標(biāo)記這兩個步驟仍然需要“Stop The World”。**初始標(biāo)記僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對象,速度很快,并發(fā)標(biāo)記階段就是進(jìn)行GC Roots Tracing的過程,而重新標(biāo)記階段則是為了修正并發(fā)標(biāo)記期間,因用戶程序繼續(xù)運作而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄,這個階段的停頓時間一般會比初始標(biāo)記階段稍長一些,但遠(yuǎn)比并發(fā)標(biāo)記的時間短。

「由于整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程中,收集器線程都可以與用戶線程一起工作,所以總體上來說,CMS收集器的內(nèi)存回收過程是與用戶線程一起并發(fā)地執(zhí)行?!?/p>

優(yōu)缺點:

  • 并發(fā)收集、低停頓
  • 產(chǎn)生大量空間碎片、并發(fā)階段會降低吞吐量
  • 初始標(biāo)記(CMS initial mark)
  • 并發(fā)標(biāo)記(CMS concurrent mark)
  • 重新標(biāo)記(CMS remark)
  • 并發(fā)清除(CMS concurrent sweep)

「G1 收集器」(整個Java堆:包括新生代和老年代)

G1 的特點是:采用并發(fā)與并行、空間整合(整體上類似標(biāo)記-整理方法,不會產(chǎn)生內(nèi)存碎片)、分代收集、「可預(yù)測的停頓」(比CMS更先進(jìn)的地方在于能讓使用者明確指定一個長度為M毫秒的時間片段內(nèi),消耗在垃圾收集上的時間不得超過N毫秒)。

G1收集器將Java堆劃分為多個大小相等的Region(獨立區(qū)域),新生代與老年代都是一部分Region的集合,G1的收集范圍則是這一個個Region。

整個工作流程:初始標(biāo)記、并發(fā)標(biāo)記、最終標(biāo)記、篩選回收。初始標(biāo)記階段僅僅只是標(biāo)記一下GC Roots能夠直接關(guān)聯(lián)的對象,并且修改TAMS(Next Top at Mark Start)的值,讓下一階段的用戶程序并發(fā)運行的時候,能在正確可用的Region中創(chuàng)建對象,這個階段需要暫停線程。并發(fā)標(biāo)記階段從GC Roots進(jìn)行可達(dá)性分析,找出存活的對象,這個階段是與用戶線程并發(fā)執(zhí)行的。最終標(biāo)記階段則是修正在并發(fā)標(biāo)記階段因為用戶程序的并發(fā)執(zhí)行而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分記錄,這部分記錄被保存在Remembered Set Logs中,最終標(biāo)記階段再把Logs中的記錄合并到Remembered Set中,這個階段是并行執(zhí)行的,仍然需要暫停用戶線程。最后在篩選階段首先對各個Region的回收價值和成本進(jìn)行排序,根據(jù)用戶所期望的GC停頓時間制定回收計劃。

內(nèi)存分配策略

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

大多數(shù)情況下,對象在新生代 Eden 上分配,當(dāng) Eden 空間不夠時,發(fā)起 Minor GC。

  • 大對象直接進(jìn)入老年代

大對象是指需要連續(xù)內(nèi)存空間的對象,最典型的大對象是那種很長的字符串以及數(shù)組。

經(jīng)常出現(xiàn)大對象會提前觸發(fā)垃圾收集以獲取足夠的連續(xù)空間分配給大對象。

-XX:PretenureSizeThreshold,大于此值的對象直接在老年代分配,避免在 Eden 和 Survivor 之間的大量內(nèi)存復(fù)制。

  • 長期存活的對象進(jìn)入老年代

為對象定義年齡計數(shù)器,對象在 Eden 出生并經(jīng)過 Minor GC 依然存活,將移動到 Survivor 中,年齡就增加 1 歲,增加到一定年齡則移動到老年代中。

-XX:MaxTenuringThreshold 用來定義年齡的閾值。

  • 動態(tài)對象年齡判斷

虛擬機(jī)并不是永遠(yuǎn)要求對象的年齡必須達(dá)到 MaxTenuringThreshold 才能晉升老年代,如果在 Survivor 中相同年齡所有對象大小的總和大于 Survivor 空間的一半,則年齡大于或等于該年齡的對象可以直接進(jìn)入老年代,無需等到 MaxTenuringThreshold 中要求的年齡。

  • 空間分配擔(dān)保

在發(fā)生 Minor GC 之前,虛擬機(jī)先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果條件成立的話,那么 Minor GC 可以確認(rèn)是安全的。

如果不成立的話虛擬機(jī)會查看 HandlePromotionFailure 的值是否允許擔(dān)保失敗,如果允許那么就會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試著進(jìn)行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值不允許冒險,那么就要進(jìn)行一次 Full GC。

巨人的肩膀

https://github.com/CyC2018/CS-Notes

本文轉(zhuǎn)載自微信公眾號「多選參數(shù)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系多選參數(shù)公眾號。

 

責(zé)任編輯:武曉燕 來源: 多選參數(shù)
相關(guān)推薦

2021-04-29 11:18:14

JVM加載機(jī)制

2010-12-10 15:40:58

JVM內(nèi)存管理

2010-09-26 16:42:04

JVM內(nèi)存組成JVM垃圾回收

2010-09-26 13:23:13

JVM內(nèi)存管理機(jī)制

2019-12-10 08:59:55

JVM內(nèi)存算法

2020-07-09 12:50:29

JVM內(nèi)存管理Java

2020-11-06 07:11:40

內(nèi)存虛擬Redis

2010-09-27 13:26:31

JVM內(nèi)存管理機(jī)制

2017-05-04 13:11:28

深度學(xué)習(xí)AI

2009-07-09 09:47:26

Sun JVM

2017-05-05 09:13:07

深度學(xué)習(xí)AI決策樹

2012-05-15 02:04:22

JVMJava

2017-09-20 08:48:09

JVM內(nèi)存結(jié)構(gòu)

2011-12-20 10:43:21

Java

2015-07-16 15:16:41

內(nèi)存泄露解決辦法

2010-08-04 13:30:07

Visual Stud

2010-09-25 15:40:52

配置JVM內(nèi)存

2023-11-19 23:29:22

Heap DumpJava

2012-01-11 10:45:57

JavaJVM

2010-09-25 15:52:27

JVM內(nèi)存JVM
點贊
收藏

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