MyCAT 反復出現 java.lang.OutOfMemoryError: Java heap space,卻發現 wrapper.conf 配置了 -Xmx4G。本文實戰演示通過 jmap -heap 穿透真實堆大小,揭示 wrapper.java.initmemory/maxmemory 單位陷阱導致 MaxHeapSize=16MB 的離譜現場,給出生產級 JVM 參數修正方案。
| 錯誤類型 | 堆疊特徵 | 所在內存區 | 典型原因 |
|---|---|---|---|
| Java heap space | TimerXX 線程大量拋出 | Java Heap | 堆太小 / 對象洩漏 / 大結果集 |
| Direct buffer memory | Bits.reserveMemory / ByteBuffer.allocateDirect | Direct Memory (堆外) | MaxDirectMemorySize 不足 / Netty 泄漏 |
[root@mycat ~]# jmap -heap $(pgrep -f MycatStartup)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 16777216 (16.0MB) <-- 關鍵證據:僅 16MB!
NewSize = 1310720 (1.25MB)
MaxNewSize = 17592186044415 MB
OldSize = 5439488 (5.1875MB)
...
# wrapper.conf 片段
wrapper.java.additional.10=-Xmx4G
wrapper.java.additional.11=-Xms1G
wrapper.java.initmemory=4G # 錯誤:單位已固定為 MB,實際生效 4MB
wrapper.java.maxmemory=15G # 錯誤:實際生效 15MB,且覆蓋 -Xmx
機制: Tanuki Wrapper 解析 wrapper.java.initmemory / maxmemory 時只讀數字,忽略單位後綴,並以 MB 為單位傳給 JVM -Xms/-Xmx。寫 4G → 讀為 4 → -Xms4m -Xmx15m → JVM 向上取整為 16MB。
# 測試不同值對 MaxHeapSize 影響
wrapper.java.maxmemory=17 -> MaxHeapSize=18MB
wrapper.java.maxmemory=4096 -> MaxHeapSize=4096MB (4GB,觸及 32 位/Wrapper 限制)
wrapper.java.maxmemory=5000 -> MaxHeapSize=4096MB (封頂)
# wrapper.conf 修正版
# 刪除或註釋:
# wrapper.java.initmemory=4G
# wrapper.java.maxmemory=15G
# 僅保留標準 JVM 參數(單位自帶,無歧義)
wrapper.java.additional.10=-Xmx4G
wrapper.java.additional.11=-Xms1G
# Direct Memory 另行配置
wrapper.java.additional.12=-XX:MaxDirectMemorySize=10G
MyCAT 提供兩種啟動方式,JVM 參數來源不同,必須保持一致:
| 啟動方式 | 配置來源 | 關鍵參數位置 |
|---|---|---|
bin/mycat start | conf/wrapper.conf | wrapper.java.additional.* |
bin/startup_nowrap.sh | 腳本內 JAVA_OPTS | -Xms2G -Xmx2G -XX:MaxDirectMemorySize=2G |
建議: 統一使用 wrapper.conf 管理參數,startup_nowrap.sh 僅作調試用,或同步修改兩處。
wrapper.java.additional.1=-server
wrapper.java.additional.2=-Xms4G
wrapper.java.additional.3=-Xmx4G
wrapper.java.additional.4=-XX:NewRatio=2
wrapper.java.additional.5=-XX:SurvivorRatio=8
wrapper.java.additional.6=-XX:MaxDirectMemorySize=8G
wrapper.java.additional.7=-XX:+UseG1GC
wrapper.java.additional.8=-XX:MaxGCPauseMillis=200
wrapper.java.additional.9=-XX:+ParallelRefProcEnabled
wrapper.java.additional.10=-XX:+PrintGCDetails
wrapper.java.additional.11=-XX:+PrintGCDateStamps
wrapper.java.additional.12=-Xloggc:/usr/local/mycat/logs/gc_%p_%t.log
wrapper.java.additional.13=-XX:+UseGCLogFileRotation
wrapper.java.additional.14=-XX:NumberOfGCLogFiles=10
wrapper.java.additional.15=-XX:GCLogFileSize=100M
wrapper.java.additional.16=-XX:+HeapDumpOnOutOfMemoryError
wrapper.java.additional.17=-XX:HeapDumpPath=/usr/local/mycat/logs/heapdump.hprof
-Dcom.sun.management.jmxremote.port=9999 接入 Prometheus JMX Exporterjvm_memory_bytes_used{area="heap"} / jvm_memory_bytes_max{area="heap"} > 80% 告警gceasy.io / GCViewer 定期審視 Full GC 頻率與耗時JVM 參數配置是「一行配置、十行註釋、百行驗證」的嚴謹工程。穿透 Wrapper 層直達 jmap -heap 驗證實際生效值,應成為中間件部署的標準驗收動作。