優秀的架構決定性能上限,扎實的編碼決定性能下限。本文彙總 50 條 Java 編碼層面的性能優化建議,每條附帶反模式代碼、優化代碼與原理解析,供開發者在 Code Review 與重構時參考。
適用場景:控制資源併發訪問、控制實例數量節約資源、無關線程間數據共享。
// 推薦:枚舉單例(天然線程安全、防反射/序列化破壞)
public enum Singleton { INSTANCE; }
原因:static 字段生命周期與類同步,若類不卸載(如系統類加載器加載),對象常駐堆內存,導致內存洩漏風險。
// 反模式
public class A { private static B b = new B(); }
// 優化:改為實例字段,或使用弱引用緩存
策略:對象池、線程局部變量、基本類型替換包裝類型、逃逸分析輔助標量替換。
// 反模式:循環中 new
for (int i=0; i<1e6; i++) list.add(new Integer(i));
// 優化:基本類型數組或 Trove/HPPC 原始類型集合
// 熱點 getter/setter 加 final
public final void setSize(int size) { this.size = size; }
public final int getSize() { return size; }
局部變量存棧(Stack),訪問速度遠快於堆上的實例/靜態字段。JIT 對局部變量優化最激進(寄存器分配、標量替換)。
Java 10+ 局部變量類型推斷 var 不影響性能,僅語法糖。
java.util.concurrent 並發容器/鎖(CAS、分段鎖、ReadWriteLock)finalize() 導致對象進入 Finalizer 佇列,GC 需額外線程處理,延遲回收、增加停頓。資源釋放請用 try-with-resources / AutoCloseable / Cleaner (Java 9+)。
String s1 = "hello"; // 常量池,單例
String s2 = new String("hello"); // 堆新對象 + char[] 數組,雙重分配
Hashtable/Vector 全方法同步,性能顯著低於無鎖實現。單線程或外部同步時毫無理由使用舊類。
// 反模式:默認 16,負載 0.75,第 13 次 put 觸發擴容
Map map = new HashMap<>();
// 優化:預估 10000 元素
Map map = new HashMap<>(16384); // 2^14 > 10000/0.75
// 反模式:每次調用 size()
for (int i=0; i<list.size(); i++) ...
// 優化
int len = list.size();
for (int i=0; i<len; i++) ...
// 反模式:無條件創建
A a = new A(); if (cond) list.add(a);
// 優化:按需創建
if (cond) { A a = new A(); list.add(a); }
無論正常返回、異常拋出、斷言失敗,finally 必執行,是資源釋放的唯一可靠位置。Java 7+ 請用 try (Resource r = ...) {}。
// 僅適用於 2 的冪次,且需加註釋
int div4 = x >> 2; // x / 4
int mul8 = x << 3; // x * 8
現代 JIT 對除法/乘法已有優化(Magic Number + 位移),手動位移收益微小,**可讀性優先**,僅在極熱路徑經基準測試確認有效時使用。
// 反模式:默認 16,擴容復制開銷大
StringBuilder sb = new StringBuilder();
// 優化:預估 1000 字符
StringBuilder sb = new StringBuilder(1000);
方法局部變量隨棧幀銷毀自動不可達,**無需手動置 null**。但對於**長生命周期對象**(單例、靜態緩存、線程池任務隊列)中引用的短命對象,及時置 null 可提前釋放內存。
二維數組 new int[1000][1000] 實際分配 1001 個對象(1000 行數組 + 1 個主數組),內存碎片化嚴重,訪問需雙重解引用。一維 new int[1_000_000] 連續內存,CPU 緩存友好。
String.split(regex) 內部編譯 Pattern、創建 Matcher、分配數組,開銷極大。單字符分隔用 StringUtils.split(str, ',') 或 str.indexOf() 手寫循環。
| 操作 | ArrayList | LinkedList |
|---|---|---|
| 隨機訪問 get(i) | O(1) ✅ | O(n) ❌ |
| 尾部添加 add(e) | O(1) 均攤 ✅ | O(1) ✅ |
| 頭部/中間添加 | O(n) 數組復制 | O(1) 只改指針 ✅ |
| 內存開銷 | Object[] 緊湊 | Node 對象 + 指針 2-3 倍 |
| CPU 緩存 | 連續極佳 ✅ | 離散極差 ❌ |
結論:95% 場景首選 ArrayList;極少數「頻繁頭部插入、無隨機訪問」考慮 LinkedList,實測往往仍不如 ArrayDeque。
// 反模式:手寫循環
for (int i=0; i<src.length; i++) dest[i] = src[i];
// 優化:native 實現,批量內存復制,JIT 可內聯為向量指令
System.arraycopy(src, 0, dest, 0, src.length);
自實現 Map 緩存易導致 OOM、無淘汰策略。成熟庫提供 LRU/LFU/TTL、弱引用鍵、統計指標、異步加載等企業級特性。
大數組(> 1MB)直接進老年代,易導致老年代碎片化、Full GC。拆分為分塊處理、流式處理、堆外內存(ByteBuffer/Unsafe/Mmap)。
創建異常需捕獲棧幀快照,開銷極大。**不要用異常控制業務流程**(如循環終止、參數校驗)。捕獲異常開銷小,可放心 try-catch 包裹核心邏輯。
// 反模式:循環 += 創建大量中間 String 對象
String s = ""; for (String x : list) s += x;
// 優化
StringBuilder sb = new StringBuilder(list.size() * 16);
for (String x : list) sb.append(x);
Java 默認初始化(null/0/false)已在構造函數鏈完成,顯式賦相同值屬多餘。注意:字段初始化表達式調用方法可能拋 NPE(類未初始化完),建議移至 initXxx() 方法在構造函數末尾調用。
Oracle 解析器需將小寫轉大寫再比對,大寫可省去轉換步驟。對 PostgreSQL/MySQL 無影響,但統一大寫利於可讀與跨庫遷移。
連接、Statement、ResultSet、InputStream/OutputStream 均持有系統文件描述符/套接字/鎖,洩漏導致服務不可用。Java 7+ 強制 try-with-resources。
JVM GC 非實時、非全知。大對象、集合、緩存條目用完後 ref = null 可提前標記不可達,降低 GC 掃描壓力。
現代 JVM (HotSpot) 對同步塊優化極佳(鎖粗化、鎖消除、偏向鎖),**可讀性與正確性優先**,無需刻意改同步方法。
// 反模式:每次循環進入/退出異常幀
for (..) try { ... } catch (...) { ... }
// 優化:外層捕獲
try { for (..) { ... } } catch (...) { ... }
OutOfMemoryError、StackOverflowError、NoClassDefFoundError 繼承 Error,catch (Exception e) 捕獲不到,需 catch (Throwable t) 或針對性 catch (Error e)。
默認 16,擴容策略 newCapacity = (oldCapacity << 1) + 2,頻繁擴容 = 頻繁復制。預估長度一次到位。
vector.remove(0) 觸發後續所有元素左移;vector.remove(size-1) 無移動。批量清空 removeAllElements() 直接 elementCount = 0,O(1)。
// 工廠模式優化
private static final Prototype BASE = new Prototype();
public static Prototype create() { return (Prototype) BASE.clone(); }
注意:clone() 為淺拷貝,引用類型字段需手動深拷貝或不可變對象。
// 危險:外部可修改數組內容
public static final String[] ARR = {"a", "b"};
// 安全:返回副本或用 List.of() (Java 9+)
public static final List LIST = List.of("a", "b");
// 推薦
for (Map.Entry e : map.entrySet()) { K k = e.getKey(); V v = e.getValue(); }
// 避免:keySet() + get() 兩次哈希查找
數組無擴容開銷、無包裝對象、JIT 優化最激進(標量替換、向量化)。熱路徑固定大小集合用數組。
同步開銷無謂,且老類擴容策略、迭代器行為與新集合框架不一致,易埋坑。
StringBuilder 無同步,單線程性能優 10%-15%。多線程共享字符串構建器極少見,**默認用 StringBuilder**,確有共享再改 StringBuffer。
基本類型存棧/寄存器,無對象頭、無間接引用、無 GC 壓力。Valhalla 專案未來將引入內聯類進一步消除差距,當前仍是鐵律。
ArrayList 直接調用避免接口分發開銷,現代 JIT 內聯已極大消除差距。**架構層面面向接口編程,實現層面具體類實例化**,IDE 重構支持完美共存。
invokestatic 比 invokevirtual 少一次解析JIT 會內聯簡單存取器,但顯式 this.field 更直觀、無內聯失敗風險。final 修飾存取器可進一步保證內聯。
枚舉實為類,每個常量一個對象,增加類加載與內存開銷。浮點運算比整數慢,定點數/整數縮放可替代。通用業務開發忽略此條,可讀性優先。
| 場景 | 推薦 |
|---|---|
| 多線程共享構建 | StringBuffer |
| 單線程/線程局部構建 | StringBuilder |
| 編譯期常量拼接 | "a" + "b" (編譯器常量折疊) |
// 高頻替換單字符/固定字符串
str.replace(oldChar, newChar); // 無正則,極快
str.replace(CharSequence, CharSequence); // 無正則,Java 9+ 優化
// 僅當確需正則時用 replaceAll/replaceFirst
String s = "abc"; // 常量池,全局唯一
String s = new String("abc"); // 堆對象 + char[],雙重浪費
// indexOf 系列:原生優化,支持起始索引
str.indexOf("sub");
str.indexOf("sub", fromIndex);
// matches():每次編譯 Pattern,極慢
size()finally 關流System.arraycopy 替代手寫循環getter/setter 加 finalString 非 StringBuffer'c' 非 "c"總結:性能優化是權衡的藝術——時間換空間、可讀換極速、通用換專用。**以基準測試為準,以業務價值為標**,在關鍵路徑應用上述準則,其餘代碼保持清晰可維護。