Linuxメモリ不足時の動作を徹底解説|kswapd・Direct Reclaim・OOM Killerの仕組み

はじめに

Linuxサーバを運用していると、「Swapの使用量が急に増えた」「メモリはまだ残っているのにパフォーマンスが低下した」「OOM Killerによってプロセスが強制終了された」といった事象に遭遇することがあります。

このような問題が発生すると、「メモリが足りない」という結果だけに目が向きがちですが、実際にはLinuxカーネルはメモリ不足を解消するために、さまざまな処理を段階的に実行しています。

例えば、空きメモリが減少すると、まずkswapdがバックグラウンドで不要なページの回収を開始します。それでも空きメモリを確保できない場合は、メモリを要求したプロセス自身がページ回収を行うDirect Reclaimが実行されます。さらに状況が悪化し、メモリを確保できなくなると、最後の手段としてOOM Killerがプロセスを終了させ、システム全体が停止することを防ぎます。

これらの仕組みを理解しておくことで、メモリ不足時のログや性能情報を正しく読み解けるようになり、障害発生時の原因調査や切り分けをスムーズに進められるようになります。

本記事では、Linuxがメモリ不足をどのように検知し、どのような順序でメモリを回収し、それでも不足した場合にどのような処理を行うのかを時系列で解説します。kswapd、Direct Reclaim、Memory Compaction、OOM Killerの役割と関係を理解し、メモリ不足時の動作を体系的に学んでいきましょう。

メモリ不足時の全体の流れ

Linuxでは、空きメモリが少なくなったからといって、すぐにOOM Killerがプロセスを終了させるわけではありません。

まずは不要なメモリを回収し、それでも空きメモリを確保できない場合に次の手段へ進むというように、段階的にメモリ不足へ対応します。

全体の流れは次のとおりです。

まず、空きメモリが一定量を下回ると、Linuxカーネルはメモリ不足になりつつあると判断し、kswapdというカーネルスレッドを起動します。kswapdはバックグラウンドで不要なページを回収し、新たなメモリ要求に備えて空きページを確保します。

kswapdによるページ回収だけで必要な空きページを確保できれば、アプリケーションはページ回収を意識することなく処理を継続できます。

一方で、メモリ消費の増加が急激だったり、kswapdによるページ回収が追いつかなかったりすると、メモリを要求したプロセス自身がページ回収を行うDirect Reclaimが実行されます。Direct Reclaimでは、ページ回収が完了するまでメモリを要求した処理が待たされるため、アプリケーションのレスポンス低下や処理遅延の原因になることがあります。

また、高次ページの確保が必要で、メモリの断片化によって連続した空き領域を確保できない場合は、Memory Compactionが実行されます。Memory Compactionでは、物理メモリ上のページを再配置することで断片化を解消し、連続した空き領域の確保を試みます。

それでも必要なメモリを確保できない場合は、LinuxはOOM Killerを実行できる状況かどうかを判断します。通常のユーザプロセスによるメモリ要求であれば、OOM Killerがプロセスを強制終了してメモリを解放します。一方、割り込み処理や一部のカーネル処理など、OOM Killerを実行できない状況では、メモリの割り当て自体が失敗することがあります。

このように、LinuxはまずkswapdやDirect Reclaimによって空きページの確保を試みます。また、高次ページが必要な場合はMemory Compactionによってメモリの断片化を解消し、それでも必要なメモリを確保できない場合は、状況に応じてOOM Killerを実行する、またはメモリ割り当てを失敗させることでシステム全体の動作を継続しています。

Linuxはいつ「メモリ不足」と判断するのか

「メモリ不足」と聞くと、物理メモリの空き容量がゼロになった状態をイメージする方も多いかもしれません。しかし、Linuxは空きメモリが完全になくなるまで待ってから対処するわけではありません。

Linuxカーネルは、あらかじめ一定量の空きページを維持するよう設計されており、空きメモリがその基準を下回ると、メモリ不足になりつつあると判断してページ回収を開始します。

この判定に利用されるのが「Watermark」です。

Watermarkとは

Watermarkとは、各メモリゾーンで維持すべき空きページ数の目安です。

Linuxでは、物理メモリを用途ごとに複数のメモリゾーンへ分割して管理しています。例えば、一般的なx86_64環境では、DMA、DMA32、Normalといったメモリゾーンが存在します。

それぞれのメモリゾーンには、次の3つのWatermarkが設定されています。

Watermark役割
High十分な空きメモリがある状態
Low空きメモリが少なくなり始めた状態。kswapdが起動する基準
Min最低限維持すべき空きメモリ。これを下回ると深刻な状態

Watermarkのイメージは次のようになります。

普段のシステムでは、空きページ数はHigh Watermark付近で維持されます。

アプリケーションがメモリを大量に消費すると空きページ数は徐々に減少し、Low Watermarkを下回るとLinuxは「このままでは空きメモリが不足する可能性がある」と判断します。

つまり、Linuxは空きメモリがゼロになってから動き始めるのではなく、余裕があるうちからページ回収を開始する仕組みになっています。

kswapd/Direct Reclaimが起動する条件

各メモリゾーンの空きページ数がLow Watermarkを下回ると、Linuxはkswapdを起動します。kswapdは、バックグラウンドで動作するカーネルスレッドであり、空きページを増やすことが役割です。

具体的には、ページキャッシュや使用されていないメモリページを回収し、必要に応じてAnonymous pageをSwapへ退避させることで、新たなメモリ要求に備えて空きページを確保します。

この処理はバックグラウンドで実行されるため、多くの場合、アプリケーションはページ回収を意識することなく処理を継続できます。

kswapdは、空きページ数がHigh Watermark付近まで回復するとページ回収を終了し、待機状態へ戻ります。このように、kswapdは空きメモリを事前に確保することで、アプリケーションがメモリ不足に陥ることを防ぐ重要な役割を担っています。

ただし、メモリ消費の増加が急激だったり、ページ回収が追いつかなかったりすると、kswapdだけでは十分な空きページを確保できない場合があります。その場合にはDirect Reclaimが実行されます。

kswapdとDirect Reclaimの違い

項目kswapdDirect Reclaim
実行主体カーネルスレッドメモリを要求したプロセス
実行タイミングLow Watermarkを下回ったときメモリ要求時に空きページが不足したとき
実行方法バックグラウンドアメモリ要求元の処理内で同期的に実行
性能への影響比較的小さい大きい

通常は、kswapdがバックグラウンドで空きページを確保しているため、Direct Reclaimが発生することは多くありません。

しかし、メモリ使用量が急激に増加した場合や、ページ回収が追いつかない場合には、Direct Reclaimが発生し、アプリケーションの性能へ直接影響を与えます。

つまり、Direct Reclaimが頻繁に発生している状態は、「Linuxがメモリ不足への対応に苦戦している状態」と考えることができます。

kswapdとは

kswapdは、Linuxカーネルがバックグラウンドで実行するカーネルスレッドです。各メモリゾーンの空きページ数がLow Watermarkを下回ると起動し、ページ回収を行うことで空きページを確保します。

この処理はバックグラウンドで実行されるため、多くの場合、アプリケーションはページ回収を意識することなく処理を継続できます。

ここからは、kswapdがどのようなページを回収し、どのように空きページを確保しているのかを見ていきましょう。

kswapdの回収対象

kswapdは、すべてのメモリを無差別に解放するわけではありません。ページの種類や利用状況を考慮しながら、性能への影響をできるだけ抑えるようにページ回収を行います。

主な回収対象は、Inactive Page Cache、Anonymous Page、Dirty Pageの3つです。

Inactive Page Cache

ページキャッシュは、ファイルの内容をメモリ上に保持し、ディスクアクセスを高速化するための仕組みです。その中でも、長時間アクセスされていないInactive Page Cacheはページ回収の対象になります。

ページキャッシュはディスク上のデータを一時的に保持しているだけなので、回収された後でも必要になればディスクから再度読み込むことができます。そのため、比較的性能への影響が小さいページとして回収対象になります。

Anonymous Page

Anonymous Pageとは、プロセスが利用するHeapやStackなどのメモリです。Anonymous Pageはディスク上に元データが存在しないため、そのまま解放することはできません。

そのため、ページ回収が必要になった場合は、Swap領域へ書き出してからメモリを解放します。

Dirty PageとWriteback

ページキャッシュの中には、アプリケーションによって更新されたものの、まだストレージへ書き込まれていないページがあります。このようなページをDirty Pageと呼びます。

Dirty Pageは、そのまま解放するとデータが失われてしまうため、まずWritebackによってストレージへ書き込みます。書き込みが完了するとClean Pageとなり、ページ回収の対象になります。

Page Cacheから回収する理由

Linuxは、ページの種類や利用状況を考慮しながらページ回収を行います。

一般的に、ページキャッシュはディスク上に元データが存在するため、回収後に再び必要になってもファイルから読み込み直すことができます。一方、Anonymous Pageはプロセスが利用する実データであり、そのまま解放することはできません。回収する場合はSwap領域へ書き出し、再び利用する際にはSwapから読み戻す必要があります。

このように、ページキャッシュは比較的回収しやすいページである一方、Anonymous Pageの回収ではSwapへの書き出しや読み戻しが発生するため、一般的に性能への影響が大きくなります。

それぞれのページの回収方法をまとめると、次のようになります。

ページの種類回収方法
Inactive Page Cache不要になれば解放し、必要になった際はディスクから再読み込み
Dirty PageWriteback完了後に回収
Anonymous PageSwapへ退避してから回収

このようにLinuxは、ページの種類や状態に応じて適切な回収方法を選択し、システム全体の性能低下をできるだけ抑えながら空きページを確保しています。

kswapd発生状況の確認

kswapdのページ回収状況は、/proc/vmstatから確認できます。

以下のコマンドを実行します。

[root@almalinux ~]# grep -E "pgscan_kswapd|pgsteal_kswapd" /proc/vmstat
pgsteal_kswapd 1324465
pgscan_kswapd 1332366
[root@almalinux ~]#

各項目の意味は次のとおりです。

項目説明
pgscan_kswapdkswapdがスキャンしたページ数
pgsteal_kswapdkswapdが実際に回収したページ数

これらの値は累積カウンタであるため、単体の値を見るだけでは判断できません。

例えば、一定間隔で取得し、短時間で大きく増加している場合は、その時間帯にkswapdが活発にページ回収を行っていたことを示します。

kswapによるメモリ回収まとめ

ページキャッシュの仕組みについては、以下で詳しく説明しています。
Linuxページキャッシュを徹底解説|Page Cache・Dirty Page・Writebackの仕組み

また、Anonymous PageがSwapへ退避される仕組みについては、以下で詳しく解説しています。
Linux Swapを徹底解説|Swap Out・Swap In・swappinessの仕組み

Direct Reclaimとは

前章で説明したように、空きページ数がLow Watermarkを下回ると、Linuxはkswapdによるページ回収を開始します。

しかし、メモリ消費が急激に増加した場合や、ページ回収が追いつかない場合には、kswapdだけでは十分な空きページを確保できないことがあります。このような状況で実行されるのが、Direct Reclaimです。

Direct Reclaimは、メモリを要求したプロセス自身の処理コンテキストでページ回収を行い、必要な空きページの確保を試みる仕組みです。

Direct Reclaimが発生する条件

通常、ページ回収はkswapdがバックグラウンドで実施するため、アプリケーションはページ回収を意識することなく処理を継続できます。

しかし、次のような状況では、kswapdによるページ回収が間に合わなくなることがあります。

  • 短時間で大量のメモリが確保された場合
  • システム全体のメモリ使用率が高い場合
  • kswapdが十分な空きページを確保できなかった場合

このような状態でプロセスが新たなメモリを要求すると、Linuxはそのプロセス自身にページ回収を実行させます。

Direct Reclaimの特徴

Direct Reclaimの最大の特徴は、メモリを要求したプロセス自身がページ回収を行うことです。

通常、ページ回収はkswapdがバックグラウンドで実行するため、アプリケーションはその存在を意識することなく処理を継続できます。

一方、Direct Reclaimでは、メモリを要求したプロセスがページ回収を完了させるまで、本来の処理を進めることができません。つまり、ページ回収がアプリケーションの処理と同じコンテキストで同期的に実行されます。

ページ回収では、ページキャッシュの解放やAnonymous PageのSwap Out、Dirty PageのWritebackなどが行われます。これらの処理ではディスクI/Oが発生する場合もあるため、ページ回収に時間がかかると、アプリケーションの応答時間が大きく悪化することがあります。

そのため、Direct ReclaimはCPU使用率やメモリ使用率だけでは把握しにくい性能劣化の原因となることがあります。

例えば、CPU使用率やメモリ使用率には大きな変化が見られないにもかかわらず、アプリケーションのレスポンスだけが急激に悪化するケースがあります。このような場合は、Direct Reclaimによってメモリ割り当て処理が遅延している可能性があります。

商用システムの障害調査では、メモリ使用率だけでなく、Direct Reclaimが頻繁に発生していないかも確認することが重要です。

Direct Reclaim発生状況の確認

Linuxでは、Direct Reclaimの発生状況を/proc/vmstatから確認できます。

[root@almalinux ~]# grep -E "pgscan_direct|pgsteal_direct" /proc/vmstat
pgsteal_direct 64
pgscan_direct 64
pgscan_direct_throttle 0
[root@almalinux ~]#

各項目の意味は次のとおりです。

項目説明
pgscan_directDirect Reclaimでスキャンしたページ数。値が増加している場合は、プロセス自身がページ回収を実行していることを示します。
pgsteal_directDirect Reclaimによって実際に回収したページ数。pgscan_directとあわせて確認することで、ページ回収の状況を把握できます。
pgscan_direct_throttleDirect Reclaimが一時的に抑制された回数。通常は0であることが多く、頻繁に増加するケースはあまりありません。

これらの値は累積カウンタであるため、単体の値を見るだけでは判断できません。

例えば、1分おきに取得して差分を確認し、短時間で大きく増加している場合は、Direct Reclaimが頻繁に発生している可能性があります。

また、pgscan_directが増えているから必ず問題というわけではありません。短時間だけ増加することは正常な場合もあります。重要なのは、レスポンス低下が発生している時間帯に継続して増加しているかどうかを確認することです。

Direct Reclaimによるメモリ回収まとめ

Memory Compactionとは

前章では、Direct Reclaimによってページ回収が行われることを説明しました。しかし、ページ回収によって十分な空きメモリが確保できても、必要なメモリを割り当てられない場合があります。その原因となるのが、メモリの断片化です。

メモリの断片化とは、空きページは存在しているものの、連続した領域として確保できない状態を指します。このような場合、LinuxはMemory Compactionを実行し、メモリを再配置することで連続した空き領域を確保します。

Compactionが必要な理由

Linuxでは、物理メモリをページ単位で管理しています。しかし、空きページが十分にあっても、バラバラに分散しているだけでは大きなメモリを確保できない場合があります。このような状態をメモリの断片化(Fragmentation)と呼びます。

下図では、空きページは4ページありますが、連続していないため、大きなメモリを割り当てることはできません。

そこでLinuxは、Compactionによって移動可能なページをまとめて移動し、空きページを一か所へ集約します。

その結果、連続した空きページが確保され、大きなメモリ要求にも対応できるようになります。

Compactionは、Huge Pagesや高次(High-Order)のメモリ確保など、連続した物理メモリが必要な場面で重要な役割を果たしています。

Buddy Allocatorとの関係

Memory Compactionが必要になる理由は、Buddy Allocatorが連続した物理ページを割り当てるためです。Buddy Allocatorでは、1ページだけでなく、2ページ、4ページ、8ページといった連続したページをまとめて確保することがあります。

しかし、メモリの断片化が進むと、空きページの総数は十分であっても、大きな連続領域を確保できなくなります。その結果、高次ページの割り当てに失敗することがあります。

高次ページは、例えば次のような用途で利用されます。

  • HugePageの確保
  • 一部のデバイスドライバによるDMAバッファ
  • ネットワークやストレージの大きなバッファ

このようなメモリ要求に対応するため、Linuxは必要に応じてMemory Compactionを実行し、連続した空きページを確保しようとします。

それでも連続した領域を確保できなかった場合は、カーネルログに「page allocation failure」が出力されることがあります。

商用システムでも、このエラーはメモリ不足ではなく、メモリの断片化が原因で発生するケースが少なくありません。

Buddy Allocatorの仕組みについては、以下で詳しく解説しています。
Linuxカーネルのメモリ管理とは?ページフレーム・メモリゾーン・Buddy Allocator・SLUBをわかりやすく解説

OOM Killerとは

前章では、Memory Compactionによってメモリの断片化を解消し、連続した空きページを確保する仕組みについて説明しました。

しかし、ページ回収やMemory Compactionを実行しても、必要なメモリを確保できない場合があります。このような状態が続くと、新たなメモリを割り当てることができず、システム全体が停止してしまう可能性があります。そこでLinuxは、最後の手段としてOOM Killerを実行します。

OOMは「Out Of Memory」の略で、OOM Killerはプロセスを強制終了することでメモリを解放し、システム全体が停止することを防ぐ仕組みです。

OOM Killerが動く条件

Linuxでは、空きメモリが不足した場合でも、すぐにOOM Killerが実行されるわけではありません。まずは、kswapdによるページ回収が行われます。

それでも空きページを確保できない場合は、Direct Reclaimによってメモリを要求したプロセス自身がページ回収を行います。

さらに、メモリの断片化が原因で連続した空き領域を確保できない場合は、Memory Compactionによって空きページを集約します。

それでも必要なメモリを確保できなかった場合に、初めてOOM Killerが実行されます。

このように、OOM Killerはページ回収やメモリ再配置では解決できない場合にのみ実行される、最後の手段です。

OOM Killerの動作

OOM Killerが実行されると、Linuxは終了させるプロセスを選択します。このとき利用される指標が、oom_scoreです。

oom_scoreは、各プロセスがOOM Killerの対象になりやすいかを表す値です。一般的には、より多くのメモリを消費しているプロセスほど高い値となり、OOM Killerによって終了される可能性が高くなります。

また、管理者はoom_score_adjを設定することで、この優先度を調整できます。

例えば、重要なシステムプロセスには低い値を設定し、逆に終了しても影響の少ないプロセスには高い値を設定することで、OOM Killerの対象になりやすさを変更できます。

現在の値は、次のファイルから確認できます。

/proc/<PID>/oom_score
/proc/<PID>/oom_score_adj

OOM Killerは、これらの値を参考にしながら終了対象を選択します。

試しに、私が運用しているLinuxサーバにおけるtomcat(PID:2546596)とpostgres(PID:357466)の値を確認してみます。

[root@almalinux ~]# cat /proc/2546596/oom_score
793
[root@almalinux ~]# cat /proc/357466/oom_score
0
[root@almalinux ~]# cat /proc/2546596/oom_score_adj
0
[root@almalinux ~]# cat /proc/357466/oom_score_adj
-1000
[root@almalinux ~]#
PIDoom_scoreoom_score_adj意味
25465967930補正なし。メモリ使用量などをもとに793と評価され、終了候補になりやすい
3574660-1000OOM Killerの対象から完全に除外されている

ログの見方

OOM Killerが実行されると、カーネルログにメッセージが出力されます。障害発生時は、まずカーネルログを確認しましょう。

例えば、次のコマンドで確認できます。ログ出力の例を記載します。

[root@almalinux ~]# dmesg | grep -i oom
Out of memory: Killed process 12345 (java) total-vm:16777216kB, anon-rss:8388608kB, file-rss:10240kB, shmem-rss:0kB
[root@almalinux ~]# journalctl -k
Aug 03 15:42:18 almalinux kernel: java invoked oom-killer: gfp_mask=0x140dca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0
Aug 03 15:42:18 almalinux kernel: CPU: 3 PID: 12345 Comm: java Not tainted 5.14.0-570.el9.x86_64
Aug 03 15:42:18 almalinux kernel: Out of memory: Killed process 12345 (java) total-vm:16777216kB, anon-rss:8388608kB, file-rss:20480kB, shmem-rss:0kB
Aug 03 15:42:18 almalinux kernel: oom_reaper: reaped process 12345 (java), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB

ログには、OOM Killerが実行された時刻や、終了されたプロセス名、PID、メモリ使用量などが記録されています。

このログから、以下のような事象を確認することができます。

  • どのプロセスが終了されたのか
  • OOM Killerが発生した時刻
  • メモリ不足が原因で障害が発生したか

商用システムでは、OOM Killerが発生した時点ですでにサービス影響が発生していることが多いため、ログを確認するだけでなく、その前後のメモリ使用量やSwap使用量、ページ回収の状況もあわせて調査することが重要です。

まとめ

本記事では、Linuxでメモリ不足が発生した際に、カーネルがどのように空きページを確保するのかを解説しました。

Linuxでは、空きメモリが少なくなったからといって、すぐにOOM Killerが実行されるわけではありません。各メモリゾーンに設定されたWatermarkを基準にメモリの状態を監視し、空きページが不足するとkswapdによるバックグラウンドのページ回収を開始します。

kswapdによる回収が追いつかず、メモリ要求時に必要な空きページを確保できない場合は、メモリを要求したプロセスの処理コンテキストでDirect Reclaimが実行されます。Direct Reclaimは同期的にページ回収を行うため、頻繁に発生するとアプリケーションのレスポンス低下や処理遅延につながる可能性があります。

また、高次ページの確保が必要にもかかわらず、メモリの断片化によって連続した空き領域を確保できない場合には、Memory Compactionによってページを再配置し、連続した空き領域の確保を試みます。それでも確保できない場合には、page allocation failureが発生することがあります。

さらに、ページ回収などを行っても必要なメモリを確保できない場合には、最後の手段としてOOM Killerが実行されることがあります。OOM Killerが実行されると、Linuxはoom_scoreやoom_score_adjなどをもとに終了対象となるプロセスを選択し、プロセスを強制終了することでメモリを解放します。

メモリ不足による障害を調査する際には、単純にメモリ使用率やSwap使用量を見るだけでは十分ではありません。Watermarkの状態に加えて、pgscan_kswapdやpgsteal_kswapd、pgscan_directやpgsteal_directといった情報を確認することで、Linux内部でどの程度ページ回収が発生しているのかを把握できます。

Linuxのメモリ不足を理解するうえで重要なのは、「空きメモリが少ない」という結果だけを見るのではなく、kswapd、Direct Reclaim、Memory Compaction、OOM Killerといった処理の流れを理解することです。この一連の仕組みを理解しておけば、メモリ不足による性能劣化やOOM発生時にも、Linux内部で何が起きていたのかをより正確に切り分けられるようになります。

メモリ管理の詳細については、以下の通り詳細を記載していますので、合わせて読んでみてください。

記事内容リンク
第1回Linuxメモリ管理の全体像(本記事)Linuxメモリ管理を徹底解説|仮想メモリ・ページキャッシュ・Swap・OOM Killerの仕組み
第2回仮想メモリの仕組みLinuxの仮想メモリを徹底解説|仕組み・MMU・ページテーブルをわかりやすく解説
第3回プロセスのメモリレイアウトLinuxプロセスのメモリレイアウトを徹底解説|Heap・Stack・Text・Data・BSS・mmapの役割
第4回Linuxカーネルのメモリ管理Linuxカーネルのメモリ管理とは?ページフレーム・メモリゾーン・Buddy Allocator・SLUBをわかりやすく解説
第5回ページキャッシュLinuxカーネルのメモリ管理とは?ページフレーム・メモリゾーン・Buddy Allocator・SLUBをわかりやすく解説
第6回SwapLinux Swapを徹底解説|Swap Out・Swap In・swappinessの仕組み
第7回メモリ不足時の動作(kswapd・OOM Killer)本記事

コメント