java.lang.OutOfMemoryError 是 Java 開發中最讓人頭痛的錯誤之一,字面意思是「記憶體不足」。當 JVM 無法分配足夠的記憶體給新建對象,且垃圾收集器也無法釋放更多空間時,就會拋出此錯誤。本文將系統梳理 OOM 的常見原因、各類錯誤類型,並針對 Tomcat、WebLogic、Resin、Eclipse+JBoss 等主流環境提供具體調優方案。
導致 OOM 的根本原因通常集中在以下五類:
clear() 或設為 null,導致 GC 無法回收。-Xms、-Xmx、-XX:MaxPermSize(JDK 7 及以前)或 -XX:MaxMetaspaceSize(JDK 8+)配置不足。| 錯誤訊息 | 所屬區域 | 典型場景 |
|---|---|---|
Java heap space | 堆內存 | 對象實例過多、大對象分配、記憶體洩漏 |
PermGen space | 永久代 | 類加載過多、JSP 預編譯、大量第三方 JAR(JDK 7 及以前) |
Metaspace | 元空間 | 類元數據過多、動態生成大量類(JDK 8+) |
GC overhead limit exceeded | GC 佔用過高 | 98% 時間花在 GC,且回收不到 2% 堆空間 |
Unable to create new native thread | 本地線程 | 線程數過多、棧大小設定過大 |
List、Map、Set 及時 clear() 或賦值 null,特別是靜態集合、單例中持有的集合。setFirstResult/setMaxResults 分頁,問題解決。session.invalidate() 主動銷毀。-Xms/-Xmx 參數。原理:JVM 堆 = Young Generation + Old Generation。默認 -Xms = 物理內存 1/64,-Xmx = 1/4。當 98% CPU 用於 GC 且可用堆 < 2% 時拋出異常。
最佳實踐:
-Xms 與 -Xmx 設為相同值,避免動態擴縮容開銷。-Xmx 不超過物理內存 80%。-Xmn (新生代) 設為 -Xmx 的 1/4~1/3。配置示例 (修改 TOMCAT_HOME/bin/catalina.sh,在 echo "Using CATALINA_BASE..." 下方添加):
JAVA_OPTS="-server -Xms2g -Xmx2g -Xmn512m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
註:JDK 8+ 已移除永久代,改用 Metaspace,請用 MetaspaceSize/MaxMetaspaceSize 替代 PermSize/MaxPermSize。
成因:類、元數據存放區。JDK 7 為 PermGen,JDK 8+ 為 Metaspace(使用本地內存)。大量 JSP 預編譯、動態代理生成類、大量第三方 JAR 會耗盡此區。
配置示例 (同修改 catalina.sh):
# JDK 7
JAVA_OPTS="-server -XX:PermSize=128m -XX:MaxPermSize=256m"
# JDK 8+
JAVA_OPTS="-server -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m"
建議:將公共第三方 JAR 移至 tomcat/shared/lib 或 common/lib,減少重複加載佔用。
修改 bea/weblogic/common/bin/commEnv.sh (Linux) 或 commEnv.cmd (Windows) 中的 MEM_ARGS:
MEM_ARGS="-Xms512m -Xmx1024m -XX:MaxPermSize=256m" # JDK 7
MEM_ARGS="-Xms512m -Xmx1024m -XX:MaxMetaspaceSize=256m" # JDK 8+
-Dprogram.name=run.bat -Djava.endorsed.dirs="D:/JBoss405/bin/../lib/endorsed"
-Xms256m -Xmx1024m -XX:PermSize=128m -XX:MaxPermSize=256m # JDK 7
# 或 JDK 8+
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m
若用命令行啟動,修改 bin/run.conf 中的 JAVA_OPTS 同樣追加上述參數。
修改 /usr/local/resin/bin/httpd.sh 中的 args 參數:
args="-Xms256m -Xmx512m"
根據服務器實際物理內存由運維調整,原則同 Tomcat。
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump,事後用 Eclipse MAT、JVisualVM、Arthas 分析支配樹、GC Roots 引用鏈,精準定位洩漏對象。-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags (JDK 9+) 或 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/logs/gc.log (JDK 8),觀察 Full GC 頻率、耗時、回收效率。OutOfMemoryError 不是單一問題,而是「記憶體需求 > 可用記憶體」的綜合癥狀。解決路徑遵循:代碼優化(分頁、釋放引用、避免大對象) > 參數調優(-Xms/-Xmx/-Xmn/Metaspace) > 容器專項配置 > Dump 深度分析。養成壓測時監控內存、GC 曲線的習慣,才能在上線前攔截風險。