理論上 Java 不比其他語言更費內存,但大量「陋習」代碼導致對象暴增、GC 壓力大、堆水位高。本文梳理 9 個常見陷阱及優化方案。
new Boolean() / new Integer()問題:JDBC、ORM 大量調用 setBoolean(new Boolean(val)),產生海量微小對象。
優化:使用靜態常量或 valueOf():
// Boolean 只有兩個實例
ps.setBoolean("flag", Boolean.valueOf(true)); // 內部返回 Boolean.TRUE/FALSE
// Integer 緩存 -128~127
Integer i = Integer.valueOf(100); // 命中緩存
// 自定義工具類兼容不同 JDK(如 IBM JDK)
IntegerUtils.valueOf(100);
+問題:循環中 s += "x" 每次創建新 StringBuilder + 新 String,極其耗內存。
優化:
StringBuilder,多線程用 StringBuffer。問題:Map 無限增長,最終 OOM。
優化:引入成熟緩存框架,實現 LRU/FIFO/過期淘汰:
影響:每層調用消耗棧幀,深層繼承增加對象頭開銷、方法分派成本。
建議:組合優於繼承;扁平化架構;關鍵路徑內聯優化(JIT 會處理,但代碼結構要清晰)。
原則:就近定義、最小作用域。循環體外定義的引用變量會延長對象存活時間,阻礙 GC。
static 字段風險:靜態字段生命週期 = 類加載器生命週期,持有大對象極易洩漏(尤其 Web 容器熱部署)。
替代:常量用 final;狀態數據遷移至單例 Bean、緩存、ThreadLocal 或數據庫。
場景:數據庫連接、線程、大數組、解析器、Socket 等。
方案:使用成熟池化庫(HikariCP、Tomcat JDBC Pool、Apache Commons Pool2 自定義池)。
後果:文件句柄、Socket、數據庫連接洩漏,間接導致內存/系統資源耗盡。
鐵律:try-with-resources(Java 7+)或 finally 塊確保 close()。
生產必備啟動參數:
-Xms4g -Xmx4g
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+PrintGCDetails -XX:+PrintGCDateStamps
-Xloggc:/logs/gc-%t.log
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps/
定期分析 GC 日誌,觀察分配速率、提升速率、老年代增長趨勢。
| 陋習 | 優化方向 | 收益 |
|---|---|---|
| new Wrapper() | valueOf() / 靜態常量 | 減少 90%+ 微小對象 |
| 字符串 + | StringBuilder | 消除中間對象,提速 10x+ |
| 無界 Map 緩存 | Caffeine/Ehcache | 可控內存、命中率優化 |
| static 濫用 | 遷移至受管 Bean | 消除類加載器洩漏 |
| 無池化 | 引入連接池/線程池 | 降低 GC、提升吞吐 |
內存管理無小事,細節決定系統穩定性上限。養成良好編碼習慣,配合科學監控,才能構建高可用 Java 系統。