メモリ管理(動的メモリ管理、動的ストレージ割り当て、または動的メモリ割り当てとも呼ばれる)は、コンピュータのメモリに適用されるリソース管理の一形態です。メモリ管理の基本的な要件は、プログラムからの要求に応じてメモリの一部を動的に割り当て、不要になったときに再利用できるように解放する方法を提供することです。これは、複数のプロセスが同時に実行される可能性のある(マルチタスク)高度なコンピュータシステムにとって非常に重要です。 [ 1 ]
メモリ管理の効率を高めるための様々な手法が考案されてきました。仮想メモリシステムは、プロセスが使用するメモリアドレスを実際の物理アドレスから分離することで、プロセスの分離を可能にし、ページングや二次記憶装置へのスワッピングを用いて、利用可能なRAM容量を超えて仮想アドレス空間のサイズを拡大します。仮想メモリマネージャの品質は、システム全体のパフォーマンスに大きな影響を与える可能性があります。このシステムにより、コンピュータは物理的に存在するメモリよりも多くのメモリを利用できるかのように見せかけることができ、複数のプロセスがメモリを共有できるようになります。
Burroughs/Unisys MCP [ 2 ]やOS/360 および後継システム[ 3 ]などの一部のオペレーティングシステムでは、メモリはオペレーティングシステムによって管理されます。[注 1 ] Unix ライクなオペレーティングシステムなどの他のオペレーティングシステムでは、メモリはアプリケーション レベルで管理されます。
アドレス空間内のメモリ管理は、一般的に手動メモリ管理と自動メモリ管理に分類される。

メモリ割り当て要求を満たすタスクは、十分なサイズの未使用メモリブロックを見つけることです。メモリ要求は、ヒープ[注3 ]またはフリーストアと呼ばれる大きなメモリプール[注2 ]から一部を割り当てることによって満たされます。任意の時点で、ヒープの一部は使用中であり、一部は「空き」(未使用)であり、将来の割り当てに使用できます。C言語では、ヒープからメモリを割り当てる関数は と呼ばれ、以前に割り当てられたメモリを取得して「空き」(将来の割り当てで使用)としてマークする関数は と呼ばれます。[注4 ]mallocfree
実装を複雑にするいくつかの問題があります。例えば、割り当てられたメモリブロック間に小さなギャップが多数存在すると、割り当て要求に使用できなくなる外部断片化が発生します。また、アロケータのメタデータによって、(個々には)小さな割り当てのサイズが膨張することもあります。これは多くの場合、チャンキングによって対処されます。メモリ管理システムは、未割り当ての割り当てを追跡し、それらが重複せず、メモリが「失われる」(つまり「メモリリーク」が発生しない)ことがないようにする必要があります。
実装される具体的な動的メモリ割り当てアルゴリズムは、パフォーマンスに大きな影響を与える可能性があります。1994 年にDigital Equipment Corporationが行った調査では、さまざまな割り当てアルゴリズムに伴うオーバーヘッドが示されています。単一のメモリ スロットを割り当てるために必要な平均命令パス長は、52 でした (さまざまなソフトウェアで命令レベルプロファイラを使用して測定)。 [ 1 ]
メモリ割り当ての正確な位置は事前にわからないため、メモリへのアクセスは通常ポインタ参照を介して間接的に行われます。メモリ領域を整理し、チャンクを割り当てたり解放したりするために使用される具体的なアルゴリズムはカーネルと密接に関連しており、以下のいずれかの方法を使用する場合があります。
固定サイズブロック割り当て(メモリプール割り当てとも呼ばれる)は、固定サイズのメモリブロック(多くの場合、すべて同じサイズ)の空きリストを使用します。これは、大きなオブジェクトを割り当てる必要のないシンプルな組み込みシステムには適していますが、特に長いメモリ アドレスでは断片化の問題が発生します。しかし、オーバーヘッドが大幅に削減されるため、この方法は、頻繁な割り当てと解放が必要なオブジェクトのパフォーマンスを大幅に向上させることができ、ビデオゲームなどでよく使用されます。
このシステムでは、メモリは1つではなく複数のメモリプールに割り当てられ、各プールは特定の2のべき乗のサイズのメモリブロック、またはその他の都合の良いサイズの進行のブロックを表します。特定のサイズのすべてのブロックは、ソートされたリンクリストまたはツリーに保持され、割り当て中に形成されたすべての新しいブロックは、後で使用するためにそれぞれのメモリプールに追加されます。利用可能なサイズよりも小さいサイズが要求された場合、利用可能な最小のサイズが選択されて分割されます。ブロックが分割されると、2つの小さなブロックに分割され、それぞれの小さなブロックは他のブロックの固有の「バディ」になります。結果として得られた部分の1つが選択され、要求が完了するまでプロセスが繰り返されます。ブロックが割り当てられるとき、アロケータはブロックを不必要に分割しないように、十分大きい最小のブロックから開始します。ブロックが解放されると、そのバディと比較されます。両方とも空いている場合は、それらが結合され、対応するサイズの大きいバディブロックリストに配置されます。
このメモリ割り当てメカニズムは、特定の型またはサイズのオブジェクトに適したメモリチャンクを事前に割り当てます。[ 5 ]これらのチャンクはキャッシュと呼ばれ、アロケータは空きキャッシュスロットのリストを追跡するだけで済みます。オブジェクトを構築すると、空きキャッシュスロットのいずれかが使用され、オブジェクトを破棄すると、スロットが空きキャッシュスロットリストに追加されます。この手法はメモリの断片化を軽減し、適切なメモリ部分を検索する必要がないため効率的です。どの空きスロットでも十分です。
多くのUnix ライクなシステムやMicrosoft Windows ではalloca、ヒープベースの と同様にスタック メモリを動的に割り当てる関数が実装されていますmalloc。コンパイラは通常、これをスタック ポインタを操作するインライン命令に変換します。[ 6 ]このように割り当てられたメモリは、呼び出された関数が戻ると自動的に解放されるため、手動で解放する必要はありませんがalloca、オーバーフローのリスクがあります。また、alloca は多くのシステムで見られるアドホックな拡張ですが、 POSIXや C 標準には含まれていないため、スタック オーバーフローが発生した場合の動作は未定義です。
エラーを報告する、より安全なバージョンの alloca が_mallocaMicrosoft Windows に存在します。これには の使用が必要です_freea。[ 7 ] gnulib は同等のインターフェースを提供しますが、オーバーフロー時に SEH 例外をスローする代わりに、大きすぎるサイズが検出されると malloc に処理を委譲します。[ 8 ]同様の機能は、glibc での の使用のように、手動でのアカウンティングとサイズチェックを使用してエミュレートできますalloca_account。[ 9 ]
アプリケーションにおけるメモリの適切な管理は難しい問題であり、メモリ管理を扱うためのさまざまな戦略が考案されてきた。
多くのプログラミング言語の実装では、プログラムの実行環境は、サブルーチンが呼び出されると、そのサブルーチンの非静的ローカル変数(自動変数と呼ばれる)のためにコールスタックにメモリを自動的に割り当て、サブルーチンが終了すると自動的にそのメモリを解放します。特別な宣言により、ローカル変数がプロシージャの呼び出し間で値を保持したり、他のサブルーチンからローカル変数にアクセスしたりすることが可能になります。ローカル変数の自動割り当てにより、利用可能なメモリの深さに制限される範囲で再帰が可能になります。
ガベージコレクションとは、プログラム内で不要になったオブジェクトに割り当てられたメモリを自動的に検出し、そのメモリを空きメモリ領域に戻すための戦略です。この方法は、プログラマがプログラム内でメモリ要求とメモリ解放を明示的に記述する「手動」メモリ管理とは対照的です。自動ガベージコレクションには、プログラマの作業負荷を軽減し、特定の種類のメモリ割り当てバグを防ぐという利点がありますが、ガベージコレクション自体にもメモリリソースが必要であり、アプリケーションプログラムとプロセッサ時間を競合する可能性があります。
参照カウントは、メモリを指す独立したポインタの数をカウントするカウンタを保持することで、プログラムがメモリを使用できなくなったことを検出するための戦略です。新しいポインタがメモリ領域を指すたびに、プログラマはカウンタを増やす必要があります。ポインタが指す場所が変わったり、ポインタがどの領域も指さなくなったり、ポインタ自体が解放されたりした場合は、カウンタを減らす必要があります。カウンタがゼロになると、メモリは未使用とみなされ、解放されます。参照カウントシステムの中には、プログラマの関与が必要なものもあれば、コンパイラによって自動的に実装されるものもあります。参照カウントの欠点は、循環参照が発生し、メモリリークを引き起こす可能性があることです。これは、「弱い参照」(参照カウントには参加しないが、指している領域が無効になったときに通知される参照)の概念を追加するか、参照カウントとガベージコレクションを組み合わせることで軽減できます。
メモリプールとは、リクエストやトランザクションのライフサイクルなど、アプリケーションの状態に基づいてメモリを自動的に解放する手法です。多くのアプリケーションは、メモリ割り当てを生成する可能性のある大きなコード塊を実行しますが、実行のある時点で、それらのコード塊がすべて不要になることがわかります。たとえば、Webサービスでは、各リクエストの後、Webサービスはリクエストの実行中に割り当てられたメモリを一切必要としなくなります。そのため、メモリが現在参照されているかどうかを追跡するのではなく、関連付けられているリクエストまたはライフサイクル段階に応じてメモリが割り当てられます。そのリクエストまたは段階が終了すると、関連付けられているすべてのメモリが同時に解放されます。
仮想メモリは、メモリ構成を物理ハードウェアから切り離す方法です。アプリケーションは仮想アドレスを介してメモリを操作します。アプリケーションが特定の仮想メモリ アドレスにアクセスしようとするたびに、仮想メモリ アドレスが実際の物理アドレスに変換されます。[ 10 ] このように、仮想メモリを追加することで、メモリ システムとアクセス方法をきめ細かく制御できます。
仮想メモリシステムでは、オペレーティングシステムがプロセスによるメモリへのアクセス方法を制限します。この機能はメモリ保護と呼ばれ、プロセスが自身に割り当てられていないメモリへの読み書きを禁止することで、あるプログラム内の悪意のあるコードや誤動作したコードが別のプログラムの動作を妨害するのを防ぎます。
特定のプロセスに割り当てられるメモリは通常は分離されていますが、プロセス間で情報を共有する必要が生じる場合もあります。共有メモリは、プロセス間通信において最も高速な手法の一つです。
メモリは通常、アクセス速度によって主記憶装置と補助記憶装置に分類されます。メモリ管理システムは、他の操作に加えて、これら2つのメモリレベル間での情報の移動も処理します。
オペレーティングシステムは、コンピュータシステム内の様々なリソースを管理します。メモリサブシステムは、メモリを管理するためのシステム要素です。メモリサブシステムは、ハードウェアメモリリソースと、そのリソースを管理するMCP OSソフトウェアを組み合わせたものです。
メモリサブシステムは、システムの物理メモリと仮想メモリ(どちらもハードウェアリソースの一部)を管理します。仮想メモリは、通常はディスクなどの周辺機器の追加領域を使用することで、物理メモリを拡張します。メモリサブシステムは、オーバーレイと呼ばれるプロセスで、メインメモリと仮想メモリ間でコードとデータを移動する役割を担います。バローズは、仮想メモリを商用化した最初の企業であり(ただし、マンチェスター大学でフェランティ・アトラス・コンピュータ向けに開発された)、B5000のシステム設計に当初から(1961年)仮想メモリを統合し、外部メモリ管理ユニット(MMU)を必要としませんでした。[ 11 ]: 48
メモリサブシステムは、メモリブロックの論理要求を、空きセグメントのリストにあるメモリの物理的な部分(セグメント)にマッピングする役割を担います。割り当てられた各ブロックは、セグメント記述子[ 12 ]によって管理されます。セグメント記述子は、アドレス、長さ、マシンタイプ、およびブロックがメインメモリにあるか、記述子で指定されたアドレスからロードする必要があるかを示すpビットまたは「存在」ビットなど、セグメントに関する関連メタデータを含む特別な制御ワードです。
ディスクリプタは、メモリの安全性とセキュリティを確保する上で不可欠であり、操作によって参照ブロックがオーバーフローしたりアンダーフローしたりすることを防ぎます(一般にバッファオーバーフローとして知られています)。ディスクリプタ自体は保護された制御ワードであり、MCP OSの特定の要素(NEWPのUNSAFEブロックディレクティブによって有効化される)を除いて操作することはできません。
ドナルド・クヌースは、『基本アルゴリズム』の第2.5節「動的ストレージ割り当て」で同様のシステムについて説明しています。
IBM System/360 は仮想メモリをサポートしていません。[注 5 ]ジョブのメモリ分離は、オプションで保護キーを使用して実現できます。各ジョブのストレージに異なるキーを割り当てます。0 はスーパーバイザ用、1 ~ 15 はスーパーバイザ用です。OS /360のメモリ管理はスーパーバイザの機能です。ストレージはマクロを使用して要求されGETMAIN、マクロを使用して解放されます。FREEMAINこれにより、スーパーバイザ ( SVC ) が呼び出され、操作が実行されます。
OS/360では、 PCP、MFT、MVTなど、システムがどのように生成されるかによって詳細が異なります。
OS/360 MVT では、ジョブの領域または共有システム キュー領域(SQA) 内のサブ割り当ては、サブプールに基づいています。サブプールは、 保護キーで保護された領域のサイズである2 KB の倍数のサイズの領域です。サブプールには 0 ~ 255 の番号が付けられます。 [ 13 ]領域内では、サブプールにはジョブのストレージ保護またはスーパーバイザのキー (キー 0) のいずれかが割り当てられます。サブプール 0 ~ 127 にはジョブのキーが割り当てられます。最初はサブプール 0 のみが作成され、メモリ要求で別のものが指定されていない限り、すべてのユーザー ストレージ要求はサブプール 0 から満たされます。サブプール 250 ~ 255 は、スーパーバイザがジョブに代わってメモリ要求によって作成します。これらのほとんどにはキー 0 が割り当てられますが、ジョブのキーを取得するサブプールもいくつかあります。サブプール番号は MFT でも関係がありますが、詳細ははるかに単純です。[ 14 ] MFTは動的領域の代わりにオペレーターによって再定義可能な 固定パーティションを使用し、PCPは単一のパーティションのみを持つ。
各サブプールは、サブプール内の割り当て済みおよび空きメモリブロックを識別する制御ブロックのリストによってマッピングされます。メモリは、十分なサイズの空き領域を見つけるか、ジョブの領域サイズまでサブプールに追加のブロックを割り当てることによって割り当てられます。割り当てられたメモリ領域の全部または一部を解放することができます。[ 15 ]
OS/VS1の詳細は 、MFT および MVT の詳細と類似しています[ 16 ] 。OS /VS2の詳細は、ページ サイズが 4 KiB である点を除いて、MVT の詳細と類似しています。OS/VS1 と OS/VS2 の両方で、共有システム キュー領域(SQA) はページングできません。
MVSでは、アドレス空間[ 17 ]には、ページング可能な共有領域である共通記憶領域(CSA)と、ページング不可能なローカルシステムキュー領域(LSQA)およびページング可能なシステムワーク領域(SWA)という2つのプライベート領域が追加されています。また、記憶キー0~7はすべて特権コードの使用用に予約されています。