<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>


    posts - 15,  comments - 34,  trackbacks - 27

    優(yōu)化 Java 垃圾收集的性能

    如何利用 IBM Java 虛擬機(jī)檢測(cè)和解決垃圾收集問題

    developerWorks
    文檔選項(xiàng)
    將此頁(yè)作為電子郵件發(fā)送

    將此頁(yè)作為電子郵件發(fā)送

    未顯示需要 JavaScript 的文檔選項(xiàng)


    對(duì)此頁(yè)的評(píng)價(jià)

    幫助我們改進(jìn)這些內(nèi)容


    級(jí)別: 初級(jí)

    Sumit Chawla, 技術(shù)主管,eServer Java Enablement, IBM

    2003 年 1 月 01 日

    您的 Java 應(yīng)用程序充分利用了所運(yùn)行的 IBM eServer 硬件的能力了嗎?在本文中,作者將介紹如何判斷垃圾收集 —— Java 虛擬機(jī)執(zhí)行的收回不再使用空間的后臺(tái)任務(wù) —— 是否調(diào)節(jié)到最佳狀態(tài)。然后,他將提供一些解決垃圾收集問題的建議。

    簡(jiǎn)介

    垃圾收集實(shí)現(xiàn)是 IBM Java Virtual Machine(JVM)卓越性能的關(guān)鍵。其他多數(shù) JVM 都需要大量調(diào)整才能提供最優(yōu)性能,而 IBM JVM 利用其“開箱即用”的默認(rèn)設(shè)置,在多數(shù)情況下都能工作得很好。但是在某些情況下,垃圾收集的性能會(huì)莫名其妙地迅速降低。結(jié)果可能導(dǎo)致服務(wù)器沒有響應(yīng)、屏幕靜止不動(dòng)或者完全失效,并常常伴有“堆空間嚴(yán)重不足”這類含糊的消息。幸運(yùn)的是,多數(shù)情況下都很容易找到原因,通常也很容易糾正錯(cuò)誤。

    本文將介紹如何確定造成性能下降的潛在原因。因?yàn)槔占且粋€(gè)很大、很復(fù)雜的話題,所以本文是在已經(jīng)發(fā)表的一組相關(guān)文章的基礎(chǔ)上展開討論(請(qǐng)參閱參考資料)。雖然本文討論的多數(shù)建議都將 Java 程序看作是一個(gè)黑盒子,但仍然有一些觀點(diǎn)可以在設(shè)計(jì)或編碼時(shí)運(yùn)用,以避免潛在的問題。

    本文提供的信息適用于所有 IBM eServer 平臺(tái),除了可重新設(shè)置的 JVM 配置(請(qǐng)參閱參考資料)。除非特別說明,文中的例子都取自運(yùn)行在四處理器 AIX 5.1 上的 Java 1.3.1 build ca131-20020706。



    回頁(yè)首


    堆管理概述

    JVM 在初始化的過程中分配堆。堆的大小取決于指定或者默認(rèn)的最小和最大值以及堆的使用情況。分配堆可能對(duì)可視化堆有所幫助,如圖 1 所示,其中顯示了 heapbaseheaplimitheaptop


    圖 1. 堆的概念視圖
    堆的概念視圖

    Heapbase 表示堆底,heaptop 則表示堆能夠增長(zhǎng)到的最大絕對(duì)值。差值(heaptop - heapbase)由命令行參數(shù) -Xmx 決定。該參數(shù)和其他命令行參數(shù)都是在關(guān)于 verbosegc 和命令行參數(shù)的 developerWorks 文檔中描述的。heaplimit 指針可以隨著堆的擴(kuò)展上升,隨著堆的收縮下降。heaplimit 永遠(yuǎn)不能超過 heaptop,也不能低于使用 -Xms 指定的初始堆大小。任何時(shí)候堆的大小都是 heaplimit - heapbase

    如果整個(gè)堆的自由空間比例低于 -Xminf 指定的值(minf 是最小自由空間),堆就會(huì)擴(kuò)展。如果整個(gè)堆的自由空間比例高于 -Xmaxf 指定的值(maxf 是最大自由空間),堆就會(huì)收縮。-Xminf-Xmaxf 的默認(rèn)值分別是 0.3 和 0.6,因此 JVM 總是嘗試將堆的自由空間比例維持在 30% 到 60% 之間。參數(shù) -Xminemine 是最小擴(kuò)展大小)和 -Xmaxemaxe 是最大擴(kuò)展大小)控制擴(kuò)展的增量。這 4 個(gè)參數(shù)對(duì)固定大小的堆不起作用(用相等的 -Xms-Xmx 值啟動(dòng) JVM,這意味著 HeapLimit = HeapTop),因?yàn)楣潭ù笮〉亩巡荒軘U(kuò)展或收縮。

    當(dāng) Java 線程請(qǐng)求存儲(chǔ)時(shí),如果 JVM 不能分配足夠的存儲(chǔ)塊來滿足這個(gè)請(qǐng)求,則可以說出現(xiàn)了分配失敗(AF)。這時(shí)候就不可避免要進(jìn)行垃圾收集。垃圾收集包括收集所有“不可達(dá)的(unreachable)”引用,以便重用它們所占用的空間。垃圾收集由請(qǐng)求分配的線程執(zhí)行,是一種 Stop-The-World(STW)機(jī)制;執(zhí)行垃圾收集算法時(shí),Java 應(yīng)用程序的其他所有線程(除了垃圾收集幫助器線程之外)都被掛起。

    IBM 實(shí)現(xiàn)使用稱為 mark-sweep-compact(MSC)的垃圾收集算法,它是根據(jù)三個(gè)不同的階段命名的。

    標(biāo)記
    表和所有“可達(dá)的”或者活動(dòng)的對(duì)象。這個(gè)階段從確定“根”開始,比如線程棧上的對(duì)象、Java Native Interface(JNI)局部引用和全局引用等,然后沿著每個(gè)引用進(jìn)行遞歸,直到所有的引用都做上標(biāo)記。
    清理
    清除所有已經(jīng)分配但沒有標(biāo)記的對(duì)象,收回這些對(duì)象使用的空間。
    壓縮
    將活動(dòng)對(duì)象移動(dòng)到一起,去掉堆中的空洞(hole)和碎片。

    關(guān)于 MSC 算法的細(xì)節(jié),請(qǐng)參閱參考資料

    并行和并發(fā)

    雖然垃圾收集本身采用 STW 機(jī)制,但最新的 IBM JVM 版本都在多處理器機(jī)器上使用多個(gè)“幫助器”線程,以便減少每個(gè)階段所花費(fèi)的時(shí)間。因此,在默認(rèn)情況下,JVM 1.3.0 在標(biāo)記階段采用并行模式。JVM 1.3.1 在標(biāo)記和清理階段都采用并行模式,在標(biāo)記階段還支持一種可選的并發(fā)模式,可以使用命令行開關(guān) -Xgcpolicy:optavgpause 切換。撰寫本文時(shí),最新版本的 JVM 1.4.0 中有一種遞增壓縮模式,它并行化了(parallelize)壓縮階段。討論這些模式時(shí),重要的是要理解并行和并發(fā)的區(qū)別。

    在擁有 N 個(gè) CPU 的多處理器系統(tǒng)中,支持并行模式的 JVM 在初始化的時(shí)候會(huì)啟動(dòng) N-1 個(gè)垃圾收集幫助器線程。運(yùn)行應(yīng)用程序代碼的時(shí)候,這些線程一直處于空閑狀態(tài),只有當(dāng)啟動(dòng)垃圾收集時(shí)才調(diào)用它們。在某一特定的階段,會(huì)將一些工作分配給驅(qū)動(dòng)垃圾收集的線程和幫助器線程,因此一共有 N 線程并行地運(yùn)行在 N-CPU 機(jī)器上。如果要禁止并行模式,惟一的方法是使用 -Xgcthreads 參數(shù)改變啟動(dòng)的垃圾收集幫助器線程個(gè)數(shù)。

    采用并發(fā)模式時(shí),JVM 會(huì)啟動(dòng)一個(gè)后臺(tái)線程(不利于垃圾收集幫助器線程),在執(zhí)行應(yīng)用程序線程的同時(shí),部分工作是在后臺(tái)完成的。后臺(tái)線程會(huì)試著與應(yīng)用程序并發(fā)執(zhí)行,以完成垃圾收集的所有操作,因此在執(zhí)行垃圾收集時(shí),可以減少 STW 造成的暫停。但在某些情況下,并發(fā)處理可能對(duì)性能造成負(fù)面影響,特別是對(duì)于 CPU 敏感的應(yīng)用程序。

    下表按照 JVM 版本列出了垃圾收集各個(gè)階段的處理類型。

      標(biāo)記 清理 壓縮
    IBM JVM 1.2.2 X X X
    IBM JVM 1.3.0 P X X
    IBM JVM 1.3.1 P, C P X
    IBM JVM 1.4.0 P, C P P

    其中:

    X
    單線程操作。
    P
    并行操作(垃圾收集期間所有幫助器線程都在工作)。
    C
    并發(fā)操作(后臺(tái)線程和應(yīng)用程序線程并發(fā)操作)。



    回頁(yè)首


    分析 verbosegc 輸出

    雖然也有分析程序和其他第三方工具,但本文僅討論對(duì) verbosegc 日志的分析。這些日志由 JVM 在指定 -verbosegc 命令行參數(shù)時(shí)生成,是一種非常可靠的獨(dú)立于平臺(tái)的調(diào)試工具。要獲得完整的 verbosegc 語(yǔ)法,請(qǐng)參閱“verbosegc and command-line parameters”。

    啟用 verbosegc 可能對(duì)應(yīng)用程序的性能有一定影響。如果這種影響是無法接受的,則應(yīng)該使用測(cè)試系統(tǒng)來收集 verbosegc 日志。服務(wù)器應(yīng)用程序通常一直使 verbosegc 處于激活狀態(tài)。這是監(jiān)控整個(gè) JVM 是否運(yùn)轉(zhuǎn)良好的一種好辦法,在出現(xiàn) OutOfMemory 錯(cuò)誤的情況下,這種方法具有無可估計(jì)的價(jià)值。

    為了有效地分析 verbosegc 記錄,必須把精力集中在相關(guān)信息上,并過濾掉“噪音”。通過編寫腳本從很長(zhǎng)的 verbosegc 追蹤記錄中提取信息并不難,但是這些記錄的格式可能(而且通常確實(shí)如此)隨不同的 JVM 版本而異。下面的例子用粗體或藍(lán)色字體表示重要的信息。即使記錄的格式看起來相差很大,也很容易在 verbosegc 日志中找到這些信息。

    您刷新的了嗎?

    在嘗試本文中的建議之前,強(qiáng)烈建議您升級(jí)到最新的 JVM 服務(wù)刷新(SR)。每次進(jìn)行新的服務(wù)刷新都會(huì)有很多修正和改進(jìn),應(yīng)用新的服務(wù)刷新可以提高 JVM 的性能和穩(wěn)定性。遷移到最新的版本(比如 JVM 1.4.0 或 1.3.1,根據(jù)使用的平臺(tái))提供了增強(qiáng)的性能特性。一定要為 JVM 安裝所有必需的 OS 補(bǔ)丁(比如 AIX 上的維護(hù)級(jí)別)。這些信息記錄在隨 SDK/JRE 提供的 readme 文件中。

    正確設(shè)置堆的大小

    計(jì)算正確的堆大小參數(shù)很容易,但它可能對(duì)應(yīng)用程序啟動(dòng)時(shí)間和運(yùn)行時(shí)性能有很大的影響。初始大小和最大值分別由參數(shù) -Xms-Xmx 控制,這些值通常是根據(jù)理想情況和重負(fù)荷情況下堆的使用情況的估計(jì)來設(shè)置的,但 verbosegc 可以幫助確定這些值,而避免胡亂猜測(cè)。下面是從啟動(dòng)到完成程序的初始化(或者進(jìn)入“就緒”狀態(tài))這段時(shí)間里,一個(gè)應(yīng)用程序的 verbosegc 輸出,如下所示。

    
    			
    <GC[0]: Expanded System Heap by 65536 bytes
    <GC[0]: Expanded System Heap by 65536 bytes
    
    <AF[1]: Allocation Failure. need 64 bytes, 0 ms since last AF>
    <AF[1]: managing allocation failure, action=1 (0/3983128) (209640/209640)>
    <GC(1): GC cycle started Tue Oct 29 11:05:04 2002
    <GC(1): freed 1244912 bytes, 34% free (1454552/4192768), in 10 ms>
    <GC(1): mark: 9 ms, sweep: 1 ms, compact: 0 ms>
    <GC(1): refs: soft 0 (age >= 32), weak 5, final 237, phantom 0>
    <AF[1]: completed in 12 ms>
    

    上述記錄表明,第一次發(fā)生 AF 時(shí),堆中的自由空間為 0%(3983128 中有 0 字節(jié)可用)。此外,第一次垃圾收集之后,自由空間比例上升到 34%,略高于 -Xminf 標(biāo)記(默認(rèn)為 30%)。根據(jù)應(yīng)用程序的使用,使用 -Xms 分配更大的初始堆可能會(huì)更好一些。幾乎可以肯定的是,上例中的應(yīng)用程序在下一次 AF 時(shí)會(huì)導(dǎo)致堆擴(kuò)展。分配更大的初始堆可以避免這種情況。一旦應(yīng)用程序進(jìn)入 Ready 狀態(tài),通常不會(huì)再遇到 AF,因此也就確定了比較好的初始堆大小。類似地,通過增加應(yīng)用程序負(fù)載也可以探測(cè)到避免出現(xiàn) OutOfMemory 錯(cuò)誤的 -Xmx 值。

    如果堆太小,即使應(yīng)用程序不會(huì)長(zhǎng)期使用很多對(duì)象,也會(huì)頻繁地進(jìn)行垃圾收集。因此,自然會(huì)出現(xiàn)使用很大的堆的傾向。但是由于平臺(tái)和其他方面的因素,堆的最大大小還受物理因素的限制。如果堆被分頁(yè),性能就會(huì)急劇惡化,因此堆的大小一定不能超出安裝在系統(tǒng)上的物理內(nèi)存總量。比如,如果 AIX 機(jī)器上有 1 GB 的內(nèi)存,就不應(yīng)該為 Java 應(yīng)用程序分配 2 GB 的堆。

    即使應(yīng)用程序在擁有 64 GB 內(nèi)存的 p690 超級(jí)計(jì)算機(jī)上運(yùn)行,也不一定就能使用 -Xmx60g(當(dāng)然是 64 位的 JVM)。雖然在很長(zhǎng)時(shí)間內(nèi),應(yīng)用程序可能不會(huì)遇到 AF,但一旦發(fā)生 AF,STW 造成的停頓將很難應(yīng)付。下面的記錄取自 32 GB AIX 系統(tǒng)上分配了 20 GB 堆空間的 64 位 JVM 1.3.1(build caix64131-20021102),它展示了大型堆在這方面的影響。

    
    <AF[29]: Allocation Failure. need 2321688 bytes, 88925 ms since last AF>
    <AF[29]: managing allocation failure, action=1 (3235443800/20968372736) 
       (3145728/3145728)>
    <GC(29): GC cycle started Mon Nov 4 14:46:20 2002
    <GC(29): freed 8838057824 bytes, 57% free (12076647352/20971518464), 
       in 4749 ms>
    <GC(29): mark: 4240 ms, sweep: 509 ms, compact: 0 ms>
    <GC(29): refs: soft 0 (age >= 32), weak 0, final 1, phantom 0>
    <AF[29]: completed in 4763 ms>
    

    垃圾收集用了將近五秒鐘,還不包括壓縮!垃圾收集周期所花費(fèi)的時(shí)間直接與堆的大小成正比。一條好的原則是根據(jù)需要設(shè)置堆的大小,而不是將它配置得太大或太小。

    常見的一種性能優(yōu)化技術(shù)是將初始堆大小(-Xms)設(shè)成與最大堆大小(-Xmx)相同。因?yàn)椴粫?huì)出現(xiàn)堆擴(kuò)展和堆收縮,所以在某些情況下,這樣做可以顯著地改善性能。通常,只有需要處理大量分配請(qǐng)求的應(yīng)用程序時(shí),才在初始和最大堆大小之間設(shè)置較大的差值。但是要記住,如果指定 -Xms100m -Xmx100m,那么 JVM 將在整個(gè)生命期中消耗 100 MB 的內(nèi)存,即使利用率不超過 10%。

    另一方面,也可以使用 -Xinitsh 在開始的時(shí)候分配較大的系統(tǒng)堆,從而避免出現(xiàn) Expanded System Heap 消息。但這些消息完全可以忽略。系統(tǒng)堆隨著需要而擴(kuò)展,并且永遠(yuǎn)不會(huì)發(fā)生垃圾收集,它只包含那些度過了 JVM 實(shí)例整個(gè)生命期的對(duì)象。

    避免堆失效

    如果使用大小可變的堆(比如,-Xms-Xmx 不同),應(yīng)用程序可能遇到這樣的情況,不斷出現(xiàn)分配失敗而堆沒有擴(kuò)展。這就是堆失效,是由于堆的大小剛剛能夠避免擴(kuò)展但又不足以解決以后的分配失敗而造成的。通常,垃圾收集周期釋放的空間不僅可以滿足當(dāng)前的分配失敗,而且還有很多可供以后的分配請(qǐng)求使用的空間。但是,如果堆處于失效狀態(tài),那么每個(gè)垃圾收集周期釋放的空間剛剛能夠滿足當(dāng)前的分配失敗。結(jié)果,下一次分配請(qǐng)求時(shí),又會(huì)進(jìn)入垃圾收集周期,依此類推。大量生存時(shí)間很短的對(duì)象也可能造成這種現(xiàn)象。

    避免這種循環(huán)的一種辦法是增加 -Xminf-Xmaxf 的值。比方說,如果使用 -Xminf.5,堆將增長(zhǎng)到至少有 50% 的自由空間。同樣,增加 -Xmaxf 也是很合理。如果 -Xminf.5 等于 5,-Xmaxf 為默認(rèn)值 0.6,因?yàn)?JVM 要把自由空間比例保持在 50% 和 60% 之間,所以就會(huì)出現(xiàn)太多的擴(kuò)展和收縮。兩者相差 0.3 是一個(gè)不錯(cuò)的選擇,這樣 -Xmaxf.8 可以很好地匹配 -Xminf.5

    如果記錄表明,需要多次擴(kuò)展才能達(dá)到穩(wěn)定的堆大小,但可以更改 -Xmine,根據(jù)應(yīng)用程序的行為來設(shè)置擴(kuò)展大小的最小值。目標(biāo)是獲得足夠的可用空間,不僅能滿足當(dāng)前的請(qǐng)求,而且能滿足以后的很多請(qǐng)求,從而避免過多的垃圾收集周期。-Xmine-Xmaxf-Xminf 為控制應(yīng)用程序的內(nèi)存使用特性提供了很大的靈活性。

    標(biāo)記棧溢出

    使用 verbosegc 最重要的一項(xiàng)檢查是沒有出現(xiàn)“mark stack overflow”消息。下面的記錄顯示了這種消息及其影響。

    
    <AF[50]: Allocation Failure. need 272 bytes, 18097 ms since last AF>
    <AF[50]: managing allocation failure, action=1 (0/190698952) 
       (9584432/10036784)>
    <GC(111): mark stack overflow>
    <GC(111): freed 77795928 bytes in 1041 ms, 43% free (87380360/200735736)>
    <GC(111): mark: 949 ms, sweep: 92 ms, compact: 0 ms>
    <GC(111): refs: soft 0 (age >= 32), weak 0, final 0, phantom 0>
    <AF[50]: completed in 1042 ms>
    

    在垃圾收集的標(biāo)記階段,如果引用的個(gè)數(shù)造成 JVM 內(nèi)部的“標(biāo)記棧”溢出,就會(huì)引發(fā)這種消息。在標(biāo)記階段,垃圾收集處理代碼使用這個(gè)棧壓入所有已知的引用,以便遞歸掃描每個(gè)活動(dòng)引用。溢出的原因是堆中的活動(dòng)對(duì)象過多(或者更準(zhǔn)確地說,對(duì)象嵌套過深),這通常表明應(yīng)用程序代碼存在缺陷。除非能夠通過外部設(shè)置控制應(yīng)用程序中的活動(dòng)對(duì)象個(gè)數(shù)(比如某種對(duì)象池),那么需要在應(yīng)用程序源代碼中解決這個(gè)問題。建議使用分析工具確定活動(dòng)的引用。

    如果不能避免大量的活動(dòng)引用,并發(fā)標(biāo)記可能是一種可行的選擇。

    擺脫 finalizer

    下面的記錄顯示了一種有趣的情況:解決分配失敗花費(fèi)了 2.78 秒鐘,其中還不包括壓縮所用的時(shí)間。

    
    <AF[1]: Allocation Failure. need 56 bytes, 0 ms since last AF>
    <AF[1]: managing allocation failure, action=1 (0/521140736) 
       (3145728/3145728)>
    <GC(1): GC cycle started Thu Aug 29 19:25:45 2002
    <GC(1): freed 321890808 bytes, 61% free (325036536/524286464), in 2776 ms>
    <GC(1): mark: 2672 ms, sweep: 104 ms, compact: 0 ms>
    <GC(1): refs: soft 0 (age >= 32), weak 11, final 549708, phantom 0>
    <AF[1]: completed in 2780 ms>
    

    罪魁禍?zhǔn)资潜仨毐唤Y(jié)束掉的對(duì)象數(shù)量。無論如何,使用 finalizer 不是一個(gè)好主意,雖然在特定的情況下這是不可避免的,但是應(yīng)該僅僅將它作為完成其他方法不能實(shí)現(xiàn)的操作的不得已方法。比方說,無論如何都要避免在 finalizer 內(nèi)部執(zhí)行分配。

    避免非常大的分配

    有時(shí)候問題不是由當(dāng)時(shí)的堆狀態(tài)造成的,而是因?yàn)榉峙涫≡斐傻摹1热纾?/P>
    
    <AF[212]: Allocation Failure. need 912920 bytes, 34284 ms since last AF>
    <AF[212]: managing allocation failure, action=2 (117758504/261028856)>
    <GC(273): freed 65646648 bytes in 2100 ms, 70% free (183405152/261028856)>
    <GC(273): mark: 425 ms, sweep: 89 ms, compact: 1586 ms>
    <GC(273): refs: soft 0 (age >= 32), weak 0, final 0, phantom 0>
    <GC(273): moved 755766 objects, 43253888 bytes, reason=0, used x4C0 more 
       bytes>
    <AF[212]: completed in 2101 ms>
    

    這些記錄來自一個(gè)非常老的 JVM(準(zhǔn)確地說是 ca130-20010615),因此壓縮的原因(紅色顯示)顯示為 0。但是壓縮 256 MB 的堆花費(fèi)了 1.5 秒!為何這么差?再來看一看最初的請(qǐng)求,最初請(qǐng)求的是 912920 字節(jié) —— 將近 1 MB。

    分配的內(nèi)存塊都必須是連續(xù)的,而隨著堆越來越滿,找到較大的連續(xù)塊越來越困難。這不僅僅是 Java 的問題,使用 C 中的 malloc 也會(huì)遇到這個(gè)問題。JVM 在壓縮階段通過重新分配引用來減少碎片,但其代價(jià)是要凍結(jié)應(yīng)用程序較長(zhǎng)的時(shí)間。上面的記錄表明已經(jīng)完成了壓縮階段,分配一大塊空間的總時(shí)間超過了 2秒。

    下面的記錄說明了最糟糕的一種情況。

    
    <AF[370]: Allocation Failure. need 2241056 bytes, 613 ms since last AF>
    <AF[370]: managing allocation failure, action=2 (135487112/1291844600)>
    <GC: Wed Oct 16 10:16:46 2002
    <GC(455): freed 41815176 bytes in 28663 ms, 13% free (177302288/1291844600)>
    <GC(455): mark: 3233 ms, sweep: 328 ms, compact: 25102 ms>
    <GC(455): refs: soft 0 (age >= 32), weak 0, final 17, phantom 0>
    <GC(455): moved 15822115 objects, 615093008 bytes, reason=1, used x698 
       more bytes>
    <AF[370]: managing allocation failure, action=3 (177302288/1291844600)>
    <AF[370]: managing allocation failure, action=4 (177302288/1291844600)>
    <AF[370]: clearing all remaining soft refs>
    <GC(456): freed 176216 bytes in 3532 ms, 13% free (177478504/1291844600)>
    <GC(456): mark: 3215 ms, sweep: 317 ms, compact: 0 ms>
    <GC(456): refs: soft 16 (age >= 32), weak 0, final 0, phantom 0>
    <GC(457): freed 9592 bytes in 23781 ms, 13% free (177488096/1291844600)>
    <GC(457): mark: 3231 ms, sweep: 315 ms, compact: 20235 ms>
    <GC(457): refs: soft 0 (age >= 32), weak 0, final 0, phantom 0>
    <GC(457): moved 2794668 objects, 110333360 bytes, reason=1, used x30 more 
       bytes>
    <AF[370]: managing allocation failure, action=5 (177488096/1291844600)>
    <AF[370]: totally out of heap space>
    <AF[370]: completed in 268307 ms>
    

    請(qǐng)求的是一個(gè) 2 MB 的對(duì)象(2241056 bytes),雖然在 1.2 GB 的堆(1291844600)中有 135 MB (135487112) 自由空間,但卻不能分配一個(gè) 2 MB 的塊。雖然進(jìn)行了一切可能的搜索,花費(fèi)了 268 秒,但仍然沒有找到足夠大的塊。而且還出現(xiàn)了糟糕的“堆空間嚴(yán)重不足”消息,指出 JVM 的內(nèi)存不足。

    最好的辦法是:如果可能的話,把分配請(qǐng)求分解成較小的塊。這樣,較大的堆空間可能會(huì)起作用,但多數(shù)情況下,這樣做只是推遲了問題出現(xiàn)的時(shí)間。

    碎片及其成因

    我們?cè)倏匆豢瓷侠械钠渲幸恍校?/P>
    
    <GC(455): freed 41815176 bytes in 28663 ms, 13% free 
       (177302288/1291844600)>
    

    雖然有 177 MB 的自由空間,卻不能分配 2 MB 的塊。原因在于:雖然垃圾收集周期可以壓縮堆中的孔洞,但是堆中有些內(nèi)容不能在壓縮過程中重新分配。比如,應(yīng)用程序可能使用 JNI 分配和引用對(duì)象或數(shù)組。這些分配在內(nèi)存中是固定的,既不能被重新分配,也不能被回收,除非使用適當(dāng)?shù)?JNI 調(diào)用來釋放它們。IBM Java 服務(wù)團(tuán)隊(duì)可以幫助確定這類引用,在這種情況下,分析工具也大有用武之地。

    類似地,因?yàn)轭悏K是在堆的外部引用的,因此也是固定的。即使沒有固定的對(duì)象,大的分配一般也會(huì)導(dǎo)致出現(xiàn)碎片。所幸的是,這類嚴(yán)重的碎片很少出現(xiàn)。

    需要并發(fā)標(biāo)記嗎?

    如果由于垃圾收集造成 Java 應(yīng)用程序不時(shí)地停頓,并發(fā)標(biāo)記可以幫助減少停頓的時(shí)間,使應(yīng)用程序運(yùn)行更平穩(wěn)。但有時(shí)候,并發(fā)標(biāo)記可能會(huì)降低應(yīng)用程序的吞吐能力。建議分別使用和禁止并發(fā)標(biāo)記,使用相同的負(fù)荷來測(cè)量對(duì)應(yīng)用程序性能的影響,并加以比較。

    但是,觀察并發(fā)標(biāo)記運(yùn)行的 verbosegc 輸出可能提供大量關(guān)于加速的信息。不需要分析打印出來的記錄的每一部分,有意義的部分包括并發(fā)標(biāo)記能夠成功掃描的概率(EXHAUSTED 和 ABORTED/HALTED),以及后臺(tái)線程能夠做多少工作。

    下面的三個(gè)記錄屬于同一個(gè) Java 應(yīng)用程序,是在一次運(yùn)行中的不同階段創(chuàng)建的,它們說明了并發(fā)運(yùn)行的三種不同結(jié)果。

    第一種可能的結(jié)果是并發(fā)標(biāo)記得到 EXHAUSTED:

    
    <CON[3]: Concurrent collection, (3457752/533723648) (3145728/3145728), 
       23273 ms since last CON>
    <GC(246): Concurrent EXHAUSTED by Background helper . Target=82856559 
       Traced=57287216 (3324474+53962742) Free=3457752>
    <GC(246): Cards cleaning Done. cleaned:13668 (33 skipped). Initial count 
       13701 (Factor 0.142)>
    <GC(246): GC cycle started Tue Oct 1 00:05:56 2002
    <GC(246): freed 436622248 bytes, 82% free (443225728/536869376), in 218 ms>
    <GC(246): mark: 51 ms, sweep: 167 ms, compact: 0 ms>
    <GC(246): In mark: Final dirty Cards scan: 43 ms 158 cards (total:127 ms)
    <GC(246): refs: soft 0 (age >= 32), weak 0, final 5, phantom 0>
    <CON[3]: completed in 230 ms>
    

    這表明并發(fā)標(biāo)記按照我們所預(yù)期的那樣工作。EXHAUSTED 意味著后臺(tái)線程能夠在出現(xiàn)分配失敗之前完成自己的工作。因?yàn)楹笈_(tái)線程掃描了 3324474 個(gè)字節(jié)(而應(yīng)用程序線程掃描了 53962742 個(gè)字節(jié)),后臺(tái)線程能夠獲得足夠的 CPU 時(shí)間來減少總的標(biāo)記時(shí)間。因此,STW 中的標(biāo)記階段只用了 51 毫秒(ms),總的 STW 時(shí)間也不過 230 毫秒。這對(duì)于 512 MB 的堆來說,這已經(jīng)很不錯(cuò)了。

    下面是 ABORTED 并發(fā)標(biāo)記運(yùn)行:

    
    <AF[164]: Allocation Failure. need 962336 bytes, 75323 ms since last AF>
    <AF[164]: managing allocation failure, action=1 (83408328/533723648) 
       (3145728/3145728)>
    <GC(247): Concurrent ABORTED. Target=84703195 Traced=0 (0+0) Free=83408328>
    <GC(247): GC cycle started Tue Oct 1 00:06:22 2002
    <GC(247): freed 350077400 bytes, 81% free (436631456/536869376), in 896 ms>
    <GC(247): mark: 695 ms, sweep: 201 ms, compact: 0 ms>
    <GC(247): refs: soft 0 (age >= 32), weak 0, final 7, phantom 0>
    <AF[164]: completed in 912 ms>
    <CONCURRENT GC Free= 11530600 Expected free space= 11526488 Kickoff=11530370>
    < Initial Trace rate is 8.00>
    

    這是最糟糕的情況。并發(fā)標(biāo)記被終止,主要是因?yàn)橐峙浯笮蛯?duì)象和調(diào)用了 System.gc()。如果應(yīng)用程序頻繁地這樣做,那么就不能從并發(fā)標(biāo)記中獲得好處。

    最好是 HALTED 并發(fā)標(biāo)記:

    
    <AF[168]: Allocation Failure. need 139280 bytes, 25520 ms since last AF>
    <AF[168]: managing allocation failure, action=1 (11204296/533723648) 
       (3145728/3145728)>
    <GC(251): Concurrent HALTED (state=64). Target=118320773 Traced=35469830 
       (14765196+20704634) Free=11204296>
    <GC(251): No Dirty Cards cleaned (Factor 0.177)>
    <GC(251): GC cycle started Tue Oct 1 00:08:06 2002
    <GC(251): freed 385174400 bytes, 74% free (399524424/536869376), in 389 ms>
    <GC(251): mark: 274 ms, sweep: 115 ms, compact: 0 ms>
    <GC(251): In mark: Final dirty Cards scan: 46 ms 2619 cards (total:225 ms)
    <GC(251): refs: soft 0 (age >= 32), weak 0, final 6, phantom 0>
    <AF[168]: completed in 414 ms>
    

    從并發(fā)標(biāo)記的應(yīng)用來看,HALTED 介于 EXHAUSTED 和 ABORTED 之間,它表明只完成了部分工作。上面的記錄說明,在進(jìn)行下一次分配失敗之前,沒有完成掃描。在垃圾收集周期中,標(biāo)記階段花費(fèi)了 274 毫秒,總的時(shí)間上升到 414 毫秒。

    在理想的情況下,多數(shù)垃圾收集周期都并發(fā)收集(由于并發(fā)標(biāo)記完成其工作而觸發(fā),標(biāo)記為 EXHAUSTED),而不是出現(xiàn)分配失敗。如果應(yīng)用程序調(diào)用 System.gc(),記錄中會(huì)出現(xiàn)很多 ABORTED 行。

    在多數(shù)應(yīng)用程序中,并發(fā)標(biāo)記都可以改善性能,對(duì)于“標(biāo)記棧溢出”也有所幫助。但是,如果標(biāo)記棧溢出是由于缺陷造成的,惟一的解決辦法就是修正缺陷。

    應(yīng)該避免的開關(guān)

    下列命令行開關(guān)應(yīng)避免 使用。

    命令行開關(guān) 說明
    -Xnocompactgc 該參數(shù)完全關(guān)閉壓縮。雖然在性能方面有短期的好處,最終應(yīng)用程序堆將變得支離破碎,即使堆中有足夠的自由空間也會(huì)導(dǎo)致 OutOfMemory 錯(cuò)誤
    -Xcompactgc 使用該參數(shù)將導(dǎo)致每個(gè)垃圾收集周期都執(zhí)行壓縮,無論是否有必要。JVM 在壓縮時(shí)要做大量的決策,在普通模式下會(huì)推遲壓縮
    -Xgcthreads 該參數(shù)控制 JVM 在啟動(dòng)過程中創(chuàng)建的垃圾收集幫助器線程個(gè)數(shù)。對(duì)于 N-處理器機(jī)器,默認(rèn)的線程數(shù)為 N-1。這些線程提供并行標(biāo)記和并行清理模式中的并行機(jī)制


    回頁(yè)首


    結(jié)束語(yǔ)

    本文簡(jiǎn)單介紹了 IBM JVM 的垃圾收集和堆管理能力。以后的 verbosegc 日志很可能提供更多有用的信息。

    總結(jié)一下文中提出的建議:

    • 只要可能就升級(jí)到最新的 JVM 版本。您遇到的錯(cuò)誤可能已經(jīng)被發(fā)現(xiàn)并解決了。
    • 調(diào)整 -Xms-Xmx-Xminf,直到 verbosegc 輸出給出分配失敗數(shù)量與每次垃圾收集造成的停頓數(shù)量之間的一個(gè)可接受平衡。使用固定大小的堆避免收縮或擴(kuò)展。
    • 如果可能的話,將較大的(>500 KB)塊分解成更小的塊。
    • 不要忽略“標(biāo)記棧溢出”消息。
    • 避免使用 finalizer。
    • 試一試并發(fā)標(biāo)記。
    • 問問是否有必要調(diào)用 System.gc(),如果沒有必要?jiǎng)t刪除它。

    如您所見,這個(gè)話題可不是三言兩語(yǔ)就能說明白的。但是,只需要打一個(gè)電話或者發(fā)一封電子郵件,就能與您的好伙伴 IBM Technical Support Team 取得聯(lián)系(參考資料中的鏈接是很好的起點(diǎn))。對(duì)于您遇到的特殊情況,他們要比任何文章都更清楚。



    回頁(yè)首


    參考資料



    回頁(yè)首


    關(guān)于作者

    author

    Sumit Chawla 在 IBM eServer 部門為 ISV 提供 Java 支持。您可以通過 sumitc@us.ibm.com 和他聯(lián)系。

    posted on 2005-12-29 13:58 jacky 閱讀(350) 評(píng)論(0)  編輯  收藏 所屬分類: java
    <2025年5月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567

    常用鏈接

    留言簿(10)

    隨筆檔案

    文章分類

    文章檔案

    相冊(cè)

    收藏夾

    java

    搜索

    •  

    最新評(píng)論


    主站蜘蛛池模板: 成人免费视频77777| 日韩电影免费在线| 91嫩草私人成人亚洲影院| 免费福利在线播放| 亚洲av无码日韩av无码网站冲| 久久久久亚洲AV成人网人人网站| 四虎成人精品永久免费AV| 亚洲一久久久久久久久| 国产亚洲精品成人a v小说| 日韩免费无码一区二区视频| 精品免费久久久久久久| 一级毛片免费视频| 国产在线精品一区免费香蕉| 激情五月亚洲色图| 亚洲自偷自偷偷色无码中文| 91香蕉视频免费| 4399影视免费观看高清直播| 西西人体大胆免费视频| 久久亚洲精品无码VA大香大香 | 亚洲一级毛片视频| 免费在线观看亚洲| 亚洲毛片在线免费观看| 人妻无码久久一区二区三区免费 | 四虎成人免费影院网址| 免费一区二区无码东京热| 亚洲国产成人无码AV在线| 亚洲一区免费观看| 亚洲国产午夜福利在线播放| 亚洲高清中文字幕免费| 91成人免费福利网站在线| 国产激情久久久久影院老熟女免费| 亚洲偷自精品三十六区| 亚洲国产av美女网站| 久久久久亚洲精品无码系列| 免费观看四虎精品国产永久| 免费专区丝袜脚调教视频| 在线观看H网址免费入口| 免费一级毛片无毒不卡| 一级做受视频免费是看美女| 亚洲欧洲国产综合AV无码久久| 78成人精品电影在线播放日韩精品电影一区亚洲 |