程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 關注性能: 邊緣剖析

關注性能: 邊緣剖析

編輯:關於JAVA

簡介:調優的並不總是速度,有時候需要調整應用程序的其他方面,如果應用程序需要調優,要做的 第一件事通常是使用剖析程序監控應用程序。但是,剖析並不總是可行的,有時候原因可能很可笑。關注 性能的本期文章中, Jack 和 Kirk 講述了他們最近經歷的一件事:他們奉命剖析一個胖客戶機,事實上 它是如此龐大,根本沒有為剖析程序留下空間。

我們從來還沒有遇到過調優應用程序內存占用的問題。通常,我們看到的和內存有關的調優要求都涉 及到降低垃圾收集的開銷,理想情況下可以通過調整堆的大小或者改變垃圾收集算法來解決,如果不行的 話,還可以采用各種技術減少內存中的對象。但是,有時候無論分配和垃圾收集的效率如何,應用程序都 要占用很多的內存。

減肥中心之旅

最近,我們奉命降低一個胖客戶機的內存占用。雖然“胖客戶機”一詞通常表示普通的 GUI 客戶應用 程序,而這個客戶機卻是幾近肥胖症。這個客戶機在 Windows 平台上運行,處理大小的極限是 2 GB。去 掉可執行地址空間和引入各種 JNI 產品所需要的其他空間之後,該應用程序能夠使用的最大堆大小大約 是 1.2 GB 或者 1.3 GB。不幸的是,某些用戶因為要向該應用程序灌輸大量的數據,以至於占用的空間 常常接近這個極限。最明顯的調優方案是轉移到 Unix 機器上,但是因為不切實際而被排除掉了——客戶 更願意讓這個應用程序減肥。

於是,我們的任務就定下來了。對這個胖客戶機進行剖析,看看到底是什麼占用了這些空間。然後對 這些對象減肥,為以後的擴展或者更大的數據量留下空間。我們認為這事很容易。可能要花點時間,因為 減少對象的數量通常不能一蹴而就,但是這麼大的堆, 肯定有很多贅肉能夠割掉。我們這樣想。

通常的過程

我們開始了通常的內存占用縮減過程:建立測試環境、規定可再現的測試、啟動剖析程序、運行測試 、分析數據、查找調優的機會。時間不斷流逝,我們一直忙個不停……或者說我們是這樣認為的。現在, 我們進入了“運行測試”階段,剖析程序趴下了。於是我們再次嘗試。又死掉了。我們改變了剖析程序的 配置,將開銷減到最少,再次嘗試。又死掉了。根本就沒留下足夠容納剖析程序完全運行的 JVM 堆空間 ,更不用說生成任何有用的剖析數據了。而我們使用的是一種上等的商業剖析程序,一般是很可靠的,所 以我們很吃驚。

試一次,再試一次

沒關系,海裡有數不清的魚,現在也有數不清的剖析程序(關於剖析程序的最新評述,請參閱 Resources)。又是一天,又使用了一個剖析程序,怎麼樣呢?不幸的是,測試過程是驚人的相似。和一 號剖析程序差不多在同一點上,二號剖析程序又讓 JVM 崩潰了。和一號剖析程序一樣,它甚至可以做更 多的配置,重新配置,降低開銷,去掉更多的數據。但它還是和一號剖析程序一樣,也崩潰了。糟糕的是 ,三號剖析程序也沒有什麼不同。

巧妙的剖析程序

但是,四號剖析程序有了微妙的變化。對存活對象的快照進行內存分析(忽略對象的創建和垃圾收集 ,只觀察某一點上存活對象的快照),在請求進行快照之前,四號剖析程序根本沒有增加 JVM 的開銷。 成功了!我們的測試第一次在剖析程序運行的時候通過了需要拍攝快照的那個點。我們很高興。然後我們 激活了快照,於是 JVM 崩潰了。

我們又嘗試了一次,但是這個剖析程序生成快照需要太多的額外空間。根本無法工作。我們又回到了 起點!盡管還有半打商業剖析程序可供嘗試,但結果是顯然的。應該做一些橫向思考了。

具有諷刺意味的是,我們的問題正在於剖析程序本身的復雜性。我們需要某種簡單的東西。當然,簡 單並不意味著開銷低,但是既然那些復雜的剖析程序令我們失望,不妨試一試。於是我們開始掃描開放源 代碼剖析程序。

重新開始

我們首先尋找那些看來是用於內存分析的剖析程序。一號開發源代碼內存剖析程序看起來絕對簡單, 也許過於簡單了。輸出結果用處不大,只有一個類列表和每個類的對象個數。但無論如何這也算是一個不 錯的起點。它崩潰了。我們陷入了重走老路的擔憂。二號開放源代碼剖析程序甚至比一號還簡單,雖然它 實際上給出了更詳細的信息:每個對象都有堆占轉儲記錄,說明對象的大小和所屬的類。和其他剖析程序 一樣,我們用較低的配置嘗試,於是可以看到堆轉儲逐漸增大——大致就是堆的大小,然後,我們看到的 是一個 1 GB 的輸出文件。我們嘗試了它,它擊潰了虛擬機。但它確實讓我們看到了部分堆轉儲。

在處理像這種很大的因素時,必須能夠判斷所需資源的數量級和要花費的時間。轉存 1 GB 的文件可 能要花很多時間。如果沒有考慮到一個操作可能花費多長的時間,您可能錯誤地認為進程被掛起了,而實 際上它仍然在運行,只是要花費轉儲 1 GB 格式化文本所需要的時間。這個開放源代碼剖析程序正在工作 ,但是第一次測試時我們忽視了給它足夠的時間。更遭的是,第一次還沒有結束的時候,我們又迫使它進 行第二次轉儲,結果造成了崩潰。所幸的是,我們認識到問題在我們自己而不是剖析程序,有了較多的認 識之後,我們再次進行了嘗試並取得了成功。

heapprofile 剖析程序

那麼,到底哪個剖析程序成功了呢?它就是 Matthias Ernst 編寫的“heapprofile”。它僅用了一頁 C 代碼,使用 Java Virtual Machine Profiler Interface (JVMPI) 把堆轉存成最簡單的格式。甚至還 要自己編譯,網站上(請參閱 Resources)沒有提供預編譯的可執行文件。這種簡單性正是我們在這個問 題裡所需要的。沒有任何開銷。除了絕對必要的之外,沒有使用堆或者 JNI 資源。程序運行的時候它什 麼也不做,當我需要堆轉儲的時候,它僅僅遍歷一次堆,直接將每個對象的大小和類轉存到一個文件,沒 有在內存中創建任何結構,正是這種結構讓其他所有剖析程序擊潰了 JVM。

當然,事情還沒有完。現在我們需要分析結果數據,使用它確定應用程序所用的對象。幸運的是,輸 出格式很容易解析。一旦找到了造成問題的對象,我們還需要找到分配這些對象的地方。為了降低開銷, 我們采用重新編譯這幾個類的簡單策略,在構造函數中放上棧跟蹤程序,Jack 的著作(請參閱 Resources)中詳細描述了這種技術。這種簡單的技術需要在構造函數中創建(而不是拋出)異常。異常 中包含分配地點的棧蹤跡。然後可以將所有對象的這些棧列成表格。因為多數棧都是相同的,標識調用棧 以及鏈接到每個棧的相關實例個數需要存儲的數據並不很多(最多幾千個字符串)。

簡單而丑陋

這都是些簡單的技術,但並沒有很高的生產率。我們更願意使用功能完備的剖析程序輸出數據,尤其 是因為它們提供的數據更便於分析。我們本來希望從堆的根開始,向下跟蹤較大的節,直到發現大量引用 堆的對象,但是我們沒有選擇這個方法。

和通常使用調優技術相比,這次使用的技術比較簡陋。但最終我們發現了一些完全不需要的對象,使 用一些類的不同實現可以完全消除它們;另一些必需的對象也可以苗條一點,或者壓縮到一起,減少其空 間需求。對象縮減通常都是如此,胖客戶機減肥也沒有一定之規。和人類一樣,讓 Java 應用程序節食也 是很困難的事情。也和節食一樣,去掉身上多余的脂肪往往比您所想的要花費更長時間。令人遺憾的是, 雖然我們從這個胖客戶機上刮掉了兩百兆字節,但它仍然沒有瘦到足以容納“真正的”內存剖析程序的運 行。

結束語

我們曾經在 Unix 討論組看到這樣一個問題 —— “Unix 大師們使用什麼編輯文本?”,隨後的討論 紛紛開始鼓吹 vi、Emacs 等。但毫無疑問,正確的答案應該是“Unix 大師使用任何能用的工具編輯文本 。” Java 平台擁有一些非常傑出的剖析程序。但最終,調優應用程序必須分析數據,無論用何種方法, 您必須拿到這些數據。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved