Linuxのメモリ管理を理解するには、システムコールの動作とツールを活用した解析が不可欠です。ユーザー空間のメモリレイアウトは、低アドレスから高アドレスの順にコードセグメント、データセグメント、ヒープ、ファイルマッピングセグメント、スタックで構成されます。コードセグメントには実行コードと定数が格納され、データセグメントはグローバル変数を保持します。ヒープは動的メモリ割り当てに使用され低アドレス方向に成長し、ファイルマッピングセグメントは共有ライブラリや共有メモリを含み高アドレス方向に成長します。スタックはローカル変数と関数呼び出しコンテキストを保持し、通常8MBの固定サイズです。
malloc関数は、小領域(128KB未満)の割り当てにbrkシステムコールを、大領域(128KB以上)の割り当てにmmapを用います。brk方式では解放されたメモリを再利用するためページフォールトを抑制できますが、メモリ断片化のリスクがあります。mmap方式は解放時に即座にシステムに返却されるため、頻繁な割り当て時にシステムオーバーヘッドが発生しやすくなります。
カーネル空間ではslabアロケータが小オブジェクト管理に活用され、パートナーシステムの上に構築されたキャッシュ機構として動作します。
メモリ回収はLRUアルゴリズムによる最近使用頻度の低いページの回収、スワップ領域へのデータ転送、OOM Killerによるメモリ過剰使用プロセスの強制終了で実施されます。OOM Killerは各プロセスのoom_scoreに基づいて終了対象を決定し、システムの安定性を維持します。
freeコマンドのavailable列は未使用メモリと回収可能なキャッシュを含むため、実際の利用可能なメモリ量を正確に示します。topコマンドのVIRTは仮想メモリ総量、RESは物理メモリ使用量、SHRは共有メモリ領域を示します。ただしSHRはコードセグメントや動的リンクライブラリも含むため、複数プロセスのメモリ使用量を合算する際は注意が必要です。
/usr/share/bcc/tools/memleak -p $(pidof app) -a
Attaching to pid 12512, Ctrl+C to quit.
[03:00:41] Top 10 stacks with outstanding allocations:
addr = 7f8f70863220 size = 8192
addr = 7f8f70861210 size = 8192
...
40960 bytes in 5 allocations from stack
create_node+0x1f [app]
main_loop+0x4f [app]
start_thread+0xdb [libpthread-2.27.so]この例ではcreate_node関数でメモリを割り当てたが解放されていないことがわかります。修正例:
struct data_block {
int value;
struct data_block *next;
};
struct data_block *allocate_data(int val) {
struct data_block *block = (struct data_block *)malloc(sizeof(struct data_block));
block->value = val;
block->next = NULL;
return block;
}
void process_data() {
struct data_block *head = NULL;
for (int i = 0; i < 500; i++) {
head = allocate_data(i);
free(head);
}
}