想讓 512 MB 或 1 GB 記憶體的 Linux VPS 跑得順暢,請先測量記憶體到底被誰佔用,再在小 swap 檔案之上加一層記憶體壓縮交換(zram),限制 systemd 日誌的大小,並關閉伺服器用不到的服務。本文在 Debian 12/13 與 Ubuntu 22.04/24.04 上逐步演示,標註 Rocky Linux 與 AlmaLinux 的差異,並說明記憶體仍然耗盡時如何解讀 OOM killer 的日誌。
步驟 1:先測量,再動手
先找出真正佔用記憶體的程序,才能對症下藥。
free -h
ps -eo pid,user,rss,comm --sort=-rss | head -n 15 # RSS in KB, biggest first
systemd-cgtop -m -n 1 # memory per service
cat /proc/pressure/memory # memory pressure (PSI)
ps 列出佔用最大的程序,systemd-cgtop 則按服務彙總記憶體,當一個服務有很多工作程序時更直觀。/proc/pressure/memory 中的 avg60 如果長期高於 10 左右,說明程序經常在等待記憶體。(Rocky/AlmaLinux 預設關閉 PSI,需要以 psi=1 核心引數啟動才能使用。)
RSS 會把共享庫完整地算進每個程序,所以加總後看起來比實際更多。smem 顯示的 PSS 會把共享記憶體合理地分攤到各個程序:
apt install smem # Rocky / AlmaLinux: from EPEL
smem -tk -s pss -r | head -n 15
步驟 2:正確解讀 free -h:看 available,而不是 free
total used free shared buff/cache available
Mem: 961Mi 402Mi 74Mi 5.0Mi 484Mi 410Mi
Swap: 1.0Gi 12Mi 1.0Gi
free 數值很小是正常現象。Linux 會把空閒記憶體用作頁面快取(buff/cache)來快取檔案,程式需要時會立即釋放。真正要看的是 available:估算新任務在不使用 swap 的情況下還能拿到多少記憶體。上例中“空閒”只有 74 MB,但可用記憶體有 410 MB,伺服器完全正常。只有當 available 長期低於記憶體的 10% 左右、swap 用量又持續上升時,才需要擔心。
步驟 3:啟用 zram(記憶體壓縮交換)
zram 會在記憶體中建立一個交換裝置,以壓縮形式存放記憶體頁,體積通常只有原來的 1/2 到 1/3。交換到 zram 比交換到硬碟快得多,是給小套餐“擴容”非常有效的方法,代價是壓縮需要少量 CPU。
確認核心支援 zram
modprobe zram && echo "zram is available"
swapon --show
如果 modprobe 提示 Module zram not found,說明核心編譯時沒有包含 zram(部分精簡版雲核心如此)。請直接跳到步驟 4,只使用 swap 檔案。
方式 A:zram-tools(Debian、Ubuntu 22.04 與 24.04)
apt install zram-tools
cp /etc/default/zramswap /etc/default/zramswap.bak
cat > /etc/default/zramswap <<'EOF'
ALGO=zstd
PERCENT=50
PRIORITY=100
EOF
systemctl restart zramswap
zramctl
swapon --show
PERCENT=50 把 zram 裝置大小設為記憶體的一半(指未壓縮的容量),實際佔用的記憶體是壓縮後的大小。PRIORITY=100 讓核心優先使用 zram,再使用硬碟上的 swap。
方式 B:systemd zram-generator(Debian 12/13、Ubuntu 24.04、Rocky/AlmaLinux 9)
apt install systemd-zram-generator # Rocky / AlmaLinux 9: dnf install zram-generator
cat > /etc/systemd/zram-generator.conf <<'EOF'
[zram0]
zram-size = min(ram / 2, 2048)
compression-algorithm = zstd
swap-priority = 100
EOF
systemctl daemon-reload
systemctl start [email protected]
zramctl
在 Rocky/AlmaLinux 上,如果 dnf 找不到該軟體包,請先啟用 EPEL(dnf install epel-release)。
zstd,請改用 lz4,它在負載較高的單核套餐上也更快。步驟 4:保留小 swap 檔案作後備,並調整 swappiness
zram 位於記憶體中,所以硬碟上的小 swap 檔案仍有價值,可作為最後的後備:這幾檔套餐用 512 MB–1 GB 就夠了。建立方法、寫入 fstab 和調整大小,請按Linux VPS 新增 Swap 交換空間教程逐步操作。swap 檔案的預設優先順序低於 zram 裝置,只有 zram 用滿後才會用到它。
啟用 zram 後,請調高 vm.swappiness:把記憶體頁壓縮換進 zram,比丟棄檔案快取再從硬碟重新讀取更划算。vm.page-cluster = 0 讓核心不再一次預讀多個 swap 頁,這種預讀只對硬碟有幫助。
cat > /etc/sysctl.d/99-zram.conf <<'EOF'
vm.swappiness = 100
vm.page-cluster = 0
EOF
sysctl --system
sysctl vm.swappiness vm.vfs_cache_pressure
/etc/sysctl.d/ 下的檔案按字母順序讀取,因此 99-zram.conf 會覆蓋 swap 教程中 99-swap.conf 設定的 vm.swappiness=10。沒有 zram 時請保持 10。該教程中的 vm.vfs_cache_pressure=50 可以保留;但千萬不要設為 0,否則核心將永遠不回收檔案系統後設資料。
步驟 5:限制 journald 日誌
systemd 日誌存放在硬碟的 /var/log/journal;如果日誌不是持久化儲存,則存放在 /run/log/journal。/run 是 tmpfs,也就是說這份日誌佔用的是記憶體。請同時限制兩者:
journalctl --disk-usage
mkdir -p /etc/systemd/journald.conf.d
cat > /etc/systemd/journald.conf.d/size.conf <<'EOF'
[Journal]
SystemMaxUse=100M
RuntimeMaxUse=20M
MaxRetentionSec=1month
EOF
systemctl restart systemd-journald
journalctl --vacuum-size=100M
journalctl --disk-usage
SystemMaxUse 限制硬碟上的日誌,RuntimeMaxUse 限制記憶體中的日誌,MaxRetentionSec 會刪除超過一個月的記錄。Ubuntu 上的 rsyslog 還會把同樣的訊息寫入 /var/log/syslog;該檔案由 logrotate 輪轉,一般保留即可。
步驟 6:關閉伺服器用不到的服務
停用任何服務之前,先看清楚正在執行什麼,並且只關閉你能確認用途的服務:
systemctl list-units --type=service --state=running
systemctl status multipathd --no-pager # what is it and what started it?
systemctl list-dependencies --reverse multipathd.service # does anything need it?
| 服務 | 常見於 | 可以關閉的情況 |
|---|---|---|
snapd | Ubuntu | snap list 中沒有你在用的 snap(見下文) |
multipathd | Ubuntu Server | multipath -ll 沒有輸出,VPS 上通常如此 |
ModemManager | 部分 Ubuntu 映象 | VPS 上一律可以關閉:根本沒有調變解調器 |
tuned | Rocky / AlmaLinux | 可選,能省下少量記憶體;拿不準就保留 |
# Ubuntu: multipath storage daemon (not needed without SAN/multipath disks)
multipath -ll # no output = not in use
systemctl disable --now multipathd.service multipathd.socket
# Modem manager - a VPS has no modem
systemctl disable --now ModemManager
# Undo at any time:
systemctl enable --now multipathd.service multipathd.socket
snapd 會佔用幾十 MB 記憶體,併為每個 snap 掛載一個 loop 裝置。解除安裝它會同時刪除所有已安裝的 snap 及其資料,所以請先檢查 snap list。例如 certbot 是用 snap 安裝的,請先改用 apt 安裝 certbot,再解除安裝 snapd。pin 檔案可以防止 apt 以後又把 snapd 當作依賴裝回來。
snap list # what is installed as a snap?
apt purge snapd
cat > /etc/apt/preferences.d/no-snapd.pref <<'EOF'
Package: snapd
Pin: release a=*
Pin-Priority: -10
EOF
ssh/sshd、systemd 自身的服務(systemd-journald、systemd-logind、systemd-networkd、systemd-resolved)、networking 或 NetworkManager、cron、時間同步(chrony 或 systemd-timesyncd)以及防火牆(ufw、firewalld、nftables)。如果裝有 qemu-guest-agent 和 cloud-init,也請保留:平臺可能依賴它們,而且伺服器啟動後它們幾乎不佔記憶體。請用 disable 而不是 mask,方便日後重新啟用。步驟 7:把自動更新安排在低峰時段
不要關閉 unattended-upgrades:安全更新比幾 MB 記憶體重要得多。記憶體高峰來自 apt 執行本身,所以把它安排到空閒時段即可。預設情況下,apt 會在 12 小時視窗內的隨機時間下載更新,並在約 06:00–07:00 之間安裝;以下 drop-in 配置把下載改到伺服器時間 04:00、安裝改到 04:30(可用 timedatectl 檢視伺服器時區):
mkdir -p /etc/systemd/system/apt-daily.timer.d /etc/systemd/system/apt-daily-upgrade.timer.d
cat > /etc/systemd/system/apt-daily.timer.d/override.conf <<'EOF'
[Timer]
OnCalendar=
OnCalendar=*-*-* 04:00
RandomizedDelaySec=15m
EOF
cat > /etc/systemd/system/apt-daily-upgrade.timer.d/override.conf <<'EOF'
[Timer]
OnCalendar=
OnCalendar=*-*-* 04:30
RandomizedDelaySec=15m
EOF
systemctl daemon-reload
systemctl list-timers 'apt-*'
Rocky/AlmaLinux 如果使用 dnf-automatic,請用同樣的方法修改 dnf-automatic.timer。
步驟 8:選擇更輕量的軟體
- 保留 OpenSSH。它空閒時只佔幾 MB。Dropbear 等替代品省不了多少記憶體,缺少部分安全功能,發行版的更新也更少,不值得冒這個險。
- 優先使用 nginx,而不是 Apache + mod_php。在 Apache 的 prefork 模式下,每個子程序都帶著完整的 PHP 直譯器。nginx + PHP-FPM,或至少 Apache 的
mpm_event+ PHP-FPM,記憶體佔用要少得多。 - PHP-FPM 使用
pm = ondemand。有請求時才啟動 worker,空閒一段時間後自動退出,而不是一直駐留在記憶體裡。 - 按套餐調整資料庫。MySQL 與 MariaDB 的預設值是按更大記憶體設計的:請調小緩衝池和
max_connections,並關閉performance_schema。
; /etc/php/8.2/fpm/pool.d/www.conf (Rocky / AlmaLinux: /etc/php-fpm.d/www.conf)
pm = ondemand
pm.max_children = 5
pm.process_idle_timeout = 10s
pm.max_requests = 500
路徑中的 PHP 版本因系統而異:Debian 12 為 8.2,Debian 13 為 8.4,Ubuntu 22.04 為 8.1,Ubuntu 24.04 為 8.3。pm.max_children 的取值是能分給 PHP 的記憶體除以單個 worker 的平均大小,例如 200 MB / 40 MB = 5。先測量大小,再檢查配置並重新載入:
# Average size of a PHP-FPM process
ps -eo rss,comm | awk '/php-fpm/ {s+=$1; n++} END {if (n) printf "%d processes, average %.0f MB\n", n, s/n/1024}'
php-fpm8.2 -t && systemctl reload php8.2-fpm # Rocky / AlmaLinux: php-fpm -t && systemctl reload php-fpm
步驟 9:瞭解 OOM killer 並找出 OOM 事件
記憶體和 swap 都用盡時,核心的 OOM killer 會終止一個程序來保住整個系統,通常是 oom_score 最高、一般也就是佔用最大的那個。服務“莫名其妙”地重啟、資料庫突然消失,往往都是被 OOM 殺掉了。請在核心日誌中搜尋:
journalctl -k | grep -i -E 'out of memory|oom-kill|killed process'
dmesg -T | grep -i -E 'out of memory|killed process'
journalctl -k -b -1 | grep -i 'killed process' # previous boot (persistent journal only)
典型的日誌如下:
Out of memory: Killed process 812 (mariadbd) total-vm:1325400kB, anon-rss:301220kB, file-rss:0kB, shmem-rss:0kB, UID:106
其中寫明瞭程序名稱和它當時佔用的記憶體(anon-rss,單位 kB)。如果同一個服務反覆被殺,請調小它的配置、新增 swap 或 zram,或按步驟 10 為它設定上限。在 Ubuntu 上還請檢查 systemctl status systemd-oomd:該服務處於執行狀態時,可能會在核心出手之前,因記憶體壓力直接停止整個服務。
步驟 10:用 systemd MemoryMax 限制吃記憶體的服務
給單個服務設定記憶體上限,可以保護伺服器上的其他服務:如果 PHP-FPM 發生記憶體洩漏,被限制的只有 PHP-FPM,SSH 和資料庫照常執行。MemoryHigh 是軟限制,超過後核心會讓該服務變慢並回收其記憶體;MemoryMax 是硬限制,達到後 OOM killer 只在該服務內部動手。
stat -fc %T /sys/fs/cgroup # cgroup2fs = cgroup v2 (required)
mkdir -p /etc/systemd/system/php8.2-fpm.service.d
cat > /etc/systemd/system/php8.2-fpm.service.d/memory.conf <<'EOF'
[Service]
MemoryHigh=250M
MemoryMax=300M
# A killed worker is replaced by the master instead of stopping the service
OOMPolicy=continue
Restart=on-failure
EOF
systemctl daemon-reload
systemctl restart php8.2-fpm
systemctl show php8.2-fpm -p MemoryHigh -p MemoryMax -p MemoryCurrent
Debian 12/13、Ubuntu 22.04/24.04 與 Rocky/AlmaLinux 9 預設使用 cgroup v2。在 Rocky/AlmaLinux 8 等使用 cgroup v1 的舊系統上,請改用 MemoryLimit=;MemoryHigh 只在 v2 下有效。不要給資料庫本身設定硬上限:突然被殺會導致崩潰恢復,應該調整它自身的引數。
步驟 11:判斷何時該升級套餐
最佳化適用於偶發的記憶體高峰。如果 available 一直很低、swap 始終處於繁忙狀態,vmstat 1 10 顯示持續的換入換出(si/so),說明業務本身就需要更多記憶體。JUSTG 雲伺服器可以在服務頁面升級套餐,詳見VPS 升級記憶體、SSD 硬碟或 IP 教程。
常見問題
zram 比 swap 檔案更好嗎?
速度上是的:它以記憶體速度工作,不產生硬碟 I/O。但它受記憶體大小限制,所以小套餐的最佳方案是兩者並用:高優先順序的 zram 負責日常交換,小 swap 檔案作為最後的後備。
zram 會佔用很多 CPU 嗎?
壓縮需要一些 CPU,通常很少。如果單核套餐上的伺服器一直在往 zram 交換,請改用 lz4 並減輕負載,因為持續交換說明記憶體確實不夠。
512 MB 的 mini 套餐能跑 Docker 嗎?
跑一兩個小應用的容器可以,但 Docker 守護程序和容器執行時本身也要佔用記憶體。請為每個容器設定記憶體上限,同時啟用 zram 和 swap;容器較多時,建議選擇 1 GB 及以上的套餐。
如果服務仍不斷被殺、又找不到原因,請至 https://www.justg.com/submitticket.php 提交工單,並附上伺服器 IP 以及 free -h 和 journalctl -k | grep -i oom 的輸出。JUSTG 提供 7×24 小時 AI 客服(工單),需要人工處理的問題會由工程師跟進。