代碼優化非一蹴而就,而是通過無數微小習慣積累形成的系統級效能提升。本文梳理 15 個高頻優化點,涵蓋循環、對象創建、異常、集合、字符串、並發等維度,附帶字節碼/JVM 視角原理分析與反模式對比。
// ❌ 反模式:每次迭代調用 size()
for (int i = 0; i < list.size(); i++) { ... }
// ✅ 優化:緩存邊界值
int size = list.size();
for (int i = 0; i < size; i++) { ... }
// 💡 原理:避免重複方法調用開銷(棧幀創建、參數壓棧、返回值處理),
// 特別是 size() 非 final 方法時無法內聯,JIT 編譯器也難以消除。
// 對於 ArrayList 等 RandomAccess 列表,現代 JIT 可能自動優化,但顯式緩存零成本且通用。
// ❌ 過早創建
String str = "expensive";
if (condition) list.add(str);
// ✅ 按需創建
if (condition) {
String str = "expensive";
list.add(str);
}
// 💡 原理:減少對象分配率,降低 Young GC 頻率。對於大對象、資源句柄(連接、文件流)更關鍵。
// 單例模式同理:Double-Checked Locking 或 Initialization-on-demand holder idiom 實現線程安全懶加載。
// ❌ 反模式:用異常做業務判斷
try {
int i = map.get(key); // 實際可能拋 NullPointerException
} catch (NullPointerException e) {
handleMissing();
}
// ✅ 正規寫法
if (map.containsKey(key)) {
int i = map.get(key);
} else {
handleMissing();
}
// 💡 原理:Throwable 填充堆棧跟蹤(fillInStackTrace)是重操作,涉及.native 方法遍歷棧幀。
// 異常應僅用於「異常情況」(錯誤、失敗),非正常業務分支。JVM 對異常路徑優化極少(不內聯、不逃逸分析)。
// ❌ 反模式:循環內 try-catch
for (int i = 0; i < n; i++) {
try { risky(); } catch (Exception e) { log(e); }
}
// ✅ 優化:提取到循環外
try {
for (int i = 0; i < n; i++) risky();
} catch (Exception e) { log(e); }
// 💡 原理:每次進入 try 塊都會在棧幀標記異常處理表,增加方法體大小、干擾 JIT 編譯決策。
// 若循環體極大且異常極少,外層包裹可顯著減小字節碼體積並提升編譯優化機會。
6. 字符串拼接用 StringBuilder// ❌ 循環內 + 拼接產生大量中間 String 對象 String s = ""; for (String part : parts) s += part; // ✅ StringBuilder 線程不安全但高性能 StringBuilder sb = new StringBuilder(totalEstimatedLength); for (String part : parts) sb.append(part); String result = sb.toString(); // 💡 原理:javac 會將 + 編譯為 StringBuilder.append,但循環內每次迭代都會 new StringBuilder()。 // 手動複用單個 StringBuilder 實例,配合預估長度,零中間對象、零擴容。 // 多線程場景用 StringBuffer(同步開銷大)或 ThreadLocal復用。 7. 優先基本類型避免裝箱拆箱
// ❌ 自動裝箱/拆箱 Integer sum = 0; for (int i = 0; i < 10000; i++) sum += i; // 每次 i 裝箱、sum 拆箱、相加、結果裝箱 // ✅ 基本類型累加 int sum = 0; for (int i = 0; i < 10000; i++) sum += i; // 💡 原理:Integer.valueOf() 緩存 -128~127,超出範圍每次 new Integer()。 // 長循環產生海量短命對象,壓垮 Young GC。集合泛型擦除後存儲引用,基本類型必須裝箱, // 此時可考慮 Trove/FastUtil 原始類型集合或自定義 primitive array 結構。8. 小方法利於 JIT 內聯
9. 局部變量加 final 輔助逃逸分析10. 熱路徑避免反射
11. ThreadLocal 替代同步鎖
FORMATTER = ThreadLocal.withInitial( () -> DateTimeFormatter.ofPattern("yyyy-MM-dd")); public String format(Date d) { return FORMATTER.get().format(d.toInstant().atZone(ZoneId.systemDefault())); } // 💡 原理:ThreadLocal 以線程為維度隔離狀態,消除同步開銷。注意防止內存洩漏:靜態 ThreadLocal + 線程池需手動 remove() 或使用 WeakReference 封裝。 // Java 8+ 建議直接用 java.time.DateTimeFormatter(不可變、線程安全),根除問題。12. 定長場景優先數組
13. Stream 並行流門檻判斷14. JVM 參數調優輔助15. 持續 Profiling 驗證優化效果
Java 性能優化是「鏈條上的最短板」博弈。掌握上述 15 技巧,配合 JMH 基準測試 驗證單點收益,再通過 生產級 Profiling 定位系統瓶頸,方能實現可量化、可持續的效能提升。記住:清晰正確的代碼優於晦澀的優化,除非 Profiling 證實該處為瓶頸。