在啟動公司內嵌的tomcat容器時出現報錯, 如下:
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (malloc) failed to allocate 160088 bytes for AllocateHeap
# An error report file with more information is saved as:
# /users/xxx/hs_err_pidxxxx.log
然后查看/users/xxx/hs_err_pidxxxx.log
內容:
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 357564416 bytes for committing reserved memory.
# Possible reasons:
# The system is out of physical RAM or swap space
# The process is running with CompressedOops enabled, and the Java Heap may be blocking the growth of the native heap
# Possible solutions:
# Reduce memory load on the system
# Increase physical memory or swap space
# Check if swap backing store is full
# Decrease Java heap size (-Xmx/-Xms)
# Decrease number of Java threads
# Decrease Java thread stack sizes (-Xss)
# Set larger code cache with -XX:ReservedCodeCacheSize=
# JVM is running with Unscaled Compressed Oops mode in which the Java heap is
# placed in the first 4GB address space. The Java Heap base address is the
# maximum limit for the native heap growth. Please use -XX:HeapBaseMinAddress
# to set the Java Heap base and to place the Java Heap above 4GB virtual address.
# This output file may be truncated or incomplete.
#
# Out of Memory Error (os_linux.cpp:2749), pid=4252, tid=0x00007f3f38bb5700
#
# JRE version: (8.0_201-b09) (build )
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.201-b09 mixed mode linux-amd64 compressed oops)
# Core dump written. Default location: /users/ems/core or core.4252 (max size 521000 kB). To ensure a full core dump, try "ulimit -c unlimited" before starting Java again
#
...
翻譯過來就是本地內存分配失敗, 可能的原因有兩種
- 系統物理內存或虛擬內存不足
- 程序在壓縮指針模式下運行, Java堆會阻塞本地堆的增長
然后使用free -m
命令查詢, 發現內存足夠:
total used free shared buffers cached
Mem: 7983 5415 2568 0 170 1460
-/+ buffers/cache: 3784 4199
Swap: 15257 71 15186
那么嘗試按第二個問題進行解決, 可能的方案有兩種:
- 禁止使用壓縮指針模式
- 方法: 在catalina.sh中
JAVA_OPTS
的值后面添加-XX:-UseCompressedOops, 再重啟tomcat
- 方法: 在catalina.sh中
- 將Java堆的起始地址設置成使Java堆大小+起始地址大於4G,
- 原因: 請參考 https://blogs.oracle.com/poonam/running-on-a-64bit-platform-and-still-running-out-of-memory,
- 方法: 在這里我將起始地址簡單直接的設為4G即4294967296
在嘗試過這兩種方法后發現依然報同樣的錯誤
這時我在想會不會是堆內存過大, 導致系統無法分配內存, 於是進行嘗試: 把堆內存減少一半, 看看效果.
- 方法: 在在catalina.sh中
JAVA_OPTS
的值中把原來的-Xms1024m -Xmx2048m
改為-Xms512m -Xmx1024m
, 再重啟tomcat
結果JVM啟動成功, 問題解決.
后續思考: 為什么在可用內存充足的情況下系統無法分配給JVM更多內存? 一直沒有想到完美的解釋, 如果有明白的兄弟可以指教一下.
嘗試對后續思考進行解答: 原因應該還是內存不足, 可能操作系統會預留一些內存, 而我的機器上默認的啟動參數是-Xms1024m -Xmx2048m
, 可能已經超過了系統允許分配的最高值, 因此無法分配內存. 當我使用java -Xms10m -Xmx20m
可以啟動成功, java -Xms500m -Xmx2000m
會失敗, 因此, 應該還是內存不足的問題
對后續思考的最終解答及該問題的完美解決方案:
這個問題是由於/proc/meminfo
下的vm.overcommit_memory
被設置成不允許overcommit
造成的
首先了解一下overcommit
的意思: 用戶進程申請的是虛擬地址, 而這個虛擬地址是不允許任意申請的, 因為虛擬內存需要物理內存做支撐, 如果分配太多虛擬內存, 會對性能參數影響. overcommit
就是對虛擬內存的過量分配
vm.overcommit_memory
的用處: 控制過量分配的策略. 這個參數一共有3個可選值:
-
0
: Heuristic overcommit handling. 就是由操作系統自己決定過量分配策略 -
1
: Always overcommit. 一直允許過量分配 -
2
: Don't overcommit. 不允許過量分配
在這個案例里面, 使用sysctl vm.overcommit_memory
來查看, 發現vm.overcommit_memory = 2
, 即采用的是不允許過量分配的設置. 而在錯誤日志中也證明了這一點:
CommitLimit: 15951192 kB
Committed_AS: 15837036 kB
Committed_AS: OS會預測啟動這個程序時, 所有的進程可能會用到多少的內存, 如果超過了CommitLimit, 就會報錯
解決方案是sudo sysctl vm.overcommit_memory=0
, 即vm.overcommit_memory = 0
, 允許系統自己決定過量分配策略