- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) >
- java垃圾收集器中內存分配和回收的一些細節是怎
這篇文章將為大家詳細講解有關(guān)java垃圾收集器中內存分配和回收的一些細節是怎樣的,文章內容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。
System.gc()
jvm提供了一個(gè)參數 DisableExplicitGC
來(lái)控制是否手工觸發(fā)GC,如果需要禁用,可以使用以下配置:
-XX:+DisableExplicitGC
System.gc()
使用并發(fā)回收在默認情況下,即使 System.gc
生效,會(huì )使用傳統的 Full GC
方式回收整個(gè)堆,而忽略參數中的 UseG1GC
和 UseConcMarkSweepGC
,即CMS和G1是沒(méi)有并發(fā)執行的。
示例代碼
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]
顯然,此時(shí)CMS和G1是沒(méi)有并發(fā)執行的,因為在日志中沒(méi)有任何并發(fā)相關(guān)的信息??梢允褂靡韵聟祦?lái)改變這種默認行為:
-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
參數后,System.gc()
這種顯式GC才會(huì )使用并發(fā)的方式進(jìn)行回收,否則無(wú)論是否啟用了CMS或G1,都不會(huì )進(jìn)行并發(fā)回收。
對于并行回收器(使用UseParallelOldGC或者UseParallelGC),在每一次FullGC之前都會(huì )伴隨一次新生代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)行一次回收,避免將所有回收工作同時(shí)交給一次Full GC進(jìn)行,從而盡可能地縮短一次停頓時(shí)間。
如果不需要這個(gè)特性,可以使用參數-XX:-ScavengeBeforeFullGC
去除發(fā)生在FullGC之前的那次新生代GC. 使用-XX:+PrintGCDetails -XX:+UseParallelOldGC -XX:-ScavengeBeforeFullGC
運行,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]
可以看到,Full GC 前已經(jīng)沒(méi)有了新生代gc。
初創(chuàng )對象在eden區產(chǎn)生
老年對象進(jìn)行老年代:虛擬機提供一個(gè)參數來(lái)控制新生代對象的最大年齡:MaxTenuringThreshold
,這個(gè)值默認是15,即新生代對象最多經(jīng)歷15次GC,就可以晉升到老年代。實(shí)際情況中,對象的實(shí)際晉升年齡是根據survivor區的使用情況動(dòng)態(tài)計算得來(lái)的,而 MaxTenuringThreshold
只是表示這個(gè)年齡的最大值??梢允褂脜?code>TargetSurvivorRatio設置survivor的目標使用率,默認為50,即如果survivor區在GC后使用率超過(guò)50%,那么就很可能會(huì )使用較小的age作為晉升年齡。
大對象進(jìn)入老年代:如果對象很大,新生代無(wú)論是eden區還是survivor區都無(wú)法容納這個(gè)對象,自然這個(gè)對象無(wú)法存放在新生代。jvm提供了參數PretenureSizeThreshold
參數來(lái)設置對象直接晉升到老年代的閾值,單位是字節。只要對象大于該指定值,就會(huì )直接在老年代分配。這個(gè)參數只對串行回收器和ParNew有效,對于ParallelGC無(wú)效。默認下該值為0,也就是不指定最大的晉升大小,一切由運行情況決定。
大對象直接進(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
參數運行,結果如下:
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
可以看到,無(wú)任何gc日志輸出,最終使用的空間中,老年代使用了大約5m空間。
TLAB 全稱(chēng)是Thread Local Allocation Buffer
,即線(xiàn)程本地分配緩存。從名字上看,TLAB是一個(gè)線(xiàn)程專(zhuān)用的內存分配區域。由于對象一般分配在堆上,而堆是全局共享的,在同一時(shí)間內,可能有多個(gè)線(xiàn)程在堆上申請空間。因此,每一次對象分配都必須進(jìn)行同步,而在競爭激烈的場(chǎng)合分配的效率又會(huì )進(jìn)一步下降??紤]到對象分配幾乎是java最常用的操作,因此java虛擬機就使用了TLAB這種線(xiàn)程專(zhuān)屬的區域來(lái)避免多線(xiàn)程沖突,提高對象分配的效率。TLAB本身占用了eden區空間,在TLAB啟用的情況下,虛擬機會(huì )為每一個(gè)java線(xiàn)程分配一塊TLAB區域。
默認情況下,TLAB的大小是會(huì )在運行時(shí)不斷調整的,使系統的運行狀態(tài)達到最優(yōu)。如果想禁用自動(dòng)調整TLAB的大小,可以使用-XX:ResizeTLAB
禁用ResizeTLAB并使用-XX:TLABSize
手工指定TLAB的大小。
示例:?jiǎn)⒂肨LAB與關(guān)閉TLAB時(shí)的性能差異
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); } }
使用參數-XX:+UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server
運行,結果為71;
使用參數-XX:-UseTLAB -Xcomp -XX:-BackgroundCompilation -XX:-DoEscapeAnalysis -server
運行,結果為135;
從結果來(lái)看,TLAB是否啟用對于對象分配的影響還是很大的。
對象的分配流程:
如果開(kāi)啟了棧上分配,系統就會(huì )先進(jìn)行棧上分配;
沒(méi)有開(kāi)啟棧上分配或者不符合條件則會(huì )進(jìn)行TLAB分配;
如果TLAB分配不成功,再?lài)L試在堆上分配;
如果滿(mǎn)足了直接進(jìn)入老年代的條件,就在老年代分配;
否則就在eden區分配,當然,如有必要,可能會(huì )進(jìn)行一次新生代GC.
finalize()
函數對垃圾回收的影響java中提供了一個(gè)類(lèi)似于C++析構函數的機制——finalize()
函數,該函數允許在子類(lèi)中被重載,用于在對象被回收時(shí)進(jìn)行資源釋放。目前普遍的認識是盡量不要使用finalize()
函數進(jìn)行資源釋放,原因主要有以下幾點(diǎn):
在finalize()
函數時(shí),可能會(huì )導致對象復活;
finalize()
函數的執行時(shí)間是沒(méi)有保障的,它完全由GC線(xiàn)程決定,在極端情況下,若不發(fā)生GC,finalize()
將沒(méi)有機會(huì )執行;
一個(gè)糟糕的finalize()
函數會(huì )嚴重影響GC性能。
雖然不推薦使用finalize()
函數,但是在某些場(chǎng)合,使用finalize()
函數可以起到雙保險的作用。比如,在的jdbc驅動(dòng)中,com.mysql.jdbc.ConnectionImpl
就實(shí)現了finalize()
函數,實(shí)現代碼如下:
protected void finalize() throws Throwable { this.cleanup((Throwable)null); super.finalize(); }
也就是,當一個(gè)jdbc connection
被回收時(shí),需要進(jìn)行連接的關(guān)閉,即這里的cleanup()
方法。事實(shí)上,在回收前,開(kāi)發(fā)人員如果正常調用了Connection.close()
方法,連接就會(huì )被顯式關(guān)閉,那樣的話(huà),在cleanup()
方法中將什么也不做。而如果開(kāi)發(fā)人員忘記顯式關(guān)閉連接,而Connection
對象又被回收了,則會(huì )隱式地進(jìn)行連接的關(guān)閉,確保沒(méi)有數據庫連接泄露。此時(shí),finalize()
函數可能會(huì )被作為一種補償措施,在正常方法出現意外時(shí)進(jìn)行補償,盡可能確保系統穩定。當然,由于其調用時(shí)間的不確定性,這不能單獨作為可靠的資源回收手段。
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自互聯(lián)網(wǎng)轉載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權請聯(lián)系QQ:712375056 進(jìn)行舉報,并提供相關(guān)證據,一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容。
Copyright ? 2009-2021 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 珠海市特網(wǎng)科技有限公司 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站