在業務測試前,必須量測硬體基準,否則無法判斷瓶頸在軟體還是硬體:
基準測試腳本在調優過程中可重複執行,驗證優化成效。
錄製真實用戶操作腳本 (JMeter, LoadRunner) 作為起點,逐步增加思考時間、資料參數化。
原則:一次只調一個參數,測試、記錄、對比。重啟後必須暖機 (Warm-up) 再測試。
TraceUtil 記錄超過閾值 (如 1 秒) 且佔比 > 20% 的方法呼叫,輸出至日誌供事後分析。public class TraceUtil {
private final Logger logger;
private final long threshold = 1000; // ms
private long begin, offtime;
private final String threadInfo, targetId;
public TraceUtil(Logger logger, Thread thread, String targetId, long begin) {
this.logger = logger;
this.threadInfo = thread.getId() + "-" + thread.getName();
this.targetId = targetId;
this.begin = begin;
}
public void trace(String event) {
long duration = System.currentTimeMillis() - begin;
long increment = duration - offtime;
offtime = duration;
float pct = (float) increment / duration * 100;
if (duration > threshold && pct > 20) {
logger.error("Response time too large: [{}], {}/{} ({}), {}, {}",
threadInfo, increment, duration, pct + "%", event, targetId);
}
}
}
當 CPU 高但吞吐低,常因 I/O 等待或同步鎖爭用。JSP 片段示範列印所有執行緒 CPU 佔用與阻塞資訊:
<%@ page import="java.lang.management.*, java.util.*" %>
<%
ThreadMXBean mx = ManagementFactory.getThreadMXBean();
mx.setThreadContentionMonitoringEnabled(true);
ThreadInfo[] infos = mx.dumpAllThreads(false, false);
for (ThreadInfo ti : infos) {
long cpu = mx.getThreadCpuTime(ti.getThreadId());
out.println("Thread: " + ti.getThreadName() + "#" + ti.getThreadId()
+ " CPU: " + cpu + " ns"
+ " Blocked: " + ti.getBlockedTime() + " ms, " + ti.getBlockedCount()
+ " Lock: " + (ti.getLockInfo() != null ? ti.getLockInfo().getClassName() : "none"));
}
%>
大量執行緒阻塞在同一 synchronized 方法/區塊,導致 CPU 閒置。檢查自有代碼與第三方庫是否過度使用同步;改用 java.util.concurrent 非阻塞資料結構、縮小鎖範圍、採用 ThreadLocal、無鎖演算法。
效能調優是系統工程,需耐心、度量、迭代,而非靠直覺猜測。