GCP帳號充值服務 GCP內存溢出OOM崩潰排查與解決:如何增加 Swap 虛擬記憶體防死機
第一章:為什麼 GCP 的 OOM 不是你想的那麼單純
你以為 OOM 就是「記憶體不夠」,但在 GCP 上,真正導致服務崩潰的原因常常是更細的邊界:容器的 cgroup 限制、虛擬記憶體策略、應用程式的記憶體分配方式、以及是否啟用了 Swap。尤其在 Compute Engine、GKE(Kubernetes)或其他以容器為核心的服務上,OOM Killer 的觸發條件不只取決於整機記憶體,還取決於「你被允許使用多少」。
當應用因為 OOM 被系統終止,表面看起來像是「瞬間崩潰」,但其實通常是某個時間窗內記憶體持續上升、達到限制後被直接殺死。這種情況如果不處理根因,只靠重啟或擴容會反覆發生,且成本越滾越高。
本文會用一個務實的思路:先把崩潰的觸發點對齊到「是哪個層級在限制記憶體」,再用 Swap 作為緊急緩衝,讓服務在記憶體壓力下更有機會自救或至少避免立刻死掉,同時也提出長期修復路線。
第二章:先判斷你看到的是哪種 OOM
排查 OOM 最怕的不是找不到資料,而是把不同類型混在一起。建議你先做三件事:確認事件時間、確認是進程被殺、確認是節點還是容器層級。
2.1 觀察系統層與應用層的關聯時間
通常你會有:負載突然上升、延遲變大、或某次任務批量處理後記憶體暴增。把這些時間點記下來,對照 GCP 的監控事件。只要能把「記憶體曲線的拐點」跟「OOM killer 的報文」對上,你的排查就會快很多。
GCP帳號充值服務 2.2 檢查內核日誌:OOM Killer 的關鍵句
在 Linux VM 上,最直接的是看內核訊息,例如 /var/log/kern.log 或 dmesg 相關輸出。你要找類似「Out of memory」或「Killed process」的段落。關鍵在於被殺的是哪個 PID、屬於哪個程式,還有當時整體可用記憶體狀態。
如果你是在容器環境,日誌可能會顯示某個容器內的主程式被殺。這時你要注意:容器被殺 ≠ 節點整體 OOM。容器可能先達到自己的 cgroup 記憶體上限。
2.3 在 GKE 裡看事件:容器層級的 OOM
如果是 GKE,你通常會看到 Pod 反覆重啟,並在描述或事件中看到類似 OOMKilled。這代表容器達到設定的 memory limit,被 kubelet 觸發殺死。此時「增加節點 Swap」不一定能直接阻止容器被 OOM,但它可能會緩解宿主機層的壓力,並讓記憶體回收更有空間。
在 GKE 上,要同時檢查:Pod 的 resources.limits.memory、requests.memory、以及是否開啟了 Swap(不同方案/版本支援狀況不同)。如果容器被設得太緊,你增加 Swap 也可能只是延遲死亡,而不是消除限制。
第三章:排查路線圖:從指標到根因
排查 OOM 的順序建議是「先定量、後定性、最後定責」。你要先用數據確認記憶體到底是 緩慢長期增長 還是 瞬時爆發,再判斷是洩漏、快取不受控、還是單次任務處理太重。
3.1 記憶體曲線:緩慢上升 vs 突然尖峰
如果記憶體曲線是緩慢上升,通常偏向洩漏或累積性資料結構沒有釋放。若是突然尖峰,則可能是:批量任務、某次 query 回傳太大、反序列化過程佔用了大量臨時記憶體、或者壓縮/解壓/轉換流程在短時間內吃爆。
你可以把尖峰對應到應用的某些行為:例如每晚的匯入任務、每分鐘的報表生成、或某個新版本上線後的特定路徑。
GCP帳號充值服務 3.2 看可用記憶體與回收行為
GCP帳號充值服務 在 Linux 上,你要關心的不只是「總記憶體」,還包括:可用頁面、是否啟動了 swap、以及回收是否頻繁。你可以用 free -m、vmstat 或 /proc/meminfo 觀察 Swap 是否為 0、回收是否失控。
如果 Swap 為 0,代表你缺少緊急緩衝;當匿名頁或頁面緊張時,就更容易觸發 OOM Killer。這也是為什麼「增加 Swap」是很常見但常被忽略的防死機策略。
3.3 以 cgroup 為中心判斷容器限制
容器環境下,真正決定容器死亡的是 cgroup 記憶體。你要確認:容器是否有 memory limit、是否達到 limit 後立刻被 kill。你可以檢查 /sys/fs/cgroup 相關目錄(不同系統可能路徑略有差異)。
如果你發現容器的 limit 與實際 peak 不匹配,那根因就不是 Swap 的問題,而是 limits 設太小。這時你需要重新評估峰值需求,並調整 requests/limits,而不是只把希望放在 Swap。
第四章:解決策略一:增加 Swap 作為緩衝層
Swap 的角色不是讓你無限擴充記憶體,而是把「立刻死」變成「有時間慢慢活」。它可以吸收尖峰負載造成的瞬時記憶體壓力,並在某些場景讓應用完成任務或完成回收。
在 GCP 上,你可用的做法取決於你的部署型態。以下以 Compute Engine 的 VM 為例說明;若你是 GKE 或容器,需搭配實際支援情況。
4.1 為什麼沒有 Swap 特別容易 OOM
Swap 是把一部分不常使用的記憶體頁移到磁碟。當 RAM 壓力大時,系統可以用 swap 把內存釋放出來。若 swap 為 0,系統只有「丟棄可回收頁」與「直接殺進程」兩條路,當不可回收的匿名頁過多時,就很容易直接觸發 OOM。
4.2 建議的 Swap 容量與選擇原則
Swap 多不一定更好。過大的 Swap 可能造成磁碟 I/O 壓力,讓應用延遲飆升,甚至引發連鎖反應。較務實的做法是:以「避免 OOM killer」為目標,讓系統在少量時間內有空間完成任務。
常見做法是先設一個相對保守的值,例如 1GB、2GB 或 4GB,觀察記憶體尖峰是否被吸收。若你的工作負載有更長的記憶體緩衝需求,可以再逐步提高。具體容量最好基於過往 peak 與事件時間窗推估,而不是拍腦袋。
4.3 在 VM 上建立 Swap(概念與步驟)
建立 Swap 的基本流程通常是:準備一個 swap 檔案或 swap partition、設權限、格式化成 swap、啟用並設定持久化。
你可以考慮用 swap 檔案(swap file)因為操作彈性高。思路如下:選定檔案位置(例如根據你的磁碟配置選擇合適的路徑)、分配檔案大小、設權限為只有 root 可讀、用 mkswap 格式化、再用 swapon 啟用。最後要確保重開機後仍然存在,通常會在 /etc/fstab 設定。
此外還要調整 swappiness(例如 vm.swappiness),讓 swap 使用的積極度不至於過高。你希望它在「快沒 RAM 了」才比較願意動用,而不是平常就大規模換出。
4.4 若磁碟性能不足,Swap 可能變成新瓶頸
在雲端環境,磁碟 I/O 速度與延遲比本地 SSD 更變化。若你把 Swap 設得過大,系統可能會頻繁把頁面換入換出,導致整體吞吐下降。此時應用可能不是「不會 OOM」,而是「更慢、更抖」。
GCP帳號充值服務 因此 Swap 是防死機的保險,不是根治。你仍需要在應用層解決記憶體壓力的來源。
第五章:解決策略二:調整容器/程序的記憶體邊界
若你在容器環境,Swap 的效果常被容器限制削弱。更直接的做法是把「記憶體 limit」設對,並確保程式有合理的記憶體上限與釋放策略。
5.1 檢查 limits:是不是硬碰硬地設太低
你需要對照最近一段時間的 peak RSS(常駐集合大小)或容器記憶體用量。若 peak 已經幾乎貼著 limit,GC 或釋放延遲就會讓你在某些時段被 kill。
建議預留一定安全餘量,例如峰值的 20%~40%(具體看抖動程度)。但不要把 limit 設得太離譜,否則節點可能承擔過大壓力,導致其他 Pod 連鎖 OOM。
5.2 針對語言運行時:讓它知道自己有上限
很多程式在沒有明確上限時,會把記憶體用到你不希望它用的程度。例如 JVM 的最大堆、Go 的 GOGC、Node.js 的堆限制等。這些都應與容器的 limit 對齊。
如果你不做對齊,常見現象是:應用看似在工作,實際上把堆用到很高,GC 延遲上升,最後仍然撞上 cgroup limit 被殺死。你要做的是:讓運行時在更早的時間開始回收,避免在極限附近才被動回收。
5.3 限制快取與批處理:把峰值砍下來
OOM 很多時候不是「平均記憶體」問題,而是「峰值任務」問題。你可以從三個地方下手:快取策略、批量大小、以及中間資料的生成方式。
例如把一次性讀入改為流式處理;把批大小從 10 萬降到 1 萬;對不必要的緩存設 TTL;避免把超大資料結構一次性載入記憶體。
這些修改通常比「只加內存」更有效,因為它直接降低峰值,不會造成持續成本上升。
第六章:解決策略三:把根因修到位(不然 Swap 只是延命)
Swap 能救急,但真正能讓你不再反覆爆炸的是根因修復。下面列出常見根因與應對方向。
6.1 記憶體洩漏:長時間運行後越來越大
GCP帳號充值服務 如果你看到記憶體曲線一路上升,伴隨 GC 無法跟上或快取持續增長,可能是洩漏或引用沒有釋放。你需要做:壓測重現、記憶體快照/heap dump、以及追蹤大物件來源。
在排查階段,先把「哪個類型/哪個模組」吃最多找出來,再談修復。因為洩漏的修法非常依賴語言與框架。
6.2 緩存不受控:資料量增長導致快取膨脹
很多服務在早期資料量小時不會出事,但當資料累積後,快取突然變成記憶體黑洞。你要確認快取是否有上限(size cap)、是否有 TTL、是否有 eviction 策略。
如果沒有,就補上。若快取需要較大的容量,也要確保它建立在「可預期」的資料量之上,而不是不設限。
6.3 臨時資料過大:一次性聚合或序列化
OOM 常常發生在「看似不該很大」的地方,例如生成報表、合併結果集、或把查詢結果一次性轉成大物件。這些操作在短時間內建立大量臨時資料,導致峰值超過 limit。
解法是分段處理、流式轉換,或把聚合改成在資料庫端做得更節省。還有一個容易忽略的點是:序列化/反序列化可能同時佔用多份中間表示形式,你要把它們的峰值算進去。
6.4 外部依賴延遲造成堆積:不是快取,是任務堆在那裡
當外部 API、資料庫或訊息隊列延遲變高,任務會在應用端排隊。排隊本身就是記憶體消耗。你要確認:在崩潰前是否出現下游延遲上升或重試風暴。
此時要做的不是立刻加記憶體,而是調整併發、超時、重試退避、以及背壓(backpressure)策略。
第七章:一個可落地的排查流程(你照著做就能收斂)
GCP帳號充值服務 把前面的內容收斂成一個流程,讓你在第一次遇到 OOM 時不會亂猜。
7.1 收集資料(先把證據集起來)
列出事件時間、被殺的進程/容器、節點與 Pod 名稱。同步抓取:記憶體用量曲線、CPU 曲線、Swap 狀態、以及相關日誌。
如果你能找到 OOM Killer 的訊息,把它的時間與節點記憶體狀態寫在同一張時間軸上。
7.2 判斷屬性:節點 OOM 還是容器 OOM
如果是節點 OOM,Swap 設定與整機記憶體策略就更重要。若是容器 OOM,你要優先檢查 memory limit 與運行時記憶體上限對齊。
這一步是分流的核心:不同分流的策略完全不一樣。
7.3 確認壓力型態:洩漏、膨脹快取、或尖峰任務
看曲線:上升是否持續,還是短時間急劇上升。把峰值對應到應用行為,鎖定候選模組。
7.4 用 Swap 與限流/調參做「止血」
在你還沒找到根因前,用 Swap 與運行時/併發/批次等手段把峰值壓下來。這樣至少能避免服務在排查過程中反覆宕機,讓你有時間做深挖。
止血的優先順序可以是:先調小峰值(限流、批次、並發),再調運行時上限(GC/heap),最後才是 Swap 作緩衝。
7.5 找根因並驗證:確保修完後曲線不再回去
修完後要重新跑類似壓測或用同樣的流量回放。你要看兩件事:最大峰值是否下降、以及記憶體是否不再持續上升。
只有曲線穩了,你才能真正說「解決」而不是「暫時撐住」。
第八章:如何避免「加 Swap 但仍然 OOM」的常見誤區
很多人加了 Swap 之後依然看到 OOM,常見原因如下。
8.1 Swap 太小或被快速耗盡
如果你的尖峰遠大於 Swap 容量,Swap 只能多撐一點點時間。你需要從過往峰值估算要撐多長,而不是一開始就設太小。
8.2 容器 limit 先把你殺了
容器達到 memory limit,可能立刻被 kill;Swap 的緩衝並不能越過 cgroup 的硬限制。此時要優先調整 memory limit 或減少容器內的峰值。
8.3 你把 swappiness 調得太激進
如果讓系統太積極換出頁面,可能導致頻繁 I/O,使應用延遲上升,吞吐下降,進而在上層形成堆積,反而更容易觸發資源耗盡。Swap 是防死機,不是追求高換出率。
8.4 你只看「有沒有 Swap」,沒看「實際 swap usage」
你要看事件發生時 Swap 是否真的被使用,以及使用量是否持續上升。如果 Swap 幾乎不動,代表 OOM 可能不是單純 RAM 緊張,可能是不可回收頁、運行時配置、或應用的結構性問題。
第九章:把策略落在日常運維:監控與預警
解決 OOM 後,真正重要的是避免再次發生。你需要把「記憶體風險」提前變成可觀測的訊號。
9.1 設計預警:靠近 limit 的行為要提早告警
在 VM 上可以用可用記憶體、交換使用量、以及回收行為作訊號。在容器上則用容器記憶體用量相對於 limit 的比例。你要避免只在 OOM 已經發生後才知道。
9.2 記錄每次 OOM 的五個欄位
建議你的 runbook 直接要求填寫:時間、節點/Pod、峰值記憶體曲線、Swap 使用量、以及最後一次成功處理的請求/批次類型。資料越結構化,你越能快速定位模式。
9.3 用演練把「加 Swap」變成流程,而不是臨時救火
Swap 調整最好有標準化的做法:容量範圍、swappiness 的合理區間、啟用後的觀察指標、以及回滾方案。當你在凌晨收到告警時,你不應該還在思考 Swap 的參數該怎麼設。
第十章:結論——以 Swap 防死機,但以根因終結循環
GCP 上的 OOM 崩潰,常常不是一句「記憶體不夠」就能交代。真正要做的是:把觸發點對齊到節點或容器層級、確認是洩漏/快取膨脹/尖峰任務/下游延遲堆積中的哪一類,然後用止血策略讓服務活下來。
增加 Swap 是很有效的防死機措施:它能讓系統在極端壓力下多一點時間,讓回收或任務完成有機會發生。但它永遠不是根治。當你能把 limits 設對、把運行時上限對齊、把峰值任務拆分或限流,並針對洩漏與快取做修復,記憶體曲線才會真正回到可預期的範圍。
如果你打算從今天開始改進,我建議你把下一次 OOM 當作一次「資料收斂」任務:先收集證據,再分流定位,再止血(包含 Swap),最後用修復驗證。這樣你會發現 OOM 不再是恐懼,而是一個可以被系統化處理的問題。

