java垃圾收集器中內(nèi)存分配和回收的一些細(xì)節(jié)是怎樣的

這篇文章將為大家詳細(xì)講解有關(guān)java垃圾收集器中內(nèi)存分配和回收的一些細(xì)節(jié)是怎樣的,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價比蘆山網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式蘆山網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋蘆山地區(qū)。費(fèi)用合理售后完善,十年實(shí)體公司更值得信賴。

1. 禁用 System.gc()

jvm提供了一個參數(shù) DisableExplicitGC 來控制是否手工觸發(fā)GC,如果需要禁用,可以使用以下配置:

-XX:+DisableExplicitGC

2. System.gc() 使用并發(fā)回收

在默認(rèn)情況下,即使 System.gc 生效,會使用傳統(tǒng)的 Full GC 方式回收整個堆,而忽略參數(shù)中的 UseG1GCUseConcMarkSweepGC,即CMS和G1是沒有并發(fā)執(zhí)行的。

示例代碼

public class Demo01 {

    public static void main(String[] args) {
        System.gc();
    }

}

使用-XX:+PrintGCDetails -XX:+UseConcMarkSweepGC,日志如下:

[Full GC (System.gc()) [CMS: 0K->372K(174784K), 0.0246450 secs] 2798K->372K(253440K), [Metaspace: 2906K->2906K(1056768K)], 0.0247414 secs] [Times: user=0.02 sys=0.01, real=0.02 secs]

使用-XX:+PrintGCDetails -XX:+UseG1GC,日志如下:

[Full GC (System.gc())  1517K->368K(8192K), 0.0089949 secs]
   [Eden: 2048.0K(12.0M)->0.0B(3072.0K) Survivors: 0.0B->0.0B Heap: 1517.6K(256.0M)->368.7K(8192.0K)], [Metaspace: 2906K->2906K(1056768K)]
 [Times: user=0.01 sys=0.00, real=0.01 secs]

顯然,此時CMS和G1是沒有并發(fā)執(zhí)行的,因?yàn)樵谌罩局袥]有任何并發(fā)相關(guān)的信息??梢允褂靡韵聟?shù)來改變這種默認(rèn)行為:

-XX:+ExplicitGCInvokesConcurrent

使用-XX:+PrintGCDetails -XX:+UseConcMarkSweepGC -XX:+ExplicitGCInvokesConcurrent,日志如下:

[GC (System.gc()) [ParNew: 2798K->398K(78656K), 0.0010206 secs] 2798K->398K(253440K), 0.0010476 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 0K(174784K)] 1797K(253440K), 0.0001720 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Final Remark) [YG occupancy: 1797 K (78656 K)][Rescan (parallel) , 0.0007200 secs][weak refs processing, 0.0000066 secs][class unloading, 0.0001855 secs][scrub symbol table, 0.0003697 secs][scrub string table, 0.0001424 secs][1 CMS-remark: 0K(174784K)] 1797K(253440K), 0.0014769 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-reset-start]

使用-XX:+PrintGCDetails -XX:+UseG1GC -XX:+ExplicitGCInvokesConcurrent,日志如下:

[GC pause (System.gc()) (young) (initial-mark), 0.0024614 secs]
   [Parallel Time: 1.5 ms, GC Workers: 8]
      [GC Worker Start (ms): Min: 100.6, Avg: 100.7, Max: 100.8, Diff: 0.2]
      [Ext Root Scanning (ms): Min: 0.0, Avg: 0.2, Max: 0.3, Diff: 0.3, Sum: 1.6]
      [Update RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
         [Processed Buffers: Min: 0, Avg: 0.0, Max: 0, Diff: 0, Sum: 0]
      [Scan RS (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Code Root Scanning (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
      [Object Copy (ms): Min: 0.4, Avg: 0.6, Max: 1.0, Diff: 0.6, Sum: 5.1]
      [Termination (ms): Min: 0.0, Avg: 0.4, Max: 0.5, Diff: 0.5, Sum: 3.0]
         [Termination Attempts: Min: 1, Avg: 3.1, Max: 8, Diff: 7, Sum: 25]
      [GC Worker Other (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.1]
      [GC Worker Total (ms): Min: 1.1, Avg: 1.2, Max: 1.4, Diff: 0.3, Sum: 9.8]
      [GC Worker End (ms): Min: 101.9, Avg: 101.9, Max: 102.0, Diff: 0.1]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 0.8 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.6 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.1 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 2048.0K(24.0M)->0.0B(23.0M) Survivors: 0.0B->1024.0K Heap: 2009.1K(256.0M)->568.1K(256.0M)]
 [Times: user=0.01 sys=0.00, real=0.00 secs] 
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0003924 secs]
[GC concurrent-mark-start]
[GC concurrent-mark-end, 0.0002255 secs]
[GC remark [Finalize Marking, 0.0001208 secs] [GC ref-proc, 0.0000284 secs] [Unloading, 0.0002767 secs], 0.0005331 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC cleanup 1039K->1039K(256M), 0.0003741 secs]
 [Times: user=0.00 sys=0.00, real=0.00 secs]

使用ExplicitGCInvokesConcurrent參數(shù)后,System.gc() 這種顯式GC才會使用并發(fā)的方式進(jìn)行回收,否則無論是否啟用了CMS或G1,都不會進(jìn)行并發(fā)回收。

3. 并行GC前額外觸發(fā)的新生代GC

對于并行回收器(使用UseParallelOldGC或者UseParallelGC),在每一次FullGC之前都會伴隨一次新生代GC,這和串行回收器相比,有很大的不同,示例如下:

public class Demo01 {
    public static void main(String[] args) {
        System.gc();
    }
}

使用-XX:+PrintGCDetails -XX:+UseSerialGC,gc日志如下:

[Full GC (System.gc()) [Tenured: 0K->367K(174784K), 0.0017465 secs] 2798K->367K(253440K), [Metaspace: 2903K->2903K(1056768K)], 0.0017770 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

可以看到,System.gc() 觸發(fā)了一次Full GC.

使用-XX:+PrintGCDetails -XX:+UseParallelOldGC,gc日志如下:

[GC (System.gc()) [PSYoungGen: 2621K->528K(76288K)] 2621K->536K(251392K), 0.0008817 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 528K->0K(76288K)] [ParOldGen: 8K->368K(175104K)] 536K->368K(251392K), [Metaspace: 2905K->2905K(1056768K)], 0.0036679 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

可以看到,觸發(fā)FullGC前,進(jìn)行了一次新生代GC。因此,這里的Sytem.gc()實(shí)際上觸發(fā)了兩次GC,這樣做的目的是先將新生代進(jìn)行一次回收,避免將所有回收工作同時交給一次Full GC進(jìn)行,從而盡可能地縮短一次停頓時間。

如果不需要這個特性,可以使用參數(shù)-XX:-ScavengeBeforeFullGC去除發(fā)生在FullGC之前的那次新生代GC. 使用-XX:+PrintGCDetails -XX:+UseParallelOldGC -XX:-ScavengeBeforeFullGC運(yùn)行,gc日志如下:

[Full GC (System.gc()) [PSYoungGen: 2621K->0K(76288K)] [ParOldGen: 0K->368K(175104K)] 2621K->368K(251392K), [Metaspace: 2906K->2906K(1056768K)], 0.0032836 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]

可以看到,F(xiàn)ull GC 前已經(jīng)沒有了新生代gc。

4. 對象何時進(jìn)入老年代

  • 初創(chuàng)對象在eden區(qū)產(chǎn)生

  • 老年對象進(jìn)行老年代:虛擬機(jī)提供一個參數(shù)來控制新生代對象的最大年齡:MaxTenuringThreshold,這個值默認(rèn)是15,即新生代對象最多經(jīng)歷15次GC,就可以晉升到老年代。實(shí)際情況中,對象的實(shí)際晉升年齡是根據(jù)survivor區(qū)的使用情況動態(tài)計算得來的,而 MaxTenuringThreshold 只是表示這個年齡的最大值??梢允褂脜?shù)TargetSurvivorRatio設(shè)置survivor的目標(biāo)使用率,默認(rèn)為50,即如果survivor區(qū)在GC后使用率超過50%,那么就很可能會使用較小的age作為晉升年齡。

  • 大對象進(jìn)入老年代:如果對象很大,新生代無論是eden區(qū)還是survivor區(qū)都無法容納這個對象,自然這個對象無法存放在新生代。jvm提供了參數(shù)PretenureSizeThreshold參數(shù)來設(shè)置對象直接晉升到老年代的閾值,單位是字節(jié)。只要對象大于該指定值,就會直接在老年代分配。這個參數(shù)只對串行回收器和ParNew有效,對于ParallelGC無效。默認(rèn)下該值為0,也就是不指定最大的晉升大小,一切由運(yùn)行情況決定。

大對象直接進(jìn)入老年代的示例:

public class Demo04 {
    public static final int _1K = 1024;
    public static void main(String[] args) {
        Map<Integer, byte[]> map = new HashMap<>();
        for(int i = 0; i < 5 * _1K; i++) {
            byte[] b = new byte[_1K];
            map.put(i, b);
        }
    }
}

使用-Xmx32m -Xms32m -XX:+UseSerialGC -XX:+PrintGCDetails -XX:PretenureSizeThreshold=1000 -XX:-UseTLAB參數(shù)運(yùn)行,結(jié)果如下:

Heap
 def new generation   total 9792K, used 963K [0x00000007be000000, 0x00000007beaa0000, 0x00000007beaa0000)
  eden space 8704K,  11% used [0x00000007be000000, 0x00000007be0f0f88, 0x00000007be880000)
  from space 1088K,   0% used [0x00000007be880000, 0x00000007be880000, 0x00000007be990000)
  to   space 1088K,   0% used [0x00000007be990000, 0x00000007be990000, 0x00000007beaa0000)
 tenured generation   total 21888K, used 5953K [0x00000007beaa0000, 0x00000007c0000000, 0x00000007c0000000)
   the space 21888K,  27% used [0x00000007beaa0000, 0x00000007bf070408, 0x00000007bf070600, 0x00000007c0000000)
 Metaspace       used 3054K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 333K, capacity 388K, committed 512K, reserved 1048576K

可以看到,無任何gc日志輸出,最終使用的空間中,老年代使用了大約5m空間。

5. 在TLAB上分配對象

TLAB 全稱是Thread Local Allocation Buffer,即線程本地分配緩存。從名字上看,TLAB是一個線程專用的內(nèi)存分配區(qū)域。由于對象一般分配在堆上,而堆是全局共享的,在同一時間內(nèi),可能有多個線程在堆上申請空間。因此,每一次對象分配都必須進(jìn)行同步,而在競爭激烈的場合分配的效率又會進(jìn)一步下降??紤]到對象分配幾乎是java最常用的操作,因此java虛擬機(jī)就使用了TLAB這種線程專屬的區(qū)域來避免多線程沖突,提高對象分配的效率。TLAB本身占用了eden區(qū)空間,在TLAB啟用的情況下,虛擬機(jī)會為每一個java線程分配一塊TLAB區(qū)域。

默認(rèn)情況下,TLAB的大小是會在運(yùn)行時不斷調(diào)整的,使系統(tǒng)的運(yùn)行狀態(tài)達(dá)到最優(yōu)。如果想禁用自動調(diào)整TLAB的大小,可以使用-XX:ResizeTLAB禁用ResizeTLAB并使用-XX:TLABSize手工指定TLAB的大小。

示例:啟用TLAB與關(guān)閉TLAB時的性能差異

public class Demo05 {
    public static void alloc() {
        byte[] b = new byte[2];
        b[0] = 1;
    }

    public static void main(String[] args) {
        long b = System.currentTimeMillis();
        for(int i = 0; i < 1000_0000; i++) {
            alloc();
        }
        long e = System.currentTimeMillis();
        System.out.println(e - b);
    }
}
  • 使用參數(shù)-XX:+UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server運(yùn)行,結(jié)果為71;

  • 使用參數(shù)-XX:-UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server運(yùn)行,結(jié)果為135;

從結(jié)果來看,TLAB是否啟用對于對象分配的影響還是很大的。

對象的分配流程:

  • 如果開啟了棧上分配,系統(tǒng)就會先進(jìn)行棧上分配;

  • 沒有開啟棧上分配或者不符合條件則會進(jìn)行TLAB分配;

  • 如果TLAB分配不成功,再嘗試在堆上分配;

  • 如果滿足了直接進(jìn)入老年代的條件,就在老年代分配;

  • 否則就在eden區(qū)分配,當(dāng)然,如有必要,可能會進(jìn)行一次新生代GC.

6. finalize() 函數(shù)對垃圾回收的影響

java中提供了一個類似于C++析構(gòu)函數(shù)的機(jī)制——finalize()函數(shù),該函數(shù)允許在子類中被重載,用于在對象被回收時進(jìn)行資源釋放。目前普遍的認(rèn)識是盡量不要使用finalize()函數(shù)進(jìn)行資源釋放,原因主要有以下幾點(diǎn):

  • finalize()函數(shù)時,可能會導(dǎo)致對象復(fù)活;

  • finalize()函數(shù)的執(zhí)行時間是沒有保障的,它完全由GC線程決定,在極端情況下,若不發(fā)生GC,finalize()將沒有機(jī)會執(zhí)行;

  • 一個糟糕的finalize()函數(shù)會嚴(yán)重影響GC性能。

雖然不推薦使用finalize()函數(shù),但是在某些場合,使用finalize()函數(shù)可以起到雙保險的作用。比如,在MySQL的jdbc驅(qū)動中,com.mysql.jdbc.ConnectionImpl就實(shí)現(xiàn)了finalize()函數(shù),實(shí)現(xiàn)代碼如下:

protected void finalize() throws Throwable {
    this.cleanup((Throwable)null);
    super.finalize();
}

也就是,當(dāng)一個jdbc connection被回收時,需要進(jìn)行連接的關(guān)閉,即這里的cleanup()方法。事實(shí)上,在回收前,開發(fā)人員如果正常調(diào)用了Connection.close()方法,連接就會被顯式關(guān)閉,那樣的話,在cleanup()方法中將什么也不做。而如果開發(fā)人員忘記顯式關(guān)閉連接,而Connection對象又被回收了,則會隱式地進(jìn)行連接的關(guān)閉,確保沒有數(shù)據(jù)庫連接泄露。此時,finalize()函數(shù)可能會被作為一種補(bǔ)償措施,在正常方法出現(xiàn)意外時進(jìn)行補(bǔ)償,盡可能確保系統(tǒng)穩(wěn)定。當(dāng)然,由于其調(diào)用時間的不確定性,這不能單獨(dú)作為可靠的資源回收手段。

關(guān)于java垃圾收集器中內(nèi)存分配和回收的一些細(xì)節(jié)是怎樣的就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

網(wǎng)站欄目:java垃圾收集器中內(nèi)存分配和回收的一些細(xì)節(jié)是怎樣的
文章起源:http://muchs.cn/article38/ijoosp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供建站公司網(wǎng)站設(shè)計公司、網(wǎng)頁設(shè)計公司響應(yīng)式網(wǎng)站、網(wǎng)站建設(shè)、網(wǎng)站內(nèi)鏈

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

外貿(mào)網(wǎng)站建設(shè)