posts - 15,  comments - 34,  trackbacks - 27

          關(guān)注性能: 談?wù)摾?/H1>

          您知道垃圾收集器在干什么嗎?

          developerWorks
          文檔選項(xiàng)
          '); //-->
          將此頁作為電子郵件發(fā)送

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

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

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

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


          對此頁的評價

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


          級別: 初級

          Jack Shirazi, 董事, JavaPerformanceTuning.com
          Kirk Pepperdine, 首席技術(shù)官, JavaPerformanceTuning.com

          2004 年 5 月 01 日

          您的應(yīng)用程序是否經(jīng)常出現(xiàn) out-of-memory 錯誤?用戶是否感受到響應(yīng)時間有些不穩(wěn)定?應(yīng)用程序是否在相當(dāng)長的時間內(nèi)變得沒有響應(yīng)?應(yīng)用程序的性能是否顯得遲緩了?如果對任何一個問題的回答是肯定的,那么您很可能遇到了垃圾收集的問題了。先別進(jìn)行優(yōu)化,且聽聽 JavaPerformanceTuning.com 的 Jack Shirazi 和 Kirk Pepperdine 來解釋如何識別垃圾收集問題,并由此幫助您回答這個問題:您知道垃圾收集器在干什么嗎?

          許多開發(fā)人員認(rèn)為,內(nèi)存管理至多是開發(fā)業(yè)務(wù)邏輯的主要任務(wù)之外的一項(xiàng)不重要的工作 —— 直到業(yè)務(wù)邏輯不能像預(yù)期的或者測試時那樣執(zhí)行得好。出現(xiàn)這種情況時,就需要知道哪里出錯了及其原因,這意味著要理解應(yīng)用程序如何與底層計(jì)算資源(特別是內(nèi)存)進(jìn)行交互。理解應(yīng)用程序如何利用內(nèi)存的最好方式是觀察垃圾收集器的行動。

          為什么我的應(yīng)用程序不連貫了?

          Java 虛擬機(jī)中最大的一個性能問題是應(yīng)用程序線程與同時運(yùn)行的 GC 的互斥性。垃圾收集器要完成其工作,需要在一段時間內(nèi)防止所有其他線程訪問它正在處理的堆空間(內(nèi)存)。按 GC 的術(shù)語,這段時間稱為“stop-the-world”,并且,正如其名字所表明的,在垃圾收集器努力工作時,應(yīng)用程序有一個急剎車。幸運(yùn)的是,這種暫停通常是很短的,很難察覺到,但是很容易想像,如果應(yīng)用程序在隨機(jī)的時刻出現(xiàn)隨機(jī)且較長時間的暫停,對應(yīng)用程序的響應(yīng)性和吞吐能力會有破壞性的影響。

          不過 GC 只是應(yīng)用程序出現(xiàn)不連貫和停頓的一個原因。那么如何確定 GC 對產(chǎn)生這些問題是否負(fù)有責(zé)任呢?要回答這個問題,我們需要測量垃圾收集器的工作強(qiáng)度,并當(dāng)在系統(tǒng)中進(jìn)行改變時繼續(xù)這些測量,以定量地確定所做的改變是否有所期望的效果。

          我需要多少內(nèi)存?

          普遍接受的信念是,在系統(tǒng)中添加內(nèi)存將解決許多性能問題。雖然這個原則對于 JVM 來說經(jīng)常是正確的,但是太多好東西可能對性能是有害的。因此技巧在于 Java 應(yīng)用程序需要多少內(nèi)存就給它多少,但是絕不多給。問題是,應(yīng)用程序需要多少內(nèi)存?對于應(yīng)用程序不連貫的情況,我們需要觀察垃圾收集行為以了解看它做的是否比所需要的更多。這些觀察將告訴我們所做的改變是否有所期望的效果。



          回頁首


          測量 GC 的活動

          生成 GC 日志的標(biāo)準(zhǔn)方式是使用 -verbose:gc 旗標(biāo),設(shè)置這個旗標(biāo)后,垃圾收集器會在每次運(yùn)行時生成它所做的事情的匯總,一般是寫入到控制臺(通過標(biāo)準(zhǔn)輸出或者標(biāo)準(zhǔn)錯誤)。許多 VM 支持一個允許 verbose GC 輸出轉(zhuǎn)向到一個文件的選項(xiàng)。例如,在 Sun 的 1.4 JVM 中,可以使用開關(guān) -Xloggc:filename 將 GC 輸出寫到文件中。對于 HP JVM,要使用 -Xverbosegc=file 開關(guān)。在本文中,我們將分析 Sun 1.4.2 和 IBM 1.4.1 JVM 捕獲的 verbose GC 輸出。

          使用這個方法監(jiān)視內(nèi)存使用的一個最大好處是它對應(yīng)用程序的性能的影響很小。不幸的是,這個解決方案并不完美,因?yàn)檫@些日志文件可能變得特別大,而維護(hù)它們可能需要重新啟動 JVM。盡管如此,這種技術(shù)在生產(chǎn)環(huán)境中仍然是可行的,因?yàn)樗梢詭椭\斷只在這種環(huán)境中才列出的性能問題。

          更深入觀察 GC

          -verbose:gc 旗標(biāo)生成的輸出根據(jù) JVM 廠商而不同,不同的垃圾收集器選項(xiàng)會報告特定于該實(shí)現(xiàn)的信息。例如,由 IBM JVM 生成的輸出比由 Sun JVM 生成的輸出冗長得多,而 Sun 的輸出更適合于由工具讀取。就是說,每一個 GC 日志傳達(dá)基本信息 —— 使用了多少內(nèi)存、恢復(fù)了多少內(nèi)存、GC 周期用了多少時間,以及在收集期間是否采取了其他行動。從這些基本測量中,我們可以推斷出有助于更好地理解所發(fā)生的事情的細(xì)節(jié)。我們要計(jì)算的統(tǒng)計(jì)如下所示:

          • 考慮的運(yùn)行時的持續(xù)時間
          • 收集總數(shù)
          • 收集頻率
          • 收集所用最長時間
          • 收集所用總時間
          • 收集所用平均時間
          • 收集的平均間隔
          • 分配的字節(jié)總數(shù)
          • 每次收集每秒分配的字節(jié)數(shù)
          • 恢復(fù)的字節(jié)總數(shù)
          • 每次收集每秒恢復(fù)的字節(jié)總數(shù)

          理解了暫停時間,我們就可以理解 GC 對應(yīng)用程序不響應(yīng)是否負(fù)有部分或者全部責(zé)任了。一種實(shí)現(xiàn)這一任務(wù)的方法是將詳細(xì)(verbose) GC 日志中的 GC 活動與系統(tǒng)采集的其他日志(如 Web 服務(wù)器日志中的請求 backlog)相對應(yīng)。幾乎可以肯定最長的 GC 暫停會導(dǎo)致整個系統(tǒng)響應(yīng)可感覺的下降,所以知道什么時候響應(yīng)下降是很有用的,這樣就可以將 GC 活動與應(yīng)用程序吞吐相關(guān)聯(lián)起來。

          另一種可能的競爭因素是堆內(nèi)存分配和恢復(fù)的比率,稱為 churn。產(chǎn)生大量立即釋放的對象的應(yīng)用程序通常會受到 churn 的拖累。更高的 churn 比率對垃圾收集器加以很大壓力,創(chuàng)造了更多的內(nèi)存資源競爭,這又可導(dǎo)致更長的暫停或者可怕的 OutOfMemoryError

          了解應(yīng)用程序是否遭遇這些問題的一個方法是測量所考慮的整個運(yùn)行時期間 GC 所占用的總時間。有了這種計(jì)算,我們就可以了解 GC 做的是否比它所應(yīng)該做的更多。讓我們推導(dǎo)出進(jìn)行這種判斷所需要的公式。



          回頁首


          Sun GC 日志記錄

          清單 1 是由 Sun 1.4.2_03 JVM 以 -Xloggc:filename 運(yùn)行默認(rèn)的標(biāo)記-清除收集器所生成的日志記錄的例子。可以看到,日志項(xiàng)非常精確地記錄了每次所做的事情。


          清單 1. 使用 -Xloggc:filename 旗標(biāo)的 GC 日志記錄
          
          69.713: [GC 11536K->11044K(12016K), 0.0032621 secs]
          69.717: [Full GC 11044K->5143K(12016K), 0.1429698 secs]
          69.865: [GC 5958K->5338K(11628K), 0.0021492 secs]
          69.872: [GC 6169K->5418K(11628K), 0.0021718 secs]
          69.878: [GC 6248K->5588K(11628K), 0.0029761 secs]
          69.886: [GC 6404K->5657K(11628K), 0.0017877 secs]
          

          首先注意到的可能是每一項(xiàng)日志記錄是寫在一組方括號內(nèi)的。其他 GC 算法,如并發(fā)收集器, 可能將一些值分解為更細(xì)的信息。如果是這種情況,這些被分解的值會由包圍在嵌入的一組方括號中的細(xì)節(jié)所替代,這使工具可以更容易地處理詳細(xì) GC 輸出。

          我們的研究首先從分析清單 1 中標(biāo)記為 69.713 的記錄開始。這個標(biāo)記是 JVM 開始后的秒數(shù)和毫秒數(shù)的時間戳。在這個例子中,JVM 在這個 GC 周期開始之前運(yùn)行了 69.713 秒。從左到右的字段為:執(zhí)行的收集的類型、GC 之前的堆使用、總的堆能力和 GC 事件的持續(xù)時間。從這個描述中我們可以看出第一個 GC 事件是一個小的收集。在 GC 開始之前,使用了 11536 Kb 的堆空間。在完成時,使用了 11044 Kb,堆能力為 12016 Kb,而整個收集用了 .0032621 秒。下一個事件,一個完全的 GC,在 69.717 秒時或者上一個小 GC 事件之后 0.003 秒時開始。注意,如果將小 GC 事件的持續(xù)時間加到其開始時間上,就會看到它在完全的 GC 開始之前不到 1毫秒結(jié)束。因此我們可以得出結(jié)論:小收集沒有恢復(fù)足夠的空間,這種失敗觸發(fā)了完全的 GC。對應(yīng)用程序來說,這像是一個持續(xù)了 0.1462319 秒的事件。讓我們繼續(xù)確定如何計(jì)算其他值。

          GC 日志記錄的參數(shù)確定

          我們通過確定每個 GC 日志記錄中的值的參數(shù)來開始分析:

          R(n) = T(n): [ <GC> HB->HE(HC), D]

          n 清單中記錄的索引,1 是第一個,m 是最后一個
          R(n) GC 記錄
          T(n) 第 n 個 GC 發(fā)生的時間
          HB GC 之前堆的數(shù)量
          HE GC 之后使用的堆數(shù)量
          HC 堆空間的總量
          D GC 周期的持續(xù)時間

          有了這些定義,我們現(xiàn)在可以推導(dǎo)出用于計(jì)算前面描述的值的公式。



          回頁首


          基本值

          我們要計(jì)算的第一個值是日志所覆蓋的運(yùn)行時整個持續(xù)時間。如果要考慮每一項(xiàng)記錄,那么就要分析最后一項(xiàng)記錄的時間戳。因?yàn)榍鍐?1 只表示全部日志記錄的一部分,我們需要從最后一項(xiàng)中提取出第一個時間戳。盡管對這個例子來說,這個數(shù)字足夠精確,但是為了絕對準(zhǔn)確,需要加上最后 GC 的持續(xù)時間。其原因是時間戳是在 GC 開始時記錄的,而記錄表示在記錄了時間戳以后發(fā)生的事情。

          剩余值是取記錄中相應(yīng)的值的總和計(jì)算的。值得注意的是恢復(fù)的字節(jié)可以通過分析記錄中測量的關(guān)系而計(jì)算,而分配的字節(jié)可以通過分析前后記錄測量之間的關(guān)系計(jì)算。例如,如果考慮在時間戳 69.872 和 69.878 之間發(fā)現(xiàn)的記錄對,可以用第一個記錄中 GC 之后占用的內(nèi)存數(shù)量減去第二個記錄在 GC 之前占用的字節(jié)數(shù)量計(jì)算在新的一代(generation)中分配的字節(jié)數(shù)量: 6248 Kb - 5418 Kb = 830 Kb 。下面表 1 展示了其他值的公式。

          要找出最長的 GC 暫停,我們只需要查看持續(xù)時間并尋找 D(n) (記錄 n 的持續(xù)時間)的最大值。

          表 1. 統(tǒng)計(jì)公式

          統(tǒng)計(jì) 計(jì)算(時間單位調(diào)整為秒)
          運(yùn)行時持續(xù)時間 RT = (T(M) + D(M)) - T(1)
          小收集的總數(shù) TMC = Sum( R(n)) 其中 GC(n) = GC
          完全收集的總數(shù) TFC = Sum( R(n)) 其中 GC(n) = Full
          收集頻率(小收集) CFM = TMC / RT
          收集頻率(完全) CFF = TFC / RT
          收集的時間(最長的小收集) MAX(D(n)) for all n 其中 GC(n) = GC
          收集的時間(最長的完全收集) MAX(D(n)) for all n 其中 GC(n) = Full
          小收集的時間(總數(shù)) TTMC = Sum(D(n)) for all n 其中 GC(n) = GC
          完全收集的時間(總數(shù)) TTFC Sum(D(n)) for all n 其中 GC(n) = Full
          收集的時間(總數(shù)) TTC = TTMC + TTFC
          小收集的時間(平均) ATMC = TTMC / RT
          完全收集的時間(平均) ATFC = TTFC / RT
          收集的時間(平均) ATC = TTC / RT
          收集間隔(平均) Sum( T(n+1) - T(n)) / (TMC + TFC) for all n
          分配的字節(jié)(總數(shù)) TBA = Sum(HB(n+1) - HE(n)) 對于所有 n
          分配的字節(jié)(每秒) TBA / RT
          分配的字節(jié)(每次收集) TBA / (TMC + TFC)
          小收集恢復(fù)的字節(jié)(總數(shù)) BRM = Sum(HB(n) - HE(n)) 其中 GC(n) = GC
          完全收集恢復(fù)的字節(jié)(總數(shù)) BRF = Sum(HB(n) - HE(n)) 其中 GC(n) = Full
          恢復(fù)的字節(jié)(總數(shù)) BRT = BRM + BRF
          恢復(fù)的字節(jié)(每次小收集) BRPM = BRM / TMC
          恢復(fù)的字節(jié)(每次完全收集) BRPF = BRF / TMF
          恢復(fù)的字節(jié)(小收集每秒) BRP = BRM / TTMC
          恢復(fù)的字節(jié)(完全收集每秒) BRF = BRF / TTFC

          可以從公式中看出,我們經(jīng)常需要分別考慮完全 GC 和小 GC。小 GC 與完全 GC 有根本性的不同,一般來說前者至少比后者要快一個數(shù)量級。我們可以通過快速分析清單 1 看出這一點(diǎn) —— 最長的小收集比完全收集快 50 倍。

          下面表 2 顯示對清單 1 中的值使用表 1 中的公式的結(jié)果。

          表 2. Sun GC 日志分析

          統(tǒng)計(jì) 計(jì)算(時間單位調(diào)整為秒)
          運(yùn)行時持續(xù)時間 (69.886 + 0.0017877) - 69.713 = 0.1747877
          小收集總數(shù) 5
          完全收集總數(shù) 1
          收集頻率(小收集) 5 / 0.1747877 = 28.6 per second
          收集頻率(完全) 1 / 0.1747877 = 5.27 per second
          收集時間(最長的小收集) 0.0032621
          收集時間(最長的完全收集) 0.1429698
          小收集的時間(總數(shù)) 0.0123469
          完全收集的時間(總數(shù)) 0.1429698
          收集的時間(總數(shù)) 0.1553167
          小收集的時間(平均) 7.1%
          完全收集的時間(平均) 81.8%
          收集的時間(平均) 88.9%
          收集間隔(平均) .173/5=0.0346
          分配的字節(jié)(總數(shù)) 3292
          分配的字節(jié)(每秒) 18834 Kb/second
          分配的字節(jié)(每次收集) 549 Kb
          小收集恢復(fù)的字節(jié)(總數(shù)) 3270 Kb
          完全收集恢復(fù)的字節(jié)(總數(shù)) 5901 Kb
          恢復(fù)的字節(jié)(總數(shù)) 5901 + 3270 = 9171 Kb
          恢復(fù)的字節(jié)(每次小收集) 3270/5 = 654
          恢復(fù)的字節(jié)(每次完全收集) 5901/1 = 5901
          恢復(fù)的字節(jié)(小收集每秒) 3270/0.0123469 = 264843 Kb/second
          恢復(fù)的字節(jié)(完全收集每秒) 5901/0.1429698 = 41274K/second

          表 2 包含從這些看來簡單的日志中推算出的大量信息。取決于所關(guān)注的問題,可能不需要計(jì)算所有這些值,因?yàn)槠渲幸恍┲当攘硪恍└杏谩τ趹?yīng)用程序長時間不響應(yīng)的情況,要關(guān)注的是 GC 持續(xù)時間和計(jì)數(shù)。



          回頁首


          IBM GC 日志記錄

          與 Sun 日志不同,IBM 日志特別冗長。即使這樣,仍然需要一個指導(dǎo)以完全理解所提供的信息。清單 2 是這種 verbose:gc 日志文件的一部分。


          清單 2. IBM JVM verbose:gc 輸出
          
          <AF[31]: Allocation Failure. need 528 bytes, 969 ms since last AF>
          <AF[31]: managing allocation failure, action=1 (0/97133320) (1082224/1824504)>
            <GC(31): GC cycle started Wed Feb 25 23:08:41 2004
            <GC(31): freed 36259000 bytes, 37% free (37341224/98957824), in 569 ms>
            <GC(31): mark: 532 ms, sweep: 37 ms, compact: 0 ms>
            <GC(31): refs: soft 0 (age >= 32), weak 0, final 2, phantom 0>
          <AF[31]: managing allocation failure, action=3 (37341224/98957824)>
            <GC(31): need to expand mark bits for 116324864-byte heap>
            <GC(31): expanded mark bits by 270336 to 1818624 bytes>
            <GC(31): need to expand alloc bits for 116324864-byte heap>
            <GC(31): expanded alloc bits by 270336 to 1818624 bytes>
            <GC(31): need to expand FR bits for 116324864-byte heap>
            <GC(31): expanded FR bits by 544768 to 3637248 bytes>
            <GC(31): expanded heap by 17367040 to 116324864 bytes, 47% free, ratio:0.417>
          <AF[31]: completed in 812 ms>
          
          <AF[32]: Allocation Failure. need 528 bytes, 1765 ms since last AF>
          <AF[32]: managing allocation failure, action=1 (0/115394264) (930600/930600)>
            <GC(32): GC cycle started Wed Feb 25 23:08:43 2004
            <GC(32): freed 54489184 bytes, 47% free (55419784/116324864), in 326 ms>
            <GC(32): mark: 292 ms, sweep: 34 ms, compact: 0 ms>
            <GC(32): refs: soft 0 (age >= 32), weak 0, final 0, phantom 0>
          <AF[32]: completed in 328 ms>
          
          <AF[33]: Allocation Failure. need 528 bytes, 1686 ms since last AF>
          <AF[33]: managing allocation failure, action=1 (0/115510592) (814272/814272)>
            <GC(33): GC cycle started Wed Feb 25 23:08:45 2004
            <GC(33): freed 56382392 bytes, 49% free (57196664/116324864), in 323 ms>
            <GC(33): mark: 285 ms, sweep: 38 ms, compact: 0 ms>
            <GC(33): refs: soft 0 (age >= 32), weak 0, final 18, phantom 0>
          <AF[33]: completed in 324 ms>
          

          清單 2 中有三項(xiàng) GC 日志記錄。我將不會提供完全的說明,而是推薦一篇由 Sam Borman 所寫的很好的文章“Sensible Sanitation”(請參閱 參考資料)。對于我們的目的,需要與像對 Sun JVM 的日志那樣推算出同樣類型的信息。好的方面是有一些計(jì)算結(jié)果已經(jīng)是現(xiàn)成的了。例如,如果分析 AF[31] (事件 31 分配失敗),將會看到 GC 之間的間隔、恢復(fù)的內(nèi)存數(shù)量、事件的持續(xù)時間。我們可以根據(jù)這些數(shù)字計(jì)算其他所需要的值。



          回頁首


          這些數(shù)字有什么意義

          如何看待這些數(shù)字取決于所要得到的結(jié)果。在許多服務(wù)器應(yīng)用程序中,它歸結(jié)為縮短暫停時間,這又歸結(jié)為減少所發(fā)生的完全收集的持續(xù)時間和次數(shù)。下個月,我們將探討如何用這些信息調(diào)優(yōu)曾經(jīng)受這個問題困擾的一個真實(shí)應(yīng)用程序。



          回頁首


          參考資料



          回頁首


          作者簡介

          Jack Shirazi 是 JavaPerformanceTuning.com的董事,也是 Java Performance Tuning, 2nd Edition (O'Reilly) 一書的作者。


          Kirk Pepperdine 是 Java Performance Tuning.com 的首席技術(shù)官,并且在過去 15 年一直關(guān)注對象技術(shù)和性能調(diào)優(yōu)。Kirk 是 Ant Developer's Handbook (MacMillan)一書的合著者。

          posted on 2005-12-29 13:55 jacky 閱讀(277) 評論(0)  編輯  收藏 所屬分類: java
          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿(10)

          隨筆檔案

          文章分類

          文章檔案

          相冊

          收藏夾

          java

          搜索

          •  

          最新評論


          主站蜘蛛池模板: 大足县| 佳木斯市| 万安县| 林西县| 阿图什市| 新干县| 清镇市| 昆山市| 通海县| 深水埗区| 嘉义市| 介休市| 淮安市| 梅州市| 隆德县| 商南县| 墨竹工卡县| 宜君县| 瓮安县| 南川市| 那坡县| 望城县| 钟祥市| 扶绥县| 台前县| 涡阳县| 河曲县| 芒康县| 定安县| 莱芜市| 洪洞县| 凌云县| 葵青区| 朝阳县| 同仁县| 清徐县| 阜阳市| 开江县| 沽源县| 建平县| 镇江市|