在仅 2GB 内存 的云服务器上同时运行 Tomcat(Java Web 应用)和 MySQL,极易因内存超限触发 OOM(Out of Memory),尤其在并发稍高或应用未优化时。以下是务实、可落地的优化方案,聚焦「避免 OOM」这一核心目标,兼顾稳定性与基本可用性:
⚠️ 前提原则(务必遵守)
- 关闭所有非必要服务:如 Redis、Nginx(若非必需)、监控X_X等;
- 应用精简:部署单个轻量级应用(如 Spring Boot 管理后台),禁用 DevTools、Actuator(或仅暴露健康端点);
- 禁止 swap 用于 Java 进程(Linux
swappiness=1或vm.swappiness=1),避免 GC 卡死; - 监控先行:部署
htop、jstat -gc <pid>、mysqladmin status,每小时检查内存/堆使用率。
✅ 一、Tomcat 优化(JVM 内存严格控制)
1. JVM 参数(关键!设为固定堆,禁用动态扩容)
# 修改 $CATALINA_HOME/bin/setenv.sh(无则新建),添加:
export JAVA_OPTS="-Xms512m -Xmx512m
-XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=128m
-XX:+UseG1GC -XX:MaxGCPauseMillis=200
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tomcat/heap.hprof
-Djava.security.egd=file:/dev/./urandom"
- ✅ 堆内存锁定为 512MB(
-Xms512m -Xmx512m):避免动态扩容导致内存碎片或超限; - ✅ 元空间限制 128MB:防止 ClassLoader 泄漏(常见于热部署);
- ✅ G1 GC + 低延迟目标:比 Parallel GC 更适合小内存场景;
- ✅
-Djava.security.egd=...:提速 Tomcat 启动(避免熵池阻塞)。
💡 若应用极轻(纯静态+简单 Servlet),可降至
-Xms384m -Xmx384m,为系统/Mysql 留更多余量。
2. Tomcat 自身配置($CATALINA_HOME/conf/server.xml)
<!-- 减少连接数 & 缩短超时 -->
<Connector port="8080" protocol="HTTP/1.1"
maxThreads="50" <!-- 关键!默认200→50 -->
minSpareThreads="10"
maxConnections="200" <!-- 默认8192→200 -->
connectionTimeout="20000"
keepAliveTimeout="15000"
maxKeepAliveRequests="100"
acceptCount="50" <!-- 队列长度,避免堆积 -->
compression="on"
compressionMinSize="1024"
noCompressionUserAgents="gozilla, traviata" />
- ✅
maxThreads=50:每个线程约占用 1MB 栈内存 → 50×1MB = 50MB,可控; - ✅
acceptCount=50:避免请求队列过大耗尽内存。
3. 其他
- 删除
$CATALINA_HOME/webapps/下所有示例应用(docs,examples,manager,host-manager); - 禁用
session持久化(<Manager pathname="" />); - 使用
logback替代java.util.logging,降低日志内存开销。
✅ 二、MySQL 优化(内存精打细算)
✅ 目标:MySQL 总内存 ≤ 600MB(含缓冲池+连接内存)
1. 关键配置(/etc/my.cnf 或 /etc/mysql/my.cnf)
[mysqld]
# 内存相关(重点!)
innodb_buffer_pool_size = 384M # 必须 ≤ 40% 总内存(2G×40%=800M,留余量→384M)
innodb_log_file_size = 64M # 日志文件大小,384M BP → 64M 合理
innodb_flush_method = O_DIRECT # 避免双缓冲
# 连接与缓存(严控)
max_connections = 50 # 每连接约 2-3MB 内存 → 50×2.5≈125MB
table_open_cache = 400 # 默认2000→400,减少 open_tables 开销
sort_buffer_size = 256K # 每连接排序缓冲,勿超512K
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 # 关闭性能库(省100MB+)
skip-innodb_doublewrite # 仅测试环境可关(生产慎用)
2. 启动后验证
-- 检查实际内存使用(近似值)
SELECT
(SELECT variable_value FROM information_schema.global_variables WHERE variable_name = 'innodb_buffer_pool_size') / 1024 / 1024 AS ibp_mb,
(SELECT variable_value FROM information_schema.global_variables WHERE variable_name = 'max_connections') * 2.5 / 1024 AS conn_mb;
-- 应显示:ibp_mb ≈ 384, conn_mb ≈ 125 → 总计约 500MB+
3. 其他建议
- 使用
mysqltuner.pl定期分析(curl https://raw.githubusercontent.com/major/MySQLTuner-perl/master/mysqltuner.pl | perl); - 定期
OPTIMIZE TABLE(对小表); - 避免
SELECT *、大结果集分页(用游标分页替代OFFSET); - 表引擎统一用
InnoDB(MyISAM 不支持事务且内存管理差)。
✅ 三、系统级协同优化
| 项目 | 推荐配置 | 说明 |
|---|---|---|
| Linux swappiness | echo 'vm.swappiness=1' >> /etc/sysctl.conf && sysctl -p |
防止内核过度交换,避免 Java GC 卡顿 |
| 系统预留内存 | 确保 free -h 显示 ≥300MB 可用内存 |
Tomcat(512M)+MySQL(600M)+OS(300M)+Buffer≈1.4G,留 600MB 余量 |
| 日志轮转 | logrotate 配置 Tomcat/MySQL 日志(每日+压缩) |
防止 /var/log 填满磁盘(间接导致 OOM) |
| 启动顺序 | 先启 MySQL → 等 10s → 再启 Tomcat | 避免 Tomcat 启动时连不上 DB 导致重试风暴 |
✅ 四、应急与监控(救命措施)
-
OOM 发生时快速定位
# 查看谁占内存最多 ps aux --sort=-%mem | head -10 # 检查 Tomcat GC 日志(启用 -Xlog:gc*:file=/var/log/tomcat/gc.log) # 检查 MySQL 连接数 mysql -e "SHOW STATUS LIKE 'Threads_connected';" -
自动保护脚本(crontab 每5分钟执行)
# /usr/local/bin/oom-guard.sh #!/bin/bash MEM_USAGE=$(free | awk 'NR==2{printf "%.0f", $3*100/$2}') if [ "$MEM_USAGE" -gt 90 ]; then echo "$(date) High memory: ${MEM_USAGE}%" >> /var/log/oom-guard.log systemctl restart mysql # 或先 kill 高内存进程 sleep 10 systemctl restart tomcat fi⚠️ 生产慎用
restart,建议先kill -9 $(pgrep -f "java.*tomcat")再重启。 -
必须开启的监控项
free -h(内存)jstat -gc <tomcat_pid>(Eden/Survivor/Old 区使用率)mysqladmin extended-status | grep -E "Threads_connected|Created_tmp_disk_tables"
🚫 绝对禁止的操作(2G机器雷区)
- ❌
Xmx1024m(堆设太大,MySQL 无内存可吃); - ❌
innodb_buffer_pool_size=1G(直接挤爆系统); - ❌ 开启
slow_query_log+general_log(I/O 和内存双杀); - ❌ 在 Tomcat 中部署多个 WAR(每个应用至少 200MB+ 堆需求);
- ❌ 使用 Hibernate 二级缓存(
ehcache/redis)且未设 size limit。
✅ 最终内存分配参考(2GB 总内存)
| 组件 | 分配内存 | 说明 |
|---|---|---|
| Linux OS | ~300MB | 内核、sshd、cron 等基础 |
| Tomcat JVM | 512MB | 堆+元空间+线程栈 |
| MySQL | ~600MB | Buffer Pool + 连接缓冲 |
| 余量 | ~600MB | 应对峰值、Page Cache、临时对象 |
✅ 实测:此配置可稳定支撑 20~30 并发用户(简单 CRUD 场景),日均请求 1w+。
如果提供你的具体场景(如:Spring Boot 版本、MySQL 版本、是否用连接池、QPS 估算),我可进一步定制参数。小内存不是不能用,而是必须像嵌入式开发一样“斤斤计较”每一MB。
需要我帮你生成完整的 setenv.sh / my.cnf 文件模板,或写一个一键检测脚本吗?
云服务器