Linuxを利用している方なら一度は経験したことがあるかもしれません。
突然パフォーマンスが低下し、システムが重くなる現象。
これは、プロセスのメモリ使用量とOSのスワップに起因することが多いです。
本記事では、Linux OSにおけるプロセスのメモリ使用量とスワップ発生のメカニズム、影響や原因、そして効果的な調査方法について詳しく紹介します。
システムでは、いろいろなプログラムが動作しているので多面的な分析が必要です。
自分が作成したプログラムが加害者たっだり、被害者だったりします。また、OSのメモリ管理の特性を知らないと原因究明は難しいです。
今回はLinuxで説明していますが、Windowsも同じ考え方です。OSの基本は同じです。
データを収集するツールが違うだけです。
この手の問題の調査方法はいろいろありますが、何かの参考になれば幸いです。
OSのメモリ管理
OSは以下の挙動をします。
(1)物理メモリを使用
(2)物理メモリが足りなくなったら、スワップ領域(ストレージ)を使用
(3)更にメモリが足りなくなったら、OS自体が稼働不可になるのことを防ぐために、OOMKiller(Out of Memory Killer)機構によりメモリを多く使用しているプロセスを強制停止し空きメモリを作ります
OSスワップとは
OSスワップとは?
メモリ不足時の救世主
OSのスワップとは、物理メモリ(RAM)が不足した際に、一時的にデータをハードディスク上に移動させる処理のことです。
これにより、物理メモリに空きがなくても、スワップ領域を使用してシステムの動作を継続できます。
なぜスワップが必要なの?
・物理メモリは有限:
物理メモリは、パソコンやサーバに搭載されている容量が決まっています。
・多くのプログラムが同時に実行:
複数のプログラムを同時に実行したり、大容量のデータを扱ったりすると、物理メモリが不足することがあります。
・スワップによるメモリ拡張:
スワップによって、ハードディスクの一部を仮想的なメモリとして利用することで、物理メモリの容量を拡張できます。
スワップの仕組み
・メモリ不足:
実行中のプログラムが、物理メモリを必要以上に消費し、空きがなくなります。
・スワップアウト:
OSは、あまり使用されていないデータ(ページ)をディスク(ハードディスクやSSD)上のスワップ領域に移動させます。
・メモリ確保:
スワップアウトによって空いた物理メモリに、新しいデータを読み込みます。
・スワップイン:
ディスクに移動したデータが必要になった場合、再び物理メモリに読み込みます。
スワップの注意点
・過度なスワップは避ける:
スワップが頻繁に発生すると、システムのパフォーマンスが大幅に低下します。
・物理メモリを増設:
スワップに頼るのではなく、物理メモリを増設することで、より安定したシステムを実現できます。
・スワップ領域の監視:
スワップ領域の使用状況を定期的に確認し、必要に応じて調整する必要があります。
OSスワップの基本のまとめ
OSのスワップは、物理メモリが不足した際に、システムを継続的に動作させるための重要な機能です。しかし、過度なスワップはシステムのパフォーマンスを低下させるため、適切な設定とチューニングが必要です。
OSスワップが発生し問題となる具体例
LinuxでもWindowsでもOSスワップが発生すると以下のような影響があります。
・データベース・サーバーのパフォーマンス低下:
メモリ不足により、データベース・サーバーがスワップ領域を利用すると、データベースの処理が遅くなることがあります。特に大規模なデータベースに対して、スワップ領域の使用はパフォーマンスに大きな影響を与えます。
・Webサーバーの応答速度の低下:
ApacheやNginxなどのWebサーバーがスワップ領域を利用すると、ウェブページの処理時間が増加し、ユーザーに対する応答速度が低下します。
・アプリケーションのクラッシュ:
特にメモリが不足している場合、システムがOOM Killer(Out of Memory Killer)を起動し、メモリを節約するためにプロセスを強制終了させることがあります。これにより、アプリケーションがクラッシュすることがあります。
これらの例からわかるように、スワップ領域の使用はシステムのパフォーマンスに大きな影響を与えることがあります。
一時的にスワップ領域を使うことは問題ありませんが、長時間スワップを使うことは定常的にディスクの読み書きが発生してシステムのパフォーマンスを低下させます。
プロセスのメモリ使用量、OSのスワップの確認方法
まず、状況把握のために、いつ、どのプロセスでメモリの使用量が増えたとか、OSのスワップが発生したかを確認する必要があります。
正確には、OSによりスワップさせられるプロセスは、使っていないメモリ(ページ)を保持しているプロセスのメモリに一部がスワップさせられます。
スワップを起こす加害者だったり、被害者だったりします。
以下のようなデータを採取して見ていきます。
Zabbixなどのシステム監視ツールで一部取得可能ですが、個々のプロセスの情報はZabbixでは取得できません。
最終的な原因究明にはプロセス(アプリケーション)がどういう処理をしたかの情報も必要です。引き金になった処理の修正やチューニングが必要になるからです。
freeコマンドでスワップ量を確認
free -h コマンドの出力値の読み方
各項目の意味
Mem:
・total: システム全体のメモリの総量
・used: 現在使用中のメモリの量
・free : 現在使用されていないメモリの量
・shared: 複数のプロセスで共有されているメモリの量
・buff/cache: バッファとキャッシュとして使用されているメモリの量
・available: 即座に使用可能なメモリの量
Swap:
スワップパーティションの状況を示します。total, used, free はそれぞれ、スワップパーティションの総量、使用量、空き容量を表す。
スワップ発生時の例
/proc/PID/statusでプロセスのメモリ使用量を確認
/proc/[PID]/status で各プロセスのメモリ使用量の詳細を確認します。 [PID]はプロセスIDの略で一意な数値が入ります。
/proc/[PID]/status ファイルには、Linuxシステムで実行中の各プロセスに関する詳細情報が含まれています。このファイルを確認することで、特定のプロセスの状態やリソース使用状況などの情報を得ることができます。
/proc/[PID]/statusにはいろいろな情報が含まれていますが、以下の項目でプロセスのメモリの使用量とスワップ量が確認できます。
・VmSize: プロセスに割り当てられた仮想メモリの総量 (キロバイト)
・VmHWM: プロセスがこれまで使用した物理メモリの最大値 (キロバイト)
・VmRSS: プロセスが現在使用している常駐セット (Resident Set Size) のサイズ (キロバイト)。物理メモリに常駐している部分
・VmData: プロセスがデータセグメントで使用しているメモリ量 (キロバイト)
・VmSwap: プロセスがスワップ領域で使用しているメモリ量 (キロバイト)
/proc/[PID]/statusの例
/proc/PID/statusの情報でプロセスのメモリ使用量を分析
/proc/PID/statusの情報で分析します。
以下の図のパターンの例
・5つのjavaアプリが同時に実行されている。それぞれのjavaプロセスがSwapを保持。
・時間経過でSwapが増えている。RSS(物理メモリ常駐部分)が減り、Swapに回った動き。・VmSizeやVmHWMに変化がないので、これらのプロセスが新たにメモリを確保した可能性は低い。
→他の要因でOS全体のメモリが不足して、各プロセスがSwapに追い出された可能性が高い。
何がどのタイミングで動作してSwapが発生したかを確認するために時系列のデータが必要です。
dstatコマンドでOSの各種情報を確認
dstatコマンドはインストールされていない可能性があるので事前にインストールする。
Red Hat Linux系
sudo dnf –y install dstat
Ubuntu系OS
sudo apt -y update
sudo apt -y install dstat
dstatコマンド とは
dstatコマンドは、Linuxシステムのリソース使用状況をリアルタイムで監視するためのツールです。dstatはCPU、メモリ、ディスクI/O、ネットワーク、ページング、システムロードなど、さまざまなシステムリソースの統計情報を同時に表示することができます。
dstatコマンドの特徴:
・複数の統計情報の同時表示: dstatは、CPU、メモリ、ディスクI/O、ネットワークなどの統計情報を一つの画面に表示可能。
・リアルタイム更新: dstatは、リアルタイムで統計情報を更新し続けるため、システムの状態を常に把握可能。
・カスタマイズ可能: dstatは、必要に応じて表示する統計情報をカスタマイズできます。様々なオプションが用意されており、特定の情報のみを表示することが可能。
dstatの実行例
メモリ不足が発生するとスワップイン・スワップアウトにより、ディスクI/O(page IN/Page OUT)が発生するので、情報を採取する必要があります。
プロセスごとのメモリとスワップの情報採取用の工夫
分析には、複数のプロセスが実行される場合もあり、10秒や1分間隔のデータが必要なので、工夫が必要です。
/proc/[PID]/statusの採取スクリプト
すべてのjavaプログラムのメモリ情報を採取します。
PID(プロセスID)だけだと、プログラム名が分からないので、以下のコマンドでプログラム名を採取しておきます。
$ ps -ef | grep java
<java-vm.shスクリプト>
スクリプトの”java”の部分を自分が調査したいプロセス名に変更すれば、他のプログラムの調査にも使えます。
#!/bin/bash
echo "Time,PID,VmSize,VmHWM,VmRSS,VmData,VmSwap"
# 10秒おきに情報を取得し、CSV形式で出力
while true; do
# javaプロセスのPIDを取得
pids=$(pgrep java)
for pid in $pids; do
if [ -e /proc/$pid/status ]; then
vm_size=$(grep VmSize /proc/$pid/status | awk '{print $2, $3}')
vm_hwm=$(grep VmHWM /proc/$pid/status | awk '{print $2, $3}')
vm_rss=$(grep VmRSS /proc/$pid/status | awk '{print $2, $3}')
vm_data=$(grep VmData /proc/$pid/status | awk '{print $2, $3}')
vm_swap=$(grep VmSwap /proc/$pid/status | awk '{print $2, $3}')
echo "$(date +'%Y-%m-%d %H:%M:%S'),$pid,$vm_size,$vm_hwm,$vm_rss,$vm_data,$vm_swap"
fi
done
sleep 10
echo "------------------------------------------------------"
free
done
出力結果は以下のような感じです。この場合は、5個のプロセスが動作しています。
Time,PID,VmSize,VmHWM,VmRSS,VmData,VmSwap
------------------------------------------------------
total used free shared buff/cache available
Mem: 64932336 45854412 421104 594028 18656820 17788032
Swap: 16777212 317952 16459260
2025-01-25 10:22:07,2544,5293816 kB,405848 kB,392468 kB,501408 kB,0 kB
2025-01-25 10:22:07,3684,11741964 kB,980372 kB,980372 kB,2441236 kB,0 kB
2025-01-25 10:22:07,3892,21838916 kB,6303636 kB,6222064 kB,7431344 kB,82508 kB
2025-01-25 10:22:07,4105,4118588 kB,599124 kB,598832 kB,1372880 kB,292 kB
2025-01-25 10:22:07,4485,9295140 kB,1296320 kB,1296320 kB,2498880 kB,0 kB
------------------------------------------------------
total used free shared buff/cache available
Mem: 64932336 45854412 421104 594028 18656820 17788032
Swap: 16777212 317952 16459260
2025-01-25 10:22:18,2544,5293816 kB,405848 kB,392468 kB,501408 kB,0 kB
2025-01-25 10:22:18,3684,11741964 kB,980372 kB,980372 kB,2441236 kB,0 kB
2025-01-25 10:22:18,3892,21838916 kB,6303636 kB,6222048 kB,7379020 kB,82508 kB
2025-01-25 10:22:18,4105,4118588 kB,599124 kB,598832 kB,1372880 kB,292 kB
2025-01-25 10:22:18,4485,9295140 kB,1296320 kB,1296320 kB,2498880 kB,0 kB
------------------------------------------------------
データ採取の手順
データ採取手順
■実行手順
今回は、javaで作成されたプログラムを調査する想定です。
(1)Linuxへの接続が切れても問題ないようにバックグランドで実行します。
Linuxにログインして以下の2つのコマンドを実行します。
nohup bash java-vm.sh > vm-java-$(date +%Y%m%d-%H%M%S).txt &
nohup dstat –time 10 –output dstat-$(date +%Y%m%d-%H%M%S).csv &
(2)調査したいプログラムを実行します。
(3)10秒ごとに出力されているかを確認します。
ファイル名は、実行日時で作成される。
cat vm-java-20250125-100312.txt
cat dstat-20250125-100312.csv
24Hで、それぞれ2MBぐらいのファイルが作成されます。
■スクリプトの停止方法
pkill -f java-vm.sh
pkill -f dstat
# 念のためにプロセスが残っていないか確認します
ps -ef | grep java-vm.sh
ps -ef | grep dstat
■回収するファイル
スクリプトの実行結果ファイル
(例) 20250125-100312 の部分は実行日時で変わります
vm-java-20250125-100312.txt
dstat-20250125-100312.csv
集計を簡単にするためにCSVに変換
ファイルの加工はいろいろやり方があるので、参考程度です。
bashのスクリプトは、生成AIで作成。ほぼ完ぺきですね。5分程度作成可能。
・PIDごとにCSVファイルに変換
・freeコマンドの結果をCSVファイルに変換
<java-csv.shスクリプト>
$ bash java-csv.sh vm-java-20250125-100312.txt
[PID]-java.csvというファイルが、動作していたプロセスの数だけ作成されます。さんk
[PID]にはプロセスIDの数値が入ります。
1234-java.csvのようなファイル名。
#!/bin/bash
# 入力ファイル名を引数から取得
input_file="$1"
# PIDのリストを作成
numbers=($(awk -F, 'NR > 1 {print $2}' ${input_file} | sort | uniq))
for number in "${numbers[@]}"
do
# 第2カラムのPIDでファイルを分ける
awk -F, "\$2 ~ /$number/" $input_file > "${number}-java.txt"
# "kB" を削除
sed 's/ kB//g' "${number}-java.txt" > "${number}-java.csv"
# ヘッダーを追加
sed -i '1i Time,PID,VmSize,VmHWM,VmRSS,VmData,VmSwap' "${number}-java.csv"
done
# freeコマンドの結果をcsvファイルに出力
grep Mem: ${input_file} > free-mem.txt
sed -i '1i total used free shared buff/cache available' free-mem.txt
# "Mem:" を削除
sed 's/Mem://g' free-mem.txt > free-mem.csv
grep Swap: ${input_file} > free-swap.txt
sed -i '1i total used(KB) free(KB)' free-swap.txt
# "Swap:" を削除
sed 's/Swap://g' free-swap.txt > free-swap.csv
データの見方
(1)freeコマンドでOS全体のスワップ量を把握
(2)dstatのデータでスワップアウト/スワップイン(page IN/page OUT)のタイミングと回数を把握
スワップアウトとスワップインの回数が少ない場合は、スワップの影響は少ない。
(3)個々のプロセスのスワップ量と発生タイミングを把握
(4)アプリの処理内容と合わせて、問題箇所を絞っていく
こういった地道な作業が必要です。
ノウハウ
検証の注意事項
OSがファイルキャッシュでメモリを使うので、正確な検証を行う時は、OSのキャッシュをクリアしておくことを推奨します。
sudo sysctl -w vm.drop_caches=3
リアルタイムでメモリ利用状況を確認する方法
以下を複数のコンソールを開いて実行して、メモリの使用状況を確認します。
今まで説明してきたスクリプト群は、これらの情報を収集しています。
リアルタイムだと結果が流れてしまって、後から分析できませんからね。
a)free –h (10秒間隔)
b)topコマンドで VIRT/RESなど確認
c)/proc/$pid/status からjavaプロセスごとにメモリ情報を取得
d)dstatでpage IN/OUTを確認
e)プログラムの実行。バックジョブで複数実行
java MemoryFileRead &
java MemoryFileRead &
java MemoryFileRead &
:
この画面の実験環境
Ubuntu 22.04 server: 物理メモリ8GB、スワップ領域4GB
以下は、C言語で作成したプログラムでの実験。OSの動作はプログラミング言語に依存しないので、JavaとC言語のプログラムで同じような動作になります。
リアルタイムでメモリ利用状況を確認するポイント
dstatコマンドでpage IN/OUT(スワップイン/スワップアウト)を確認すると
このプログラムは、メモリ確保とファイルの読み込み(read)しか行っていない状況でディスクの書き込み(write)が発生しています。
ディスクのwrite量が、page out の値とほぼ同じ値で、スワップアウトへのディスク書き込みだと推測できます。
また、freeコマンドでスワップが発生してスワップ領域を使っていること分かります。
画像ではスワップ領域も使い切り、OOM Killerが発生し、プログラムがKILL(強制停止)されています。
検証で使ったファイルのダウンロード
この記事で説明したスクリプト、Excelのグラフのファイル、Javaプログラム、C言語のプログラムをダウンロード可能です。
自由に使ってください。
【Linux-memory-swap調査用関連ファイルのダウンロード】
最後に
今回紹介した技術的なノウハウが分かりやすく書かれた書籍があるので、紹介します。
私もこの本を読んで理解が深まりました。復習して、細かい知識を再確認できました。
第4章に メモリ管理とfreeコマンドの詳細説明があります。
この類の本を読んだことがなければ、読むことを強くお勧めします。1レベル上がります。
Webの情報でなんとなく知っていた知識が、体系立てて理解できます。
メモリ不足の時のOOMkillerが発生する場合があります。OOMkillerについては、以下の記事にまとめました。