即使做了很好的前期規劃,應用程序仍可能出現重大的性能問題。 這篇由兩個部分構成的文章給出了一些幫助您分析這些問題的技術, 重點關注的是基于 Eclipse 的富客戶機 Windows 應用程序。 這是第 1 部分,我將向您展示如何度量基于 Eclipse 的 RCP 應用程序性能, 判斷速度降低的原因是由于 CPU 還是 I/O 瓶頸, 保持 UI 線程空閑以保持響應性。 我還會給您提供一些避免線程錯誤以及提高應用程序啟動性能的技巧。 第 2 部分將討論一些跟蹤內存問題的方法。 這些技術中的大部分也適用于 Eclipse 之外的應用程序。
關鍵概念
在您探究性能問題時,第一步是要判定出問題的任務究竟是受限于 CPU(CPU-bound) 還是受限于I/O(I/O-bound)。
CPU-bound 意味著 CPU 是完成工作的瓶頸,因此一個更快的 CPU 會 更快地完成動作。舉例而言,如果您的 CPU 是 100MHz,用 50 秒 對 100,000 個電子郵件排序,那么 您可以期望一個 1GHz 的 CPU 能在 5 秒內完成排序。
但提高 CPU 頻率并不一定會使任務運行更快。I/O-bound 任務是指那些以 I/O 為瓶頸的任務。 一個很好的例子是從磁盤讀取大型文件或從 Web 站點下載文件。 一般而言,CPU 速度對 I/O-bound 任務沒有影響,因為 處理文件讀取的是 I/O 子系統。典型情形是,源設備不能保持足夠高的傳輸速率以 使得 CPU 保持繁忙。CPU 在其等待數據時沒有事情做,干脆休眠了。
不要猜測
不要費心去猜測為什么應用程序速度緩慢。 您的猜測或許是錯誤的,不要猜測,要分析。
所以有多個因素可能導致速度降低:CPU 繁忙、應用程序做的 I/O 過多、 等待 I/O 完成或上述情況的某種組合。憑空猜測其原因沒有意義,使用工具判斷則更為有效。 下一節介紹了一些工具,可以幫助判定任務是受限于 CPU 還是 I/O。
用于 Windows 的監視工具
對應用程序做監視的成本
開發人員經常會問對應用程序做分析會不會改變應用程序的行為。 這是一個好現象,不過他們應該問的是分析會對應用程序有何種程度 的影響。 總的來說,某種技術對應用程序的觸及越深入,影響會越大。 記錄比取樣的開支更大,所以也比幾個固定位置的日志消息有更大的開銷。
舉個例子,絕大多數剖析器都會帶來很大的開銷,使方法調用的時間不再有效,只有相對時間有效。根據您選擇的是對調用堆棧做取樣還是記錄每個方法調用,分析所耗費的時間會有所不同。 記錄使一個小而快的方法被頻繁調用成為熱點,因為隨著方法不斷被調用, 記錄的開銷不斷增加。 而另一方面,堆棧取樣不會導致熱點,因為它在調用堆棧時所耗費得時間非常少。
根據您所做的分析類型,您可能會不關心分析應用程序行為所導致 的開銷。比如說,您可能會愿意為準確捕獲某方法被調用的次數而付出較高代價, 因為那對于理解應用程序順序分析至關重要。
圖 1. Perfmon Add Counters 對話框
我一般會添加如下計數器,選中 Process 作為 Performance 對象:
% User Time:該進程正在處理的工作量。
Handle count:該進程打開的句柄數。 其中,句柄數代表了某個應用程序打開的文件或套接字數目。
IO Data Bytes/sec:該進程正在操作的磁盤、網絡或設備 I/O 量。
Private Bytes:與該進程相關的不能被共享的內存量 —— 應用程序大小的粗略估算。(該值對應于 Task Manager 中的 VM 大小。)
Thread Count:與該進程相關的線程數目。
另一方面,如果我要 “實時” 觀察重復問題, 我會使用 Sysinternals Process Explorer。它的優勢是能夠關注一個進程而不是整臺機器。 在考察一個特定問題時,您通常希望只觀察的涉及到的那個應用程序。
在 Process Explorer 內雙擊您要監視的應用程序,打 開該進程的 Properties 對話框(參見 圖 2):
圖 2. Javaw 進程的屬性
圖 2 中的 javaw 進程來自于 JEdit。在本例中,我從磁盤打開了一個 14MB 的文本文件。 從下往上觀察圖 2 的三個圖表,您可以發現:
I/O Bytes History 圖表中的大型峰值表示磁盤 I/O 要讀取那個 14MB 文件。 在線上停留片刻,會顯示已讀取 14MB。
Private Bytes 跳升至 33MB。Java™ 堆(heap)會為 14MB 文本文件要求 28MB 空間, 這主要是因為 Java 語言使用 16 位 Unicode 字符。Swing 和 JEdit 為管理編輯還需要另外 5MB 空間。
CPU Usage History 的大型峰值說明文件讀取到內存后的執行了處理動作。 在此例中, JEdit 在更新顯示,對文件做語法高亮等處理。
如果操作緩慢是由于 I/O 所致,您需要判斷是哪部分應用程序導致了 I/O 問題。如果 操作緩慢是由于 CPU 所致,需要使用分析器。
設置分析器
為 RCP 應用程序所做的分析器設置與為其他很多類型的應用程序所做的不同, 因為 RCP 應用程序一般由一個可執行程序或 shell 腳本啟動,而不是直接啟動 Java 運行庫。 問題還可能更復雜,因為 RCP 啟動器為 Java 處理程序創建命令行參數并啟動它。 這種更高級別的間接性會在您嘗試分析或精細控制 JVM 調用參數時造成困難。 為了不依賴于應用程序啟動器而啟動 Java 運行庫, 我經常提取 Java 命令行并直接啟動它。下面是一種方法:
正常地啟動應用程序。
運行后,啟動 Process Explorer 并找到 javaw 或 Java 進程。打開進程屬性 并從詳細信息(參見 圖 3)里復制命令行參數。
將復制內容粘貼到一個批處理文件并按照需求做修改。(按這種方式,您可以創建一個核心 批處理文件,配備幾個變量用來添加或刪除 VM 參數、類路徑入口等等。)
圖 3. 用于 Java 進程的命令行參數
確定 UI 線程中的長時間運行動作
絕大多數現代操作系統都有一個單獨的 UI 線程。 同樣的,Standard Widget Toolkit(SWT)也是如此。您必須小心 不要讓這個單獨的線程執行長時間運行操作,比如大量的磁盤 I/O、網絡調用或 其他那些大工作量操作。