Ubuntu 下 Tomcat 内存不足的排查与优化
一、先快速定位问题类型
- 查看 Tomcat 日志:重点关注 logs/catalina.out、logs/startup.log、logs/localhost.log,从异常栈和 GC 信息判断是堆内存、元空间、线程栈还是系统内存不足。
- 观察异常关键词:
- Java heap space → 堆内存不足;
- PermGen space(Java 7 及更早)→ 永久代不足;
- Metaspace(Java 8+)→ 元空间不足;
- 出现 “There is insufficient memory for the Java Runtime Environment to continue”“Cannot allocate memory”“Cannot create GC thread” → 多为系统物理内存或 swap 不足、或线程/句柄等资源限制导致。
- 检查系统资源:
- 内存与交换分区:
free -h、swapon -s;
- 线程与句柄:
ulimit -a、ps -eLf | wc -l;
- 端口占用:
ss -lntp | grep ':8080\|:8005'。
以上步骤能快速确定是“JVM 参数过小”还是“系统资源瓶颈”。
二、针对不同错误类型调整配置
- 堆内存不足(Java heap space)
- 适度提升堆上限:将 -Xms 与 -Xmx 设为相同值以减少 GC 波动,例如:
-Xms2g -Xmx2g(请结合机器内存与容器/虚拟机配额)。
- 元空间不足(Metaspace,Java 8+)
- 增加元空间上限:
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m(避免无限制增长)。
- 永久代不足(PermGen,Java 7 及更早)
- 增加永久代:
-XX:PermSize=128m -XX:MaxPermSize=256m。
- 线程栈溢出(StackOverflowError)
- 减少单线程栈深度:
-Xss256k(默认通常为 1M,视应用而定)。
- 系统层面内存不足(无法提交内存、无法创建 GC 线程)
- 先减小 -Xmx、降低线程数(见下一节)、减少应用加载的 Class/依赖;
- 增加物理内存或开启/扩大 swap;
- 放宽系统限制:如
ulimit -n 65535(文件描述符)、ulimit -u 65535(用户进程数),并在 /etc/security/limits.conf 持久化;
- 确认使用 64 位 Java + 64 位 OS。
这些参数与系统侧调整能分别解决不同层面的“内存不足”。
三、在 Ubuntu 正确设置 Tomcat 的 JVM 参数
- 通用方式(适用于多数安装方式):编辑 $CATALINA_HOME/bin/catalina.sh,在文件靠前位置加入(或导出)变量:
- Java 8 示例:
export JAVA_OPTS="-server -Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -Xss256k"
- Java 7 示例:
export JAVA_OPTS="-server -Xms2g -Xmx2g -XX:PermSize=128m -XX:MaxPermSize=256m -Xss256k"
- 如需 GC 日志便于排查,可追加:
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/opt/tomcat/logs/gc.log
- Debian/Ubuntu 包管理安装的 Tomcat(如 tomcat7/tomcat8/tomcat9):优先编辑 /etc/default/tomcatX(X 为版本号),在该文件中设置或追加 JAVA_OPTS,例如:
JAVA_OPTS="-Djava.awt.headless=true -Dfile.encoding=UTF-8 -server -Xms2g -Xmx2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
- 使配置生效并重启:
- 包管理安装:
sudo systemctl restart tomcatX
- 解压版:
$CATALINA_HOME/bin/shutdown.sh && $CATALINA_HOME/bin/startup.sh
- 验证是否生效:
ps -ef | grep tomcat | grep -E 'Xms|Xmx|Metaspace|PermSize' 应能看到你设置的参数。
以上路径与方式覆盖“解压版”和“deb 包安装版”的主流做法,避免改错文件导致配置不生效。
四、应用与容器的配套优化建议
- 控制并发与线程栈:在 conf/server.xml 中结合业务调优,例如:
<Connector port="8080" protocol="HTTP/1.1" maxThreads="200" minSpareThreads="50" acceptCount="250" enableLookups="false" URIEncoding="UTF-8" />
- 线程数减少可直接降低内存压力(每个线程会占用栈空间)。
- 减少常驻对象与类加载:清理无用依赖、避免在应用启动阶段加载大体积库、关闭开发期功能(如热加载、自动部署)。
- 开启并定期分析 GC 日志:观察 Full GC 频率、晋升失败、元空间占用增长趋势,据此微调堆与元空间。
- 资源隔离:若在同一台机器运行多个实例,合理分配每个实例的 -Xmx 与线程数,避免“总和超过物理内存”。
这些优化能显著降低内存压力并提升稳定性。
五、常见坑位与快速修复清单
- 把 -Xmx 设得过大导致系统无法分配内存或无法创建 GC 线程:先降低到机器可承受范围,再逐步上调;必要时增加 swap 或扩容。
- 32 位 Java/系统导致地址空间受限:改用 64 位 Java + 64 位 OS。
- 文件描述符/进程数限制过低:在
/etc/security/limits.conf 提升 nofile、nproc,并重启会话或系统服务。
- 包安装版改了 catalina.sh 却无效:应修改 /etc/default/tomcatX 中的 JAVA_OPTS。
- 启动闪退但看不到错误:前台运行
./catalina.sh run 或查看 catalina.out,优先从日志定位。
以上为高频根因与对应处理,能快速排除环境与配置层面的“坑”。