欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

GC參考手冊二java中垃圾回收原理解析

 更新時間:2022年01月25日 14:35:14   作者:金色夢想  
由于有個垃圾回收機制,java中的額對象不在有“作用域”的概念,只有對象的引用才有“作用域”。垃圾回收可以有效的防止內存泄露,有效的使用空閑的內存<BR>

內存碎片整理

每次執(zhí)行清除(sweeping), JVM 都必須保證不可達對象占用的內存能被回收重用。但這(最終)有可能會產生內存碎片(類似于磁盤碎片), 進而引發(fā)兩個問題:

寫入操作越來越耗時, 因為尋找一塊足夠大的空閑內存會變得非常麻煩。

在創(chuàng)建新對象時, JVM在連續(xù)的塊中分配內存。如果碎片問題很嚴重, 直至沒有空閑片段能存放下新創(chuàng)建的對象,就會發(fā)生內存分配錯誤(allocation error)。

要避免這類問題,JVM 必須確保碎片問題不失控。因此在垃圾收集過程中, 不僅僅是標記和清除, 還需要執(zhí)行 “內存碎片整理” 過程。這個過程讓所有可達對象(reachable objects)依次排列, 以消除(或減少)碎片。示意圖如下所示:

分代假設

我們前面提到過,執(zhí)行垃圾收集需要停止整個應用。很明顯,對象越多則收集所有垃圾消耗的時間就越長。但可不可以只處理一個較小的內存區(qū)域呢? 為了探究這種可能性,研究人員發(fā)現,程序中的大多數可回收的內存可歸為兩類:

大部分對象很快就不再使用

還有一部分不會立即無用,但也不會持續(xù)(太)長時間

這些觀測形成了 弱代假設(Weak Generational Hypothesis)?;谶@一假設, VM中的內存被分為年輕代(Young Generation)和老年代(Old Generation)。老年代有時候也稱為 年老區(qū)(Tenured)。

拆分為這樣兩個可清理的單獨區(qū)域,允許采用不同的算法來大幅提高GC的性能。

這種方法也不是沒有問題。例如,在不同分代中的對象可能會互相引用, 在收集某一個分代時就會成為 “事實上的” GC root。

當然,要著重強調的是,分代假設并不適用于所有程序。因為GC算法專門針對“要么死得快”,“否則活得長” 這類特征的對象來進行優(yōu)化, JVM對收集那種存活時間半長不長的對象就顯得非常尷尬了。

內存池

堆內存中的內存池劃分也是類似的。不太容易理解的地方在于各個內存池中的垃圾收集是如何運行的。請注意,不同的GC算法在實現細節(jié)上可能會有所不同,但和本章所介紹的相關概念都是一致的。

新生代(Eden,伊甸園)

Eden 是內存中的一個區(qū)域, 用來分配新創(chuàng)建的對象。通常會有多個線程同時創(chuàng)建多個對象, 所以 Eden 區(qū)被劃分為多個 線程本地分配緩沖區(qū)(Thread Local Allocation Buffer, 簡稱TLAB)。通過這種緩沖區(qū)劃分,大部分對象直接由JVM 在對應線程的TLAB中分配, 避免與其他線程的同步操作。

如果 TLAB 中沒有足夠的內存空間, 就會在共享Eden區(qū)(shared Eden space)之中分配。如果共享Eden區(qū)也沒有足夠的空間, 就會觸發(fā)一次 年輕代GC 來釋放內存空間。如果GC之后 Eden 區(qū)依然沒有足夠的空閑內存區(qū)域, 則對象就會被分配到老年代空間(Old Generation)。

當 Eden 區(qū)進行垃圾收集時, GC將所有從 root 可達的對象過一遍, 并標記為存活對象。

我們曾指出,對象間可能會有跨代的引用, 所以需要一種方法來標記從其他分代中指向Eden的所有引用。這樣做又會遭遇各個分代之間一遍又一遍的引用。JVM在實現時采用了一些絕招: 卡片標記(card-marking)。從本質上講,JVM只需要記住Eden區(qū)中 “臟”對象的粗略位置, 可能有老年代的對象引用指向這部分區(qū)間。

標記階段完成后, Eden中所有存活的對象都會被復制到存活區(qū)(Survivor spaces)里面。整個Eden區(qū)就可以被認為是空的, 然后就能用來分配新對象。這種方法稱為 “標記-復制”(Mark and Copy): 存活的對象被標記, 然后復制到一個存活區(qū)(注意,是復制,而不是移動)。。

存活區(qū)

Eden 區(qū)的旁邊是兩個存活區(qū), 稱為 from 空間和 to 空間。需要著重強調的的是, 任意時刻總有一個存活區(qū)是空的(empty)。

空的那個存活區(qū)用于在下一次年輕代GC時存放收集的對象。年輕代中所有的存活對象(包括Edenq區(qū)和非空的那個 “from” 存活區(qū))都會被復制到 ”to“ 存活區(qū)。GC過程完成后, ”to“ 區(qū)有對象,而 ‘from’ 區(qū)里沒有對象。兩者的角色進行正好切換 。

存活的對象會在兩個存活區(qū)之間復制多次, 直到某些對象的存活 時間達到一定的閥值。分代理論假設, 存活超過一定時間的對象很可能會繼續(xù)存活更長時間。

這類“ 年老” 的對象因此被提升(promoted )到老年代。提升的時候, 存活區(qū)的對象不再是復制到另一個存活區(qū),而是遷移到老年代, 并在老年代一直駐留, 直到變?yōu)椴豢蛇_對象。

為了確定一個對象是否“足夠老”, 可以被提升(Promotion)到老年代,GC模塊跟蹤記錄每個存活區(qū)對象存活的次數。每次分代GC完成后,存活對象的年齡就會增長。當年齡超過提升閾值(tenuring threshold), 就會被提升到老年代區(qū)域。

具體的提升閾值由JVM動態(tài)調整,但也可以用參數 -XX:+MaxTenuringThreshold 來指定上限。如果設置 -XX:+MaxTenuringThreshold=0 , 則GC時存活對象不在存活區(qū)之間復制,直接提升到老年代。現代 JVM 中這個閾值默認設置為15個 GC周期。這也是HotSpot中的最大值。

如果存活區(qū)空間不夠存放年輕代中的存活對象,提升(Promotion)也可能更早地進行。

老年代(Old Generation)

老年代的GC實現要復雜得多。老年代內存空間通常會更大,里面的對象是垃圾的概率也更小。

老年代GC發(fā)生的頻率比年輕代小很多。同時, 因為預期老年代中的對象大部分是存活的, 所以不再使用標記和復制(Mark and Copy)算法。而是采用移動對象的方式來實現最小化內存碎片。老年代空間的清理算法通常是建立在不同的基礎上的。原則上,會執(zhí)行以下這些步驟:

通過標志位(marked bit),標記所有通過 GC roots 可達的對象.

刪除所有不可達對象

整理老年代空間中的內容,方法是將所有的存活對象復制,從老年代空間開始的地方,依次存放。

通過上面的描述可知, 老年代GC必須明確地進行整理,以避免內存碎片過多。

永久代(PermGen)

在Java 8 之前有一個特殊的空間,稱為“永久代”(Permanent Generation)。這是存儲元數據(metadata)的地方,比如 class 信息等。此外,這個區(qū)域中也保存有其他的數據和信息, 包括 內部化的字符串(internalized strings)等等。實際上這給Java開發(fā)者造成了很多麻煩,因為很難去計算這塊區(qū)域到底需要占用多少內存空間。

預測失敗導致的結果就是產生 java.lang.OutOfMemoryError: Permgen space 這種形式的錯誤。

除非 ·OutOfMemoryError· 確實是內存泄漏導致的,否則就只能增加 permgen 的大小,例如下面的示例,就是設置 permgen 最大空間為 256 MB:

java -XX:MaxPermSize=256m com.mycompany.MyApplication

元空間

既然估算元數據所需空間那么復雜, Java 8直接刪除了永久代(Permanent Generation),改用 Metaspace。從此以后, Java 中很多雜七雜八的東西都放置到普通的堆內存里。

當然,像類定義(class definitions)之類的信息會被加載到 Metaspace 中。元數據區(qū)位于本地內存(native memory),不再影響到普通的Java對象。默認情況下, Metaspace的大小只受限于 Java進程可用的本地內存。

這樣程序就不再因為多加載了幾個類/JAR包就導致 java.lang.OutOfMemoryError: Permgen space. 

注意, 這種不受限制的空間也不是沒有代價的 —— 如果 Metaspace 失控, 則可能會導致很嚴重的內存交換(swapping), 或者導致本地內存分配失敗。

如果需要避免這種最壞情況,那么可以通過下面這樣的方式來限制 Metaspace 的大小, 如 256 MB:

java -XX:MaxMetaspaceSize=256m com.mycompany.MyApplication

Minor GC vs Major GC vs Full GC

垃圾收集事件(Garbage Collection events)通常分為: 小型GC(Minor GC) - 大型GC(Major GC) - 和完全GC(Full GC) 。本節(jié)介紹這些事件及其區(qū)別。然后你會發(fā)現這些區(qū)別也不是特別清晰。

最重要的是,應用程序是否滿足 服務級別協議(Service Level Agreement, SLA), 并通過監(jiān)控程序查看響應延遲和吞吐量。也只有那時候才能看到GC事件相關的結果。重要的是這些事件是否停止整個程序,以及持續(xù)多長時間。

雖然 Minor, Major 和 Full GC 這些術語被廣泛應用, 但并沒有標準的定義, 我們還是來深入了解一下具體的細節(jié)吧。

小型GC(Minor GC)

年輕代內存的垃圾收集事件稱為小型GC。這個定義既清晰又得到廣泛共識。對于小型GC事件,有一些有趣的事情你應該了解一下:

  • 當JVM無法為新對象分配內存空間時總會觸發(fā) Minor GC,比如 Eden 區(qū)占滿時。所以(新對象)分配頻率越高, Minor GC 的頻率就越高。
  • Minor GC 事件實際上忽略了老年代。從老年代指向年輕代的引用都被認為是GC Root。而從年輕代指向老年代的引用在標記階段全部被忽略。
  • 與一般的認識相反, Minor GC 每次都會引起全線停頓(stop-the-world ), 暫停所有的應用線程。對大多數程序而言,暫停時長基本上是可以忽略不計的, 因為 Eden 區(qū)的對象基本上都是垃圾, 也不怎么復制到存活區(qū)/老年代。如果情況不是這樣, 大部分新創(chuàng)建的對象不能被垃圾回收清理掉, 則 Minor GC的停頓就會持續(xù)更長的時間。

所以 Minor GC 的定義很簡單 —— Minor GC 清理的就是年輕代。

Major GC vs Full GC

值得一提的是, 這些術語并沒有正式的定義 —— 無論是在JVM規(guī)范還是在GC相關論文中。

我們知道, Minor GC 清理的是年輕代空間(Young space),相應的,其他定義也很簡單:

  • Major GC(大型GC) 清理的是老年代空間(Old space)。
  • Full GC(完全GC)清理的是整個堆, 包括年輕代和老年代空間。

杯具的是更復雜的情況出現了。很多 Major GC 是由 Minor GC 觸發(fā)的, 所以很多情況下這兩者是不可分離的。另一方面, 像G1這樣的垃圾收集算法執(zhí)行的是部分區(qū)域垃圾回收, 所以,額,使用術語“cleaning”并不是非常準確。

這也讓我們認識到,不應該去操心是叫 Major GC 呢還是叫 Full GC, 我們應該關注的是: 某次GC事件 是否停止所有線程,或者是與其他線程并發(fā)執(zhí)行。

這些混淆甚至根植于標準的JVM工具中。我的意思可以通過實例來說明。讓我們來對比同一JVM中兩款工具的GC信息輸出吧。這個JVM使用的是 并發(fā)標記和清除收集器(Concurrent Mark and Sweep collector,-XX:+UseConcMarkSweepGC).

my-precious: me$ jstat -gc -t 4235 1s
Time S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
 5.7 34048.0 34048.0  0.0   34048.0 272640.0 194699.7 1756416.0   181419.9  18304.0 17865.1 2688.0 2497.6      3    0.275   0      0.000    0.275
 6.7 34048.0 34048.0 34048.0  0.0   272640.0 247555.4 1756416.0   263447.9  18816.0 18123.3 2688.0 2523.1      4    0.359   0      0.000    0.359
 7.7 34048.0 34048.0  0.0   34048.0 272640.0 257729.3 1756416.0   345109.8  19072.0 18396.6 2688.0 2550.3      5    0.451   0      0.000    0.451
 8.7 34048.0 34048.0 34048.0 34048.0 272640.0 272640.0 1756416.0  444982.5  19456.0 18681.3 2816.0 2575.8      7    0.550   0      0.000    0.550
 9.7 34048.0 34048.0 34046.7  0.0   272640.0 16777.0  1756416.0   587906.3  20096.0 19235.1 2944.0 2631.8      8    0.720   0      0.000    0.720
10.7 34048.0 34048.0  0.0   34046.2 272640.0 80171.6  1756416.0   664913.4  20352.0 19495.9 2944.0 2657.4      9    0.810   0      0.000    0.810
11.7 34048.0 34048.0 34048.0  0.0   272640.0 129480.8 1756416.0   745100.2  20608.0 19704.5 2944.0 2678.4     10    0.896   0      0.000    0.896
12.7 34048.0 34048.0  0.0   34046.6 272640.0 164070.7 1756416.0   822073.7  20992.0 19937.1 3072.0 2702.8     11    0.978   0      0.000    0.978
<strong>13.7 34048.0 34048.0 34048.0  0.0   272640.0 211949.9 1756416.0   897364.4  21248.0 20179.6 3072.0 2728.1     12    1.087   1      0.004    1.091
14.7 34048.0 34048.0  0.0   34047.1 272640.0 245801.5 1756416.0   597362.6  21504.0 20390.6 3072.0 2750.3     13    1.183   2      0.050    1.233</strong>
15.7 34048.0 34048.0  0.0   34048.0 272640.0 21474.1  1756416.0   757347.0  22012.0 20792.0 3200.0 2791.0     15    1.336   2      0.050    1.386
16.7 34048.0 34048.0 34047.0  0.0   272640.0 48378.0  1756416.0   838594.4  22268.0 21003.5 3200.0 2813.2     16    1.433   2      0.050    1.484

此片段截取自JVM啟動后的前17秒。根據這些信息可以得知: 有2次Full GC在12次Minor GC(YGC)之后觸發(fā)執(zhí)行, 總計耗時 50ms。當然,也可以通過具備圖形界面的工具得出同樣的信息, 比如 jconsole 或者 jvisualvm (或者最新的 jmc)。

在下結論之前, 讓我們看看此JVM進程的GC日志。顯然需要配置 -XX:+PrintGCDetails 參數,GC日志的內容更詳細,結果也有一些不同:

java -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC eu.plumbr.demo.GarbageProducer
3.157: [GC (Allocation Failure) 3.157: [ParNew: 272640K->34048K(306688K), 0.0844702 secs] 272640K->69574K(2063104K), 0.0845560 secs] [Times: user=0.23 sys=0.03, real=0.09 secs] 
4.092: [GC (Allocation Failure) 4.092: [ParNew: 306688K->34048K(306688K), 0.1013723 secs] 342214K->136584K(2063104K), 0.1014307 secs] [Times: user=0.25 sys=0.05, real=0.10 secs] 
... cut for brevity ...
11.292: [GC (Allocation Failure) 11.292: [ParNew: 306686K->34048K(306688K), 0.0857219 secs] 971599K->779148K(2063104K), 0.0857875 secs] [Times: user=0.26 sys=0.04, real=0.09 secs] 
12.140: [GC (Allocation Failure) 12.140: [ParNew: 306688K->34046K(306688K), 0.0821774 secs] 1051788K->856120K(2063104K), 0.0822400 secs] [Times: user=0.25 sys=0.03, real=0.08 secs] 
12.989: [GC (Allocation Failure) 12.989: [ParNew: 306686K->34048K(306688K), 0.1086667 secs] 1128760K->931412K(2063104K), 0.1087416 secs] [Times: user=0.24 sys=0.04, real=0.11 secs] 
13.098: [<strong>GC (CMS Initial Mark)</strong> [1 CMS-initial-mark: 897364K(1756416K)] 936667K(2063104K), <strong>0.0041705 secs</strong>] [Times: user=0.02 sys=0.00, real=0.00 secs] 
13.102: [CMS-concurrent-mark-start]
13.341: [CMS-concurrent-mark: 0.238/0.238 secs] [Times: user=0.36 sys=0.01, real=0.24 secs] 
13.341: [CMS-concurrent-preclean-start]
13.350: [CMS-concurrent-preclean: 0.009/0.009 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 
13.350: [CMS-concurrent-abortable-preclean-start]
13.878: [GC (Allocation Failure) 13.878: [ParNew: 306688K->34047K(306688K), 0.0960456 secs] 1204052K->1010638K(2063104K), 0.0961542 secs] [Times: user=0.29 sys=0.04, real=0.09 secs] 
14.366: [CMS-concurrent-abortable-preclean: 0.917/1.016 secs] [Times: user=2.22 sys=0.07, real=1.01 secs] 
14.366: [<strong>GC (CMS Final Remark)</strong> [YG occupancy: 182593 K (306688 K)]14.366: [Rescan (parallel) , 0.0291598 secs]14.395: [weak refs processing, 0.0000232 secs]14.395: [class unloading, 0.0117661 secs]14.407: [scrub symbol table, 0.0015323 secs]14.409: [scrub string table, 0.0003221 secs][1 CMS-remark: 976591K(1756416K)] 1159184K(2063104K), <strong>0.0462010 secs</strong>] [Times: user=0.14 sys=0.00, real=0.05 secs] 
14.412: [CMS-concurrent-sweep-start]
14.633: [CMS-concurrent-sweep: 0.221/0.221 secs] [Times: user=0.37 sys=0.00, real=0.22 secs] 
14.633: [CMS-concurrent-reset-start]
14.636: [CMS-concurrent-reset: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

通過GC日志可以看到, 在12 次 Minor GC之后發(fā)生了一些 “不同的事情”。并不是兩個 Full GC, 而是在老年代執(zhí)行了一次 GC, 分為多個階段執(zhí)行:

  • 初始標記階段(Initial Mark phase),耗時 0.0041705秒(約4ms)。此階段是全線停頓(STW)事件,暫停所有應用線程,以便執(zhí)行初始標記。
  • 標記和預清理階段(Markup and Preclean phase)。和應用線程并發(fā)執(zhí)行。
  • 最終標記階段(Final Remark phase), 耗時 0.0462010秒(約46ms)。此階段也是全線停頓(STW)事件。
  • 清除操作(Sweep)是并發(fā)執(zhí)行的, 不需要暫停應用線程。

所以從實際的GC日志可以看到, 并不是執(zhí)行了兩次 Full GC操作, 而是只執(zhí)行了一次清理老年代空間的 Major GC 。

如果只關心延遲, 通過后面 jstat 顯示的數據, 也能得出正確的結果。它正確地列出了兩次 STW 事件,總計耗時 50 ms。這段時間影響了所有應用線程的延遲。如果想要優(yōu)化吞吐量, 這個結果就會有誤導性 —— jstat 只列出了 stop-the-world 的初始標記階段和最終標記階段, jstat 的輸出完全隱藏了并發(fā)執(zhí)行的GC階段。

以上就是GC參考手冊二java中垃圾回收原理解析的詳細內容,更多關于GC參考手冊java垃圾回收的資料請關注腳本之家其它相關文章!

原文鏈接:https://plumbr.io/handbook/garbage-collection-in-java

相關文章

  • SpringBoot2.x的依賴管理配置

    SpringBoot2.x的依賴管理配置

    這篇文章主要介紹了SpringBoot2.x的依賴管理配置,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06
  • springboot簡單實現單點登錄的示例代碼

    springboot簡單實現單點登錄的示例代碼

    本文主要介紹了springboot簡單實現單點登錄的示例代碼,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01
  • Java實現復雜的進制轉換器功能示例

    Java實現復雜的進制轉換器功能示例

    這篇文章主要介紹了Java實現復雜的進制轉換器功能,結合實例形式分析了java數學運算的相關實現技巧,需要的朋友可以參考下
    2017-01-01
  • Java泛型的類型擦除示例詳解

    Java泛型的類型擦除示例詳解

    Java泛型(Generic)的引入加強了參數類型的安全性,減少了類型的轉換,但有一點需要注意,Java 的泛型在編譯器有效,在運行期被刪除,也就是說所有泛型參數類型在編譯后都會被清除掉,這篇文章主要給大家介紹了關于Java泛型的類型擦除的相關資料,需要的朋友可以參考下
    2021-07-07
  • Spring?Cloud?Eureka高可用配置(踩坑記錄)

    Spring?Cloud?Eureka高可用配置(踩坑記錄)

    在進行Eureka高可用配置時,控制臺一直出現“......”的錯誤,但是在瀏覽器中輸入地址:peer1:8761 卻是可正常運行,這篇文章主要介紹了Spring?Cloud踩坑之Eureka高可用配置,需要的朋友可以參考下
    2023-08-08
  • SpringCloud使用Zookeeper作為注冊中心

    SpringCloud使用Zookeeper作為注冊中心

    這篇文章主要介紹了SpringCloud如何使用Zookeeper作為注冊中心,幫助大家更好的理解和學習使用Zookeeper,感興趣的朋友可以了解下
    2021-04-04
  • SpringBoot中@GetMapping注解的使用

    SpringBoot中@GetMapping注解的使用

    @GetMapping注解是Spring Boot中最常用的注解之一,它可以幫助開發(fā)者定義和處理HTTP GET請求,本文就來介紹一下SpringBoot中@GetMapping注解的使用,感興趣的可以了解一下
    2023-10-10
  • Intellij IDEA神器居然還有這些小技巧

    Intellij IDEA神器居然還有這些小技巧

    Intellij IDEA真是越用越覺得它強大,它總是在我們寫代碼的時候,不時給我們來個小驚喜,本文給大家主要介紹一些你可能不知道的但是又實用的小技巧,感興趣的朋友跟隨小編一起看看吧
    2021-01-01
  • Spring-webflux訪問關系型數據庫實戰(zhàn)

    Spring-webflux訪問關系型數據庫實戰(zhàn)

    這篇文章主要為大家介紹了Spring-webflux訪問關系型數據庫實戰(zhàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-07-07
  • Java實現規(guī)則幾何圖形的繪制與周長面積計算詳解

    Java實現規(guī)則幾何圖形的繪制與周長面積計算詳解

    隨著計算機的發(fā)展,人們對圖形的計算要求會越來越高。在各行各業(yè)中的計算人員會對圖形的計算要有便利的要求,規(guī)則幾何圖形問題求解程序應運而生!本文將用Java編寫一個程序,可以實現規(guī)則幾何圖形的繪制與周長面積計算,感興趣的可以了解一下
    2022-07-07

最新評論