在仅 2GB 内存 的云服务器上同时运行 Tomcat(Java Web 应用)和 MySQL,极易因内存不足触发 OOM(Out of Memory),尤其在并发稍高或应用未优化时。以下是务实、可落地的优化方案,兼顾稳定性与可用性,避免“纸上谈兵”:
✅ 一、核心原则(先立规矩)
| 项目 | 推荐分配(严格上限) | 说明 |
|---|---|---|
| 系统预留 | ≥ 300MB | Linux 基础服务、内核缓存、SSH 等 |
| MySQL 最大内存 | ≤ 600MB | 启动后常驻 + 峰值可控 |
| Tomcat JVM 堆内存 | -Xms400m -Xmx400m |
禁止动态伸缩!固定大小防GC抖动 |
| JVM 元空间/直接内存 | -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m -XX:MaxDirectMemorySize=128m |
防止元空间爆炸 |
| 剩余缓冲 | ≥ 200MB | 预留给 OS page cache、临时文件、突发流量 |
⚠️ 总内存占用 ≈ 系统(300) + MySQL(600) + Tomcat堆(400) + JVM非堆(200) + OS缓存 = ≈1.5GB,留足余量。
✅ 二、MySQL 极简优化(my.cnf)
[mysqld]
# 内存控制(关键!)
innodb_buffer_pool_size = 400M # InnoDB核心缓存,占MySQL总内存60%以上
innodb_log_file_size = 64M # 日志文件大小,避免过大刷盘压力
innodb_flush_method = O_DIRECT # 绕过OS缓存,减少内存争用
# 连接与查询(防连接数爆炸)
max_connections = 50 # 默认151 → 必须降!查 `show processlist` 确认真实并发
wait_timeout = 60 # 空闲连接60秒断开(应用层也需配连接池超时)
interactive_timeout = 60
# 查询优化(降低单次内存消耗)
sort_buffer_size = 256K # 每连接排序缓存,勿设1M+
read_buffer_size = 128K
read_rnd_buffer_size = 256K
join_buffer_size = 256K
tmp_table_size = 32M # 内存临时表上限
max_heap_table_size = 32M
# 关闭非必要功能(省内存+提启动速度)
skip-log-bin # 关闭binlog(除非需要主从/恢复)
skip-performance-schema # 关闭性能监控(开发/测试环境)
innodb_file_per_table = ON # 推荐,但非内存项
[client]
default-character-set = utf8mb4
✅ 验证命令:
-- 检查实际内存使用(近似)
SELECT
(SELECT variable_value FROM information_schema.global_variables WHERE variable_name = 'innodb_buffer_pool_size') AS buffer_pool,
(SELECT variable_value FROM information_schema.global_variables WHERE variable_name = 'max_connections') AS max_conn;
-- 查看当前连接数
SHOW STATUS LIKE 'Threads_connected';
✅ 三、Tomcat 优化(server.xml + JVM参数)
1. bin/setenv.sh(Linux)添加 JVM 参数:
#!/bin/sh
export JAVA_OPTS="-server
-Xms400m -Xmx400m
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m
-XX:MaxDirectMemorySize=128m
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:+ExplicitGCInvokesConcurrent
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/opt/tomcat/logs/heapdump.hprof
-Djava.awt.headless=true
-Dfile.encoding=UTF-8"
✅ 为什么选 G1?2G小堆下比CMS更稳定,暂停可控;禁用
-XX:+UseParallelGC(吞吐优先,停顿长)。
2. conf/server.xml 连接器调优:
<Connector
port="8080"
protocol="org.apache.coyote.http11.Http11Nio2Protocol"
maxThreads="100" <!-- 根据CPU核数:2核→≤100,避免线程过多OOM -->
minSpareThreads="10"
acceptCount="100" <!-- 队列长度,防瞬间洪峰 -->
connectionTimeout="20000"
redirectPort="8443"
compression="on"
compressionMinSize="1024"
noCompressionUserAgents="go-http-client"
compressableMimeType="text/html,text/xml,text/plain,application/javascript,application/json"
/>
<!-- 关闭AJP(除非必须) -->
<!-- <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> -->
3. conf/context.xml 防会话膨胀:
<Context>
<!-- 禁用session持久化(无状态应用推荐) -->
<Manager className="org.apache.catalina.session.PersistentManager" saveOnRestart="false">
<Store className="org.apache.catalina.session.FileStore"/>
</Manager>
<!-- 或缩短超时 -->
<Manager sessionTimeout="30" />
</Context>
✅ 四、应用层必做(否则配置全白搭)
| 问题 | 解决方案 | 检查方式 |
|---|---|---|
| 数据库连接泄漏 | Spring Boot:spring.datasource.hikari.leak-detection-threshold=60000(ms)手动close ResultSet/Statement/Connection |
日志搜 leak,监控 HikariCP active connections |
| 大结果集查询 | 加 LIMIT、分页、流式读取(ResultSet.setFetchSize(Integer.MIN_VALUE)) |
EXPLAIN 查 rows > 1w 的SQL |
| 缓存滥用 | 禁用 @Cacheable 大对象;用 Redis 替代本地缓存(Caffeine需设 maximumSize=1000) |
jstat -gc <pid> 观察老年代增长 |
| 日志爆炸 | Logback:<appender> 中 maxFileSize="10MB" + maxHistory="7" |
du -sh /opt/tomcat/logs/* |
| 上传文件过大 | Tomcat:<Connector ... maxParameterCount="1000" maxPostSize="10485760"/>(10MB) |
Nginx前置限流更佳 |
✅ 五、监控与兜底(救命措施)
-
实时监控内存(每5分钟检查):
# 检查Java进程RSS(真实物理内存) ps -o pid,rss,comm -p $(pgrep -f "tomcat.*jvm") | awk '{sum+=$2} END {print "Tomcat RSS: " sum/1024 " MB"}' # MySQL内存估算 mysql -e "SHOW ENGINE INNODB STATUSG" | grep "BUFFER POOL" -A 5 -
OOM自动保护(systemd示例):
# /etc/systemd/system/tomcat.service [Service] MemoryLimit=600M # systemd强制限制 OOMScoreAdjust=-500 # 降低被OOM Killer杀死概率(MySQL设-800,Tomcat-500) -
启用Linux OOM Killer日志:
dmesg -T | grep -i "killed process" # 若频繁触发,立即检查是哪个进程(MySQL/Tomcat/其他)吃内存
✅ 六、终极建议(省钱又省心)
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 生产环境 | ❌ 拒绝2G跑Tomcat+MySQL ✅ 升级到 4GB(主流云厂商约¥30/月) |
成本远低于故障排查+数据丢失风险 |
| 学习/测试 | ✅ 用 Docker 分离环境:docker run --memory=600m mysql:8.0docker run --memory=600m tomcat:9-jre11 |
内存硬隔离,互不干扰,易复位 |
| 轻量API服务 | ✅ 改用 SQLite(无服务端)+ Jetty(比Tomcat轻30%) | 适合单机、低并发、无事务强需求场景 |
🔍 快速诊断清单(发现OOM后立即执行)
# 1. 查内存大户
ps aux --sort=-%mem | head -10
# 2. Tomcat GC情况
jstat -gc $(pgrep -f "tomcat.*jvm") 5s 5
# 3. MySQL连接数 & 睡眠连接
mysql -e "SHOW PROCESSLIST;" | grep "Sleep" | wc -l
# 4. 检查Swap是否被大量使用(危险信号!)
free -h && swapon --show
# 5. 查看最近OOM事件
dmesg -T | tail -20 | grep -i "killed process"
如按此方案严格执行,2GB服务器可稳定支撑 日均1~2万PV、并发<50 的中小型后台管理/内部系统。但请牢记:内存是硬约束,配置只是缓解,架构减负才是根本。若业务增长,优先考虑:
- 数据库拆分(读写分离)
- 静态资源交由CDN
- 接口异步化(消息队列削峰)
- 升级硬件(最经济的长期方案)
需要我帮你生成 完整的 my.cnf + setenv.sh 模板,或针对你的具体应用(Spring Boot/传统WAR)做定制化调优,欢迎贴出 top、jstat、SHOW PROCESSLIST 截图,我来逐行分析 👇
轻量云Cloud