摘要:堆和方法區(qū)只有在程序運(yùn)行時才能確定內(nèi)存的使用情況,垃圾回收器所關(guān)注的主要就是這部分內(nèi)存。虛擬機(jī)會根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動態(tài)調(diào)整比率參數(shù)以提供最合適的停頓時間或最大的吞吐量。
Tip:內(nèi)容為對《深入理解Java虛擬機(jī)》(周志明 著)第三章內(nèi)容的總結(jié)和筆記。這是第一次拜讀時讀到的一些重點,做個分享,也為后面再次閱讀和實踐做保障。
3.1 概述程序計數(shù)器、虛擬機(jī)棧、本地方法棧三個區(qū)域跟隨線程的生命周期,棧中的棧幀隨方法的進(jìn)出而有序的進(jìn)行出棧和入棧,每一個棧幀分配多少內(nèi)存基本上是在類節(jié)后確定下來時就已知的。
Java堆和方法區(qū)只有在程序運(yùn)行時才能確定內(nèi)存的使用情況,垃圾回收器所關(guān)注的主要就是這部分內(nèi)存。
在堆中,尤其是在新生代中,常規(guī)應(yīng)用進(jìn)行一次垃圾收集一般可以回收70%~95%的空間,而永久代的垃圾收集效率遠(yuǎn)低于此。
3.2 如何判斷對象是否存活 3.2.1 引用計數(shù)算法給對象添加一個引用計數(shù)器,每當(dāng)有地方引用它時,計數(shù)器就加一;當(dāng)引用失效時,計數(shù)器就減一;任何時刻計數(shù)器為0的對象就是不可能被再被使用的。
引用計數(shù)算法實現(xiàn)簡單,判定效率也很高;但是它很難解決對象間相互循環(huán)引用的問題。JVM 沒有選用這種方法管理內(nèi)存。
3.2.2 可達(dá)性分析法算法的基本思路就是通過一系列的稱為“ GC ROOT ”的對象作為起始點,從這些節(jié)點開始向下搜索。從圖論的角度看,當(dāng)一個對象到 GC ROOT 不可達(dá)的時候這個對象就是不可用的;反之則是可用的。
在 Java 中,可以作為 GC ROOT 的對象包括以下4種:
虛擬機(jī)棧(棧幀中的本地變量表)中引用的對象;
方法區(qū)中類靜態(tài)屬性引用的對象;
方法區(qū)中常量引用的對象;
本地方法棧中 JNI(即 Native 方法)引用的對象。
3.2.3 Java 的四種引用在 JDK1.2 后,Java 對引用概念擴(kuò)充,分為強(qiáng)引用、軟引用、弱引用、虛引用。強(qiáng)度漸弱。
強(qiáng)引用
就是值在程序代碼之中普遍存在的,類似 Object obj = new Object() 這類的引用,只要強(qiáng)引用還在,垃圾收集器永遠(yuǎn)不會回收掉被引用的對象。
軟引用
它關(guān)聯(lián)著的對象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把這些對象列進(jìn)回收范圍內(nèi)進(jìn)行第二次回收。提供 SoftReference 類來實現(xiàn)軟引用。
弱引用
強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前。提供 WeakReference 類來實現(xiàn)軟引用。
虛引用
一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來去的一個對象實例。為一個對象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知。提供 PhantomReference 類來實現(xiàn)軟引用。
即使在可達(dá)性分析算法中不可達(dá)的對象,也并不是立即就會被銷毀,它至少要經(jīng)歷兩次標(biāo)記過程:
一個對象在分析后發(fā)現(xiàn)是不可達(dá)的,它將會被第一次標(biāo)記并且進(jìn)行一次篩選,篩選的條件是此對象是否有必要執(zhí)行 finalize() 方法。當(dāng)對象沒有覆蓋 finalize() 方法,或者 finalize() 方法已經(jīng)被調(diào)用過,虛擬機(jī)將認(rèn)為它沒必要再執(zhí)行。即任何一個對象的 finalize() 方法都只會被系統(tǒng)自動調(diào)用一次。
如果對象判定認(rèn)為有必要執(zhí)行 finalize() 方法,那么這個對象會進(jìn)入 F-Queue 隊列中,并在稍后由一個虛擬機(jī)自動建立的、低優(yōu)先級的 Finalize 線程去執(zhí)行它。這里的“執(zhí)行”是指虛擬機(jī)會觸發(fā)這個方法,但是不一定會等它結(jié)束(防止阻塞)。然后 GC 會將 F-Queue 中的對象進(jìn)行第二次標(biāo)記,如果在 finalize 方法中與 GC ROOT 建立關(guān)聯(lián),那么它將被移除“即將回收”的集合。
3.2.5 方法區(qū)回收永久代中的垃圾收集主要回收兩部分的內(nèi)容:廢棄常量和無用的類。
廢棄常量的回收和堆中對象的回首方法類似。
類需要滿足下面3個條件才算是“無用的類”。滿足這些條件只是可以被回收,是否肯定回收還有虛擬機(jī)的一些參數(shù)控制。
該類的所有實例都已經(jīng)被回收;
加載該類的 ClassLoader 已經(jīng)被回收;
該類對應(yīng)的 java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
3.3 垃圾收集算法 3.3.1 標(biāo)記-清除算法思想:算法分為“標(biāo)記”和“清除”兩個階段。
首先標(biāo)記出所有需要回收的對象;
標(biāo)記完后同意回收所有被標(biāo)記的對象。
這種算法有兩個不足:
一是效率問題,標(biāo)記和清除兩個過程效率都不太高;
二是空間問題,空間會碎片化。
3.3.2 復(fù)制算法復(fù)制算法是為了解決效率問題,也解決了內(nèi)存碎片的問題。
思想:
它將可用的內(nèi)存分為相等的兩塊;
每次只用一塊;
GC 時將還存活的對象復(fù)制到另一塊上面,然后全面清理使用過的內(nèi)存。
代價是將內(nèi)存縮小為原來的一半。
3.3.3 標(biāo)記-整理算法思想:
標(biāo)記過程跟“標(biāo)記-清除”算法一樣;
然后讓所有存活的對象都向一端移動,然后直接清理掉邊界以外的內(nèi)存。
3.3.4 分代收集算法當(dāng)前商業(yè)虛擬機(jī)的垃圾收集都采用“分代收集”算法。
思想:
根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊。(一般是把 Java 堆分為新生代和老年代)
新生代中,采用復(fù)制算法回收。因為新生代中對象98%是很短暫的。所以不必按照1:1劃分內(nèi)存,而是將內(nèi)存分為一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一個 Survivor ?;厥諘r將存活的對象復(fù)制到另一塊 Survivor 中,然后清理 Eden 和使用過的 Survivor 空間。HotSpot 中 Eden 和 Survivor 比例為8:1。當(dāng) Survivior 空間不夠時,需要依賴其它內(nèi)存(老年代)進(jìn)行分配擔(dān)保。
老年代中因為對象存活率高、沒有額外的空間對它進(jìn)行分配擔(dān)保,就必須使用“標(biāo)記-清理”或“標(biāo)記-整理”算法進(jìn)行回收。
3.4 HotSpot 的算法實現(xiàn) 3.4.1 枚舉根節(jié)點可作為 GC ROOT 的節(jié)點主要在全局性的引用(eg.常量、類靜態(tài)屬性)和執(zhí)行上下文(eg.棧幀中的本地變量表)。
在可達(dá)性分析時,執(zhí)行系統(tǒng)需要在一個一致性的快照中(一致性的意思就是說在分析期間執(zhí)行系統(tǒng)中對象引用關(guān)系不可以還在不斷變化,否則分析準(zhǔn)確性無法保證)。這是 GC 進(jìn)行時必須停頓所有 JAVA 執(zhí)行線程的一個重要原因。
準(zhǔn)確式 GC:虛擬機(jī)可以知道內(nèi)存中某個位置的數(shù)據(jù)具體是什么類型。這樣在 GC 的時候虛擬機(jī)能準(zhǔn)確的判斷堆上的數(shù)據(jù)是否還可能被使用。
在 HotSpot 中一組 OopMap 的數(shù)據(jù)結(jié)構(gòu)來記錄哪些地方存放著對象引用。(普通對象指針 Ordinary Object Pointer)
3.4.2 安全點可能導(dǎo)致引用關(guān)系變化(Oop 內(nèi)容變化)的指令很多,為每條指令生成 OopMap 是不現(xiàn)實的。
HotSpot 只有在安全點才能暫停。
安全點的選定基本上是以“是否具有讓程序長時間執(zhí)行的特征”為標(biāo)準(zhǔn)進(jìn)行選定的。
安全點的另一個要處理的問題是:如何在 GC 發(fā)生時讓所有的線程都到最近的安全點停下來。這里有兩種方案:
搶斷式中斷:不需要線程主動配合,GC 發(fā)生時,先中斷所有線程,發(fā)現(xiàn)有線程不在安全點上再恢復(fù)它,讓它運(yùn)行至安全點。
主動式中斷:當(dāng)需要 GC 時,僅僅在安全點設(shè)置一個標(biāo)志,各線程執(zhí)行時主動去輪詢這個標(biāo)志,發(fā)現(xiàn)為真就中斷掛起。
3.4.3 安全區(qū)域當(dāng)線程處于 Sleep 狀態(tài)或者 Blocked 狀態(tài)時,它無法響應(yīng) JVM 的中斷請求,此時需要安全區(qū)域來解決。
安全區(qū)域就是指在一段代碼片段中,引用關(guān)系不會發(fā)生變化,在這個區(qū)域中的任何地方開始 GC 都是安全的。
當(dāng)線程要離開安全區(qū)域時,她要檢查系統(tǒng)是否已經(jīng)完成了根節(jié)點枚舉,完成才能離開。
3.5 垃圾收集器 3.5.1 HotSpot 虛擬機(jī)的垃圾收集器 3.5.2 Serial 收集器收集器采用復(fù)制算法;
這個收集器是一個單線程的收集器;
它只會使用一個 CPU一個線程去完成垃圾收集工作;
它在進(jìn)行垃圾收集時,必須暫停其它所有的工作線程,直到它收集結(jié)束!
3.5.3 ParNew 收集器收集器采用復(fù)制算法;
ParNew 收集器就是 Serial 收集器的多線程版本;
目前只有它能與 CMS 收集器配合工作;
HotSpot 虛擬機(jī)中第一款真正意義上的并發(fā)收集器;
3.5.4 Parallel Scavenge(清除) 收集器Parallel Scavenge 收集器是一個新生代收集器,采用復(fù)制算法,也是一個并行的多線程收集器;
它不同于其它收集器的特點是:
跟其它收集器關(guān)注點不一樣,CMS 等收集器目標(biāo)是盡可能縮短垃圾收集時用戶線程的停頓時間;PS 收集器的目標(biāo)是吞吐量優(yōu)先(吞吐量 = 運(yùn)行用戶代碼時間 / (運(yùn)行用戶代碼時間 + 垃圾收集時間));
GC 自適應(yīng)調(diào)整策略。虛擬機(jī)會根據(jù)當(dāng)前系統(tǒng)的運(yùn)行情況收集性能監(jiān)控信息,動態(tài)調(diào)整 GC 比率參數(shù)以提供最合適的停頓時間或最大的吞吐量。
3.5.5 Serial Old 收集器Serial Old 是 Serial 的老年代版本;
它是一個單線程收集器;
采用“標(biāo)記-整理”算法;
在 Server 模式下,它還有兩大用途:
在 JDK1.5 之前與 Parallel Scavenge 收集器配合使用;
作為 CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生 CMF(Concurrent Mode Failure)時使用。
3.5.6 Parallel Old 收集器Parallel Old 是 Parallel Scavenge收集器的老年代版本;
是一個多線程收集器;
采用“標(biāo)記-整理”算法;
在注重吞吐量以及 CPU 資源敏感的場合,可以考慮 Parallel Scavenge 加 Parallel Old 收集器。
3.5.7 CMS 收集器CMS 收集器的目標(biāo)是獲取最短回收停頓時間,適合重視服務(wù)的響應(yīng)速度的場合。
采用“標(biāo)記-清除”算法;
CMS 收集器的運(yùn)行過程;
初始標(biāo)記(需要 Stop The World):標(biāo)記 GC ROOT 直接關(guān)聯(lián)到的對象。
并發(fā)標(biāo)記:進(jìn)行 GC ROOT 的 Tracing 過程,即可達(dá)性分析。
重新標(biāo)記(需要 Stop The World):修正并發(fā)標(biāo)記階段因用戶繼續(xù)運(yùn)行導(dǎo)致的標(biāo)記的變動。時間一般比初始標(biāo)記長,比并發(fā)標(biāo)記短。
并發(fā)清除:根據(jù)標(biāo)記清除內(nèi)存。
整個過程中耗時最長的并發(fā)標(biāo)記和并發(fā)清除過程收集線程都可以和用戶線程一起工作。
CMS 收集器也有三個明顯的缺點:
CMS 收集器對 CPU 資源非常敏感。
雖然不會導(dǎo)致用戶線程停頓,但是會占用一部分 CPU 資源而導(dǎo)致程序變慢?;厥站€程數(shù)是 (CPU 數(shù)量 + 3) / 4。
CMS 收集器無法處理浮動垃圾(并發(fā)清理階段產(chǎn)生的垃圾)。
浮動垃圾要留到下次 GC 時再進(jìn)行清理。因為并行清理時用戶線程還在運(yùn)行,所以要預(yù)留一部分老年代空間提供線程使用(JDK1.6 為92%時開始)。如果預(yù)留無法滿足要求,會出現(xiàn)“Concurrent Mode Failure”錯誤,此時需要臨時啟用 Serial Old 收集器來進(jìn)行老年代垃圾收集。
CMS 收集器會產(chǎn)生大量空間碎片。
3.5.8 G1 收集器G1 收集器是一款面向服務(wù)端應(yīng)用的垃圾收集器;
與其它收集器相比有以下特點:
并行與并發(fā)
能充分利用多 CPU、多核環(huán)境下的硬件優(yōu)勢。
分代收集
G1 收集器獨立管理整個 GC 堆,但依然有分代的概念,用不同的方式處理新老對象。
空間整理
G1 整體上是基于“標(biāo)記-整理”算法的,從局部(兩個 Region 之間)看是基于“復(fù)制”算法的,都不會產(chǎn)生內(nèi)存碎片。
可預(yù)測的停頓
G1 可以建立可預(yù)測的停頓時間模型。
實現(xiàn)思想:
它將整個 JAVA 內(nèi)存堆劃分為多個大小相等的獨立區(qū)域(Region);
G1 跟蹤各個 Region 里面的垃圾堆積的價值大小,優(yōu)先回收價值最大的 Region。
在 G1 收集器的 Region 之間的對象引用以及其它收集器中的新生代與老年代之間的對象引用,虛擬機(jī)都是使用 Remembered Set 來避免全堆掃描的。G1 中每個 Region 都有一個與之對應(yīng)的 Remembered Set .
運(yùn)行過程:
初始標(biāo)記:標(biāo)記 GC ROOT 直接關(guān)聯(lián)到的對象,并且修改 TAMS(Next Top at Mark Start)的值,以便下次用戶程序運(yùn)行時,能在正確的可用的 Region 中創(chuàng)建對象。
并發(fā)標(biāo)記:進(jìn)行可達(dá)性分析。
最終標(biāo)記:修正并發(fā)標(biāo)記階段因用戶繼續(xù)運(yùn)行導(dǎo)致的標(biāo)記的變動。更新 Remembered Set。
篩選回收:對各個 Region 回收價值排序,然后根據(jù)用戶期望的 GC 停頓時間來制定回收計劃。
3.6 內(nèi)存分配與回收策略 3.6.1 對象優(yōu)先在 Eden 分配大多數(shù)情況下,對象在新生代 Eden 區(qū)中分配。當(dāng) Eden 區(qū)沒有足夠空間進(jìn)行分配時,虛擬機(jī)將發(fā)起一次 Minor GC(新生代 GC)。
3.6.2 大對象直接進(jìn)入老年代典型的大對象就是那種很長的字符串以及數(shù)組。
虛擬機(jī)提供一個 -XX:PretenureSizeThreshold 參數(shù),令大于這個設(shè)置值的對象直接在老年代分配。目的是避免在 Eden 區(qū)及兩個 Survivor 區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。
3.6.3 長期存活的對象將進(jìn)入老年代虛擬機(jī)給每個對象設(shè)置一個對象年齡(AGE)計數(shù)器;
每經(jīng)過一次 Minor GC,年齡增加一歲;當(dāng)?shù)揭欢q數(shù)(默認(rèn)15)后進(jìn)入老年代。
3.6.4 動態(tài)對象年齡判斷為了更好地適應(yīng)不同程序的內(nèi)存狀況,如果在Survivor 空間中相同年齡所有對象大小綜合大于 Survivor 空間的一半,年齡大于或等于該年齡的對象就可以直接進(jìn)入老年代。
3.6.5 空間分配擔(dān)保在發(fā)生 Minor GC 之前,虛擬機(jī)會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果這個條件成立,那么 Minor GC 可以確保是安全的。
如果條件不成立,虛擬機(jī)會查看是否允許擔(dān)保失敗。如果允許,會檢查老年代最大可用的連續(xù)空間是否大于歷次晉升帶老年代對象的平均大小,大于則會進(jìn)行一次 Minor GC;小于或者不允許蛋白失敗則改為進(jìn)行一次 Full GC。
JDK1.6 Update 24后強(qiáng)制允許擔(dān)保失敗,HandlePromotionFailure 參數(shù)不起作用了。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/65375.html
摘要:抽時間重新讀了一遍深入理解一書。驗證確保文件的字節(jié)流中包含的信息符合當(dāng)前虛擬機(jī)的要求,并且不會危害虛擬機(jī)自身的安全??梢娦钥梢娦允侵府?dāng)一個線程修改了共享變量的值,其他線程能夠立即得知這個修改。 抽時間重新讀了一遍《深入理解JVM》一書。以下為摘錄內(nèi)容。 1 java內(nèi)存區(qū)域 showImg(https://segmentfault.com/img/bVboDgk?w=617&h=365...
摘要:的重要性毋庸置疑,可以毫不夸張的說虛擬機(jī)是整個平臺的基石。方面的知識,也一直是等大廠面試考核的重點。本專欄將分為如下幾個大模塊進(jìn)行分析開篇介紹運(yùn)行時數(shù)據(jù)區(qū)。最主要的是讓我知道能得到多少小伙伴的認(rèn)可,畢竟大家的認(rèn)可,就是不懈努力的動力 JVM的重要性毋庸置疑,可以毫不夸張的說Java虛擬機(jī)是整個Java平臺的基石。 JVM方面的知識,也一直是BAT等大廠面試考核的重點。特別是JVM調(diào)優(yōu),...
摘要:但是為了豐富引用的種類,以適應(yīng)各種應(yīng)用,中加入了中引用,但是除了強(qiáng)引用,其生命周期會有所不同,生存能力遞減。加載該類的已被回收。 GC面臨的問題有三個:哪些內(nèi)存需要回收、什么時候回收和怎么回收 哪些內(nèi)存需要回收,一般有兩種方法 引用計數(shù) 對每個對象都有個被引用的次數(shù),單被引用的次數(shù)為0的時候,就表示對象需要被回收 引用計數(shù)的缺點是沒有辦法解決循環(huán)引用導(dǎo)致的內(nèi)存泄露問題 ...
摘要:前言本文內(nèi)容基本摘抄自深入理解虛擬機(jī),以供復(fù)習(xí)之用,沒有多少參考價值。此區(qū)域是唯一一個在虛擬機(jī)規(guī)范中沒有規(guī)定任何情況的區(qū)域。堆是所有線程共享的內(nèi)存區(qū)域,在虛擬機(jī)啟動時創(chuàng)建。虛擬機(jī)上把方法區(qū)稱為永久代。 前言 本文內(nèi)容基本摘抄自《深入理解Java虛擬機(jī)》,以供復(fù)習(xí)之用,沒有多少參考價值。想要更詳細(xì)了解請參考原書。 第二章 1.運(yùn)行時數(shù)據(jù)區(qū)域 showImg(https://segment...
摘要:執(zhí)行引擎作用執(zhí)行字節(jié)碼,或者執(zhí)行本地方法運(yùn)行時數(shù)據(jù)區(qū)其實就是指在運(yùn)行期間,其對內(nèi)存空間的劃分和分配。 雖是讀書筆記,但是如轉(zhuǎn)載請注明出處https://uestc-dpz.github.io..拒絕伸手復(fù)制黨 JVM Java 虛擬機(jī) Java 虛擬機(jī)(Java virtual machine,JVM)是運(yùn)行 Java 程序必不可少的機(jī)制。JVM實現(xiàn)了Java語言最重要的特征:即平臺...
閱讀 1410·2021-11-24 09:38
閱讀 2120·2021-09-22 15:17
閱讀 2444·2021-09-04 16:41
閱讀 3549·2019-08-30 15:56
閱讀 3548·2019-08-29 17:19
閱讀 1996·2019-08-28 18:09
閱讀 1277·2019-08-26 13:35
閱讀 1745·2019-08-23 17:52