真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

詳解Java虛擬機(第⑤篇)——垃圾收集

詳解Java 虛擬機(第⑤篇)——垃圾收集

垃圾收集主要是針對堆和方法區(qū)進行。程序計數(shù)器、虛擬機棧和本地方法棧這三個區(qū)域?qū)儆诰€程私有的,只存在于線程的生命周期內(nèi),線程結(jié)束之后就會消失,因此不需要對這三個區(qū)域進行垃圾回收。

專注于為中小企業(yè)提供成都網(wǎng)站建設(shè)、網(wǎng)站制作服務(wù),電腦端+手機端+微信端的三站合一,更高效的管理,為中小企業(yè)奉賢免費做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動了1000+企業(yè)的穩(wěn)健成長,幫助中小企業(yè)通過網(wǎng)站建設(shè)實現(xiàn)規(guī)模擴充和轉(zhuǎn)變。

一、判斷一個對象是否可被回收

1. 引用計數(shù)算法

為對象添加一個引用計數(shù)器,當對象增加一個引用時計數(shù)器加 1,引用失效時計數(shù)器減 1。引用計數(shù)為 0 的對象可被回收。

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

public class Test {
    public Object instance = null;
    public static void main(String[] args) {
        Test a = new Test();
        Test b = new Test();
        a.instance = b;
        b.instance = a;
        a = null;
        b = null;
        doSomething();
    }
}

在上述代碼中,a 與 b 引用的對象實例互相持有了對象的引用,因此當我們把對 a 對象與 b 對象的引用去除之后,由于兩個對象還存在互相之間的引用,導致兩個 Test 對象無法被回收。

  • 優(yōu)點:執(zhí)行效率高,程序執(zhí)行受影響較小。
  • 缺點:無法檢測出循環(huán)引用的情況,引起內(nèi)存泄漏。

2. 可達性分析算法

通過判斷對象的引用鏈是否可達來決定對象是否可以被回收。

以 GC Roots 為起始點進行搜索,可達的對象都是存活的,不可達的對象可被回收。

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

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

3. 方法區(qū)的回收

因為方法區(qū)主要存放永久代對象,而永久代對象的回收率比新生代低很多,所以在方法區(qū)上進行回收性價比不高。

主要是對常量池的回收和對類的卸載。

為了避免內(nèi)存溢出,在大量使用反射和動態(tài)代理的場景都需要虛擬機具備類卸載功能。

類的卸載條件很多,需要滿足以下三個條件,并且滿足了條件也不一定會被卸載:

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

4. finalize()

類似 C++ 的析構(gòu)函數(shù),用于關(guān)閉外部資源。但是 try-finally 等方式可以做得更好,并且該方法運行代價很高,不確定性大,無法保證各個對象的調(diào)用順序,因此最好不要使用。

當一個對象可被回收時,如果需要執(zhí)行該對象的 finalize() 方法,那么就有可能在該方法中讓對象重新被引用,從而實現(xiàn)自救。自救只能進行一次,如果回收的對象之前調(diào)用了 finalize() 方法自救,后面回收時不會再調(diào)用該方法。

Object 的finalize()方法的作用是否與C++的析構(gòu)函數(shù)作用相同?

  • 與C++的析構(gòu)函數(shù)不同,析構(gòu)函數(shù)調(diào)用確定,而finalize()方法是不確定的;
  • 當垃圾回收器要宣告一個對象死亡時,至少要經(jīng)歷兩次標記過程。如果對象在進行可達性分析以后,沒有與GC Root直接相連接的引用量,就會被第一次標記,并且判斷是否執(zhí)行finalize()方法;如果這個對象覆蓋了finalize()方法,并且未被引用,就會被放置于F-Queue隊列,稍后由虛擬機創(chuàng)建的一個低優(yōu)先級的finalize()線程去執(zhí)行觸發(fā)finalize()方法;
  • 由于線程的優(yōu)先級比較低,執(zhí)行過程隨時可能會被終止;
  • 給予對象最后一次重生的機會

二、引用類型

無論是通過引用計數(shù)算法判斷對象的引用數(shù)量,還是通過可達性分析算法判斷對象是否可達,判定對象是否可被回收都與引用有關(guān)。

Java 提供了四種強度不同的引用類型。

1. 強引用

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

使用 new 一個新對象的方式來創(chuàng)建強引用。

Object obj = new Object();

拋出OOM Error終止程序也不會回收具有強引用的對象,只有通過將對象設(shè)置為null來弱化引用,才能使其被回收。

2. 軟引用

表示對象處在有用但非必須的狀態(tài)。

被軟引用關(guān)聯(lián)的對象只有在內(nèi)存不夠的情況下才會被回收??梢杂脕韺崿F(xiàn)內(nèi)存敏感的高速緩存。

使用 SoftReference 類來創(chuàng)建軟引用。

Object obj = new Object();
SoftReference sf = new SoftReference(obj);
obj = null;  // 使對象只被軟引用關(guān)聯(lián)

3. 弱引用

表示非必須的對象,比軟引用更弱一些。適用于偶爾被使用且不影響垃圾收集的對象。

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

使用 WeakReference 類來創(chuàng)建弱引用。

Object obj = new Object();
WeakReference wf = new WeakReference(obj);
obj = null;

4. 虛引用

又稱為幽靈引用或者幻影引用,一個對象是否有虛引用的存在,不會對其生存時間造成影響,也無法通過虛引用得到一個對象。

不會決定對象的生命周期,任何時候都可能被垃圾回收器回收。必須和引用隊列ReferenceQueue聯(lián)合使用。

為一個對象設(shè)置虛引用的唯一目的是能在這個對象被回收時收到一個系統(tǒng)通知,起哨兵作用。具體來說,就是通過判斷引用隊列ReferenceQueue是否加入虛引用來判斷被引用對象是否被GC回收。

使用 PhantomReference 來創(chuàng)建虛引用。

Object obj = new Object();
ReferenceQueue queue = new ReferenceQueue();
PhantomReference pf = new PhantomReference(obj, queue);
obj = null;

引用隊列(ReferenceQueue):當GC(垃圾回收線程)準備回收一個對象時,如果發(fā)現(xiàn)它還僅有軟引用(或弱引用,或虛引用)指向它,就會在回收該對象之前,把這個軟引用(或弱引用,或虛引用)加入到與之關(guān)聯(lián)的引用隊列(ReferenceQueue)中。如果一個軟引用(或弱引用,或虛引用)對象本身在引用隊列中,就說明該引用對象所指向的對象被回收了。無實際的存儲結(jié)構(gòu),存儲邏輯依賴于內(nèi)部節(jié)點之間的關(guān)系來表達。

三、垃圾收集算法

1. 標記 - 清除

在標記階段,從根集合進行掃描,會檢查每個對象是否為活動對象,如果是活動對象,則程序會在對象頭部打上標記。

在清除階段,會進行對象回收并取消標志位,另外,還會判斷回收后的分塊與前一個空閑分塊是否連續(xù),若連續(xù),會合并這兩個分塊?;厥諏ο缶褪前褜ο笞鳛榉謮K,連接到被稱為 “空閑鏈表” 的單向鏈表,之后進行分配時只需要遍歷這個空閑鏈表,就可以找到分塊。

在分配時,程序會搜索空閑鏈表尋找空間大于等于新對象大小 size 的塊 block。如果它找到的塊等于 size,會直接返回這個分塊;如果找到的塊大于 size,會將塊分割成大小為 size 與 (block - size) 的兩部分,返回大小為 size 的分塊,并把大小為 (block - size) 的塊返回給空閑鏈表。

不足:

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

2. 標記 - 整理

讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。

優(yōu)點:

  • 不會產(chǎn)生內(nèi)存碎片

不足:

  • 需要移動大量對象,處理效率比較低。

3. 復制

將內(nèi)存劃分為大小相等的兩塊,每次只使用其中一塊,當這一塊內(nèi)存用完了就將還存活的對象復制到另一塊上面,然后再把使用過的內(nèi)存空間進行一次清理。

主要不足是只使用了內(nèi)存的一半。

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

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

4. 分代收集

Stop-the-World

  • JVM由于要執(zhí)行GC而停止了應(yīng)用程序的執(zhí)行;
  • 任何一種GC算法中都會發(fā)生;
  • 多數(shù)GC優(yōu)化通過減少Stop-the-world發(fā)生的時間來提升程序性能。

Safepoint

分析過程中對象引用關(guān)系不會發(fā)生變化的點;
產(chǎn)生Safepoint的地方:方法調(diào)用;循環(huán)跳轉(zhuǎn);異常跳轉(zhuǎn)等
現(xiàn)在的商業(yè)虛擬機采用分代收集算法,它根據(jù)對象存活周期將內(nèi)存劃分為幾塊,不同塊采用適當?shù)氖占惴ā?p>一般將堆分為新生代和老年代。

  • 新生代使用:復制算法
  • 老年代使用:標記 - 清除 或者 標記 - 整理 算法

四、垃圾收集器

以上是 HotSpot 虛擬機中的 7 個垃圾收集器,連線表示垃圾收集器可以配合使用。

  • 單線程與多線程:單線程指的是垃圾收集器只使用一個線程,而多線程使用多個線程;
  • 串行與并行:串行指的是垃圾收集器與用戶程序交替執(zhí)行,這意味著在執(zhí)行垃圾收集的時候需要停頓用戶程序;并行指的是垃圾收集器和用戶程序同時執(zhí)行。除了 CMS 和 G1 之外,其它垃圾收集器都是以串行的方式執(zhí)行。

1. Serial 收集器(-XX:+UseSerialGC)

Serial 翻譯為串行,也就是說它以串行的方式執(zhí)行。

它是單線程的收集器,只會使用一個線程進行垃圾收集工作。

它的優(yōu)點是簡單高效,在單個 CPU 環(huán)境下,由于沒有線程交互的開銷,因此擁有最高的單線程收集效率。

它是 Client 場景下的默認新生代收集器,因為在該場景下內(nèi)存一般來說不會很大。它收集一兩百兆垃圾的停頓時間可以控制在一百多毫秒以內(nèi),只要不是太頻繁,這點停頓時間是可以接受的。

2. ParNew 收集器(-XX:+UseParNewGC)

它是 Serial 收集器的多線程版本。

它是 Server 場景下默認的新生代收集器,除了性能原因外,主要是因為除了 Serial 收集器,只有它能與 CMS 收集器配合使用。

3. Parallel Scavenge 收集器(-XX:+UseParallelGC)

與 ParNew 一樣是多線程收集器。

其它收集器目標是盡可能縮短垃圾收集時用戶線程的停頓時間,而它的目標是達到一個可控制的吞吐量,因此它被稱為“吞吐量優(yōu)先”收集器。這里的吞吐量指 CPU 用于運行用戶程序的時間占總時間的比值。

停頓時間越短就越適合需要與用戶交互的程序,良好的響應(yīng)速度能提升用戶體驗。而高吞吐量則可以高效率地利用 CPU 時間,盡快完成程序的運算任務(wù),適合在后臺運算而不需要太多交互的任務(wù)。

縮短停頓時間是以犧牲吞吐量和新生代空間來換取的:新生代空間變小,垃圾回收變得頻繁,導致吞吐量下降。

可以通過一個開關(guān)參數(shù)打開 GC 自適應(yīng)的調(diào)節(jié)策略(GC Ergonomics),就不需要手工指定新生代的大?。?Xmn)、Eden 和 Survivor 區(qū)的比例、晉升老年代對象年齡等細節(jié)參數(shù)了。虛擬機會根據(jù)當前系統(tǒng)的運行情況收集性能監(jiān)控信息,動態(tài)調(diào)整這些參數(shù)以提供最合適的停頓時間或者最大的吞吐量。

4. Serial Old 收集器(-XX:+UseSerialOldGC)

是 Serial 收集器的老年代版本,也是給 Client 場景下的虛擬機使用。如果用在 Server 場景下,它有兩大用途:

  • 在 JDK 1.5 以及之前版本(Parallel Old 誕生以前)中與 Parallel Scavenge 收集器搭配使用。
  • 作為 CMS 收集器的后備預(yù)案,在并發(fā)收集發(fā)生 Concurrent Mode Failure 時使用。

5. Parallel Old 收集器(-XX:+UseParallelOldGC)

是 Parallel Scavenge 收集器的老年代版本。

在注重吞吐量以及 CPU 資源敏感的場合,都可以優(yōu)先考慮 Parallel Scavenge 加 Parallel Old 收集器。

6. CMS 收集器(-XX:+UseConcMarkSweepGC)

CMS(Concurrent Mark Sweep),Mark Sweep 指的是標記 - 清除算法。

分為以下六個流程:

  • 初始標記:僅僅只是標記一下 GC Roots 能直接關(guān)聯(lián)到的對象,速度很快,需要停頓。
  • 并發(fā)標記:進行 GC Roots Tracing 的過程,它在整個回收過程中耗時最長,不需要停頓。
  • 并發(fā)預(yù)清理:查找執(zhí)行并發(fā)標記階段從年輕代晉升到老年代的對象
  • 重新標記:為了修正并發(fā)標記期間因用戶程序繼續(xù)運作而導致標記產(chǎn)生變動的那一部分對象的標記記錄,需要停頓。
  • 并發(fā)清除:清理垃圾對象,不需要停頓。
  • 并發(fā)重置:重置CMS收集器的數(shù)據(jù)結(jié)構(gòu),等待下一次垃圾回收。
    在整個過程中耗時最長的并發(fā)標記和并發(fā)清除過程中,收集器線程都可以與用戶線程一起工作,不需要進行停頓。

具有以下缺點:

  • 吞吐量低:低停頓時間是以犧牲吞吐量為代價的,導致 CPU 利用率不夠高。
  • 無法處理浮動垃圾,可能出現(xiàn) Concurrent Mode Failure。浮動垃圾是指并發(fā)清除階段由于用戶線程繼續(xù)運行而產(chǎn)生的垃圾,這部分垃圾只能到下一次 GC 時才能進行回收。由于浮動垃圾的存在,因此需要預(yù)留出一部分內(nèi)存,意味著 CMS 收集不能像其它收集器那樣等待老年代快滿的時候再回收。如果預(yù)留的內(nèi)存不夠存放浮動垃圾,就會出現(xiàn) Concurrent Mode Failure,這時虛擬機將臨時啟用 Serial Old 來替代 CMS。
  • 標記 - 清除算法導致的空間碎片,往往出現(xiàn)老年代空間剩余,但無法找到足夠大連續(xù)空間來分配當前對象,不得不提前觸發(fā)一次 Full GC。

7. G1 收集器(-XX:+UseG1GC)

G1(Garbage-First),它是一款面向服務(wù)端應(yīng)用的垃圾收集器,在多 CPU 和大內(nèi)存的場景下有很好的性能。HotSpot 開發(fā)團隊賦予它的使命是未來可以替換掉 CMS 收集器。

堆被分為新生代和老年代,其它收集器進行收集的范圍都是整個新生代或者老年代,而 G1 可以直接對新生代和老年代一起回收。

G1 把堆劃分成多個大小相等的獨立區(qū)域(Region),新生代和老年代不再物理隔離。

通過引入 Region 的概念,從而將原來的一整塊內(nèi)存空間劃分成多個的小空間,使得每個小空間可以單獨進行垃圾回收。這種劃分方法帶來了很大的靈活性,使得可預(yù)測的停頓時間模型成為可能。通過記錄每個 Region 垃圾回收時間以及回收所獲得的空間(這兩個值是通過過去回收的經(jīng)驗獲得),并維護一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的 Region。

每個 Region 都有一個 Remembered Set,用來記錄該 Region 對象的引用對象所在的 Region。通過使用 Remembered Set,在做可達性分析的時候就可以避免全堆掃描。

如果不計算維護 Remembered Set 的操作,G1 收集器的運作大致可劃分為以下幾個步驟:

  • 初始標記
  • 并發(fā)標記
  • 最終標記:為了修正在并發(fā)標記期間因用戶程序繼續(xù)運作而導致標記產(chǎn)生變動的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程的 Remembered Set Logs 里面,最終標記階段需要把 Remembered Set Logs 的數(shù)據(jù)合并到 Remembered Set 中。這階段需要停頓線程,但是可并行執(zhí)行。
  • 篩選回收:首先對各個 Region 中的回收價值和成本進行排序,根據(jù)用戶所期望的 GC 停頓時間來制定回收計劃。此階段其實也可以做到與用戶程序一起并發(fā)執(zhí)行,但是因為只回收一部分 Region,時間是用戶可控制的,而且停頓用戶線程將大幅度提高收集效率。

具備如下特點:

  • 并行和并發(fā)
  • 分代收集
  • 空間整合:整體來看是基于“標記 - 整理”算法實現(xiàn)的收集器,從局部(兩個 Region 之間)上來看是基于“復制”算法實現(xiàn)的,這意味著運行期間不會產(chǎn)生內(nèi)存空間碎片。
  • 可預(yù)測的停頓:能讓使用者明確指定在一個長度為 M 毫秒的時間片段內(nèi),消耗在 GC 上的時間不得超過 N 毫秒。

五、降低停頓時間

1. 使用 CMS 收集器
CMS 收集器進行垃圾回收,有 4 個步驟:

  • 初始標記
  • 并發(fā)標記
  • 重新標記
  • 并發(fā)清除

其中初始標記和重新標記需要 “stop the world”,但耗時時間最長的并發(fā)標記、并發(fā)清除過程中,GC 線程都可與用戶線程一起工作。整體上說,CMS 和用戶線程是并行的。

2. 增量算法

基本思路:若一次性將所有垃圾進行處理,會造成系統(tǒng)長時間的停頓,則就讓 GC 線程與用戶線程交替執(zhí)行。每次 GC 線程只收集一小塊區(qū)域的內(nèi)存空間,接著切換到用戶線程,重復幾次,直至 GC 完成。

問題:存在線程切換和上下文切換,造成系統(tǒng)吞吐量下降。


網(wǎng)站題目:詳解Java虛擬機(第⑤篇)——垃圾收集
轉(zhuǎn)載來源:http://weahome.cn/article/iphicd.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部