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