背景

我有一台 OpenCloudOS 服务器,3.5G 内存。跑 Java 服务的时候,CPU 动不动飙到 100%,内存吃完就开始 OOM Kill。每次都要 SSH 上去手动重启,很烦。

整理了一套低成本方案,现在稳定多了。

问题诊断

先搞清楚谁在吃资源:

1
2
3
4
5
6
7
8
9
10
11
# 实时进程监控
top -o %MEM

# 查看 Java 进程详情
ps aux | grep java

# 查看 OOM 记录
dmesg | grep -i "out of memory"

# 系统内存使用分布
free -h

典型场景:Java 堆内存设太大 + 没有 GC 调优 + 其他服务也在挤占内存。

解决方案

1. JVM 参数调优

1
2
3
4
5
6
7
# 修改启动脚本,限制堆内存
java -Xms256m -Xmx512m \
-XX:MaxMetaspaceSize=128m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-jar app.jar
参数 说明 建议值
-Xms256m 初始堆内存 256m,避免频繁扩容
-Xmx512m 最大堆内存 服务器内存的 1/3 ~ 1/2
-XX:MaxMetaspaceSize 元空间上限 128m,防无限膨胀
-XX:+UseG1GC G1 垃圾回收器 小内存首选,延迟可控
-XX:MaxGCPauseMillis GC 最大停顿 200ms,够用了

核心原则:堆内存不要超过物理内存的 50%。留空间给操作系统、文件缓存和其他进程。

2. 自动重启守护

服务器上的 Java 进程崩溃是常态,写个 systemd 服务自己做守护:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# /etc/systemd/system/myapp.service
[Unit]
Description=My Java Application
After=network.target

[Service]
Type=simple
User=webwlx
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/java -Xms256m -Xmx512m -jar /opt/myapp/app.jar
Restart=always
RestartSec=10
StandardOutput=append:/var/log/myapp.log
StandardError=append:/var/log/myapp-error.log

# 内存限制(兜底)
MemoryMax=768M

[Install]
WantedBy=multi-user.target
1
2
systemctl daemon-reload
systemctl enable --now myapp

MemoryMax=768M 是硬限制,超了系统直接杀进程,比 OOM Killer 优雅。

3. 内存监控 + 告警

写个简单的监控脚本,内存超阈值自动记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/bash
# /opt/scripts/mem-check.sh

THRESHOLD=85
USAGE=$(free | grep Mem | awk '{printf("%.0f", $3/$2 * 100)}')

if [ "$USAGE" -gt "$THRESHOLD" ]; then
echo "[$(date)] 内存使用率 ${USAGE}%,超过阈值 ${THRESHOLD}%" \
>> /var/log/mem-alert.log

# 列出 top 5 内存大户
ps aux --sort=-%mem | head -6 \
>> /var/log/mem-alert.log
fi

crontab 每 5 分钟跑一次:

1
*/5 * * * * /opt/scripts/mem-check.sh

4. 服务分级,按需启停

3.5G 内存开不了太多服务。给服务分级:

等级 服务 策略
必须常驻 Nginx、SSH 始终运行
核心业务 Java 主服务 始终运行 + 守护
辅助服务 日志收集、定时任务 按需启动
低频服务 测试环境、管理后台 不用时停掉
1
2
3
4
# 快速启停辅助服务
alias app-start='systemctl start myapp'
alias app-stop='systemctl stop myapp'
alias app-status='systemctl status myapp'

5. Nginx 减压

静态资源、反向代理都走 Nginx,别让 Java 干这些活:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server {
listen 80;
server_name example.com;

# 静态资源直接返回
location /static/ {
alias /var/www/static/;
expires 30d;
gzip_static on;
}

# API 转发到 Java 服务
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
}
}

6. Swap 兜底

3.5G 内存实在不够用的时候,Swap 能救命(虽然慢):

1
2
3
4
5
6
7
8
9
10
11
# 创建 1G Swap 文件
dd if=/dev/zero of=/swapfile bs=1M count=1024
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

# 持久化
echo '/swapfile none swap sw 0 0' >> /etc/fstab

# 降低 swap 倾向(尽量用物理内存)
echo 'vm.swappiness=10' >> /etc/sysctl.conf

效果

优化后:

  • Java 进程内存稳定在 400-500MB
  • CPU 不再长时间 100%
  • 崩溃自动重启,不需要手动干预
  • 系统整体内存使用率从 95% 降到 70% 左右

什么时候该加钱

这套方案能让你在 3.5G 的内存上多撑一段时间,但有上限。如果:

  • 用户量上来了,并发请求数持续增长
  • 数据库也跑在同一台机器上
  • Swap 使用频繁(iostat 看到大量 IO)

该加钱就加钱。技术优化有天花板,业务增长没有。