Javaアプリケーションを開発・運用するにあたり、OSのメモリ管理の概要を知っておく必要があります。本節では、OSの一般的なメモリ管理技法の1つである仮想アドレス空間を説明します。
なお、仮想アドレス空間の具体的なアーキテクチャーはOSごとに異なりますが、本節では各OSに共通する内容を説明します。
仮想メモリ
OSは、物理メモリ(RAM)だけでなくスワップファイルを活用することにより、多くのメモリ領域を使用することができます。このテクノロジーを仮想メモリといいます。仮想メモリの容量は、RAMとスワップファイルのサイズの合計になります。
図1 OSが利用可能なメモリ容量
ただし、ハードディスクへのアクセスはRAMより低速なので、メモリのスワッピングは性能に大きく影響を与えますので、注意が必要になります。
仮想アドレス空間
OS上でプログラムを起動すると、OSがプログラムを実行・管理する単位としてのプロセスが生成されます。同様にして、Javaアプリケーションを起動すると、Javaプロセスが生成されます。
OS上で生成されたプロセスには、仮想アドレス空間が割り当てられます。仮想アドレス空間は、それぞれのプロセスで独立したものであり、あるプロセスから別のプロセスの仮想アドレス空間にアクセスすることはできません。マシンに積んでいる物理メモリ(RAM)のサイズとは関係なく、仮想アドレス空間のサイズは、常に一定です。たとえば、32ビットアーキテクチャーのOSの場合、物理メモリ(RAM)のサイズに依存せず、仮想アドレス空間のサイズは常に4GB(2の32乗バイト)です。
このため、大量の仮想メモリを用意しても、1つのプロセスで使用できるメモリ量の上限は仮想アドレス空間の上限になりますから、プロセスが仮想アドレス空間の大きさ(実際には後述のユーザ空間の大きさ)を越えるメモリ量を必要とする場合は、メモリ不足の状態になります。
逆に、プロセスが必要とするメモリ量が仮想アドレス空間の大きさ(実際には後述のユーザ空間の大きさ)の上限未満だったとしても、そのメモリ量に相当する仮想メモリ量がOS上になければ、メモリ不足の状態になります。
また、大量の仮想メモリを用意しても、OS上で大量のプロセスが動作していて仮想メモリを大量に消費している状態であれば、各プロセスに割り当てられた仮想アドレス空間を使い切っていなくても、メモリ不足の状態になる場合があります。
ユーザ空間
プロセスが持つ仮想アドレス空間のうち、実際にプロセスが使用できる空間を、ユーザ空間といいます。ユーザ空間には、プログラムの実体(Windows(R)でJavaアプリケーションを実行する場合は、java.exeなど)がコピーされるだけでなく、スタックやヒープなどのさまざまなセグメントがあります。更にユーザ空間は、実行するプログラムだけでなく、そのプログラムを実行させるためのOS側のプログラムなどでも使用します。Javaプロセスのユーザ空間の場合には、前述の各セグメントの他に、Javaオブジェクトを格納するセグメント(=Javaヒープ)があります。このため、Javaアプリケーションをチューニングする際の対象となるJavaヒープのサイズの上限値は、ユーザ空間のサイズよりも少なくなります。
Javaアプリケーションのチューニングを実施する際は、仮想メモリの容量やプロセスの使用状況など、システムの状態を考慮する必要があります。なおOSの種類やアプリケーションの実行モードによって、ユーザ空間として使用できる上限値が異なるため、注意が必要です。
なお、各セグメント獲得・解放時の実際の制御処理はOSが行います。そのため、各セグメントに関する管理方法/動作仕様/大きさについては、JDK/JREを実行する各OSの仕様に依存します。そして、プロセス外部からはユーザ空間に空きが存在するように見える場合であっても、OSの制御処理上は利用できる空きが無いと判断され、セグメントに対応するメモリ領域が獲得できず、プロセス動作が異常となる場合がありますので注意してください。
また、OSの制御処理上、各セグメント間には、アプリケーションから利用できない隙間領域が存在する場合があります。そのため、通常、ユーザ空間としての上限値まで仮想メモリを使用することはできません。プロセスが使用するメモリ量として、ユーザ空間の上限値まで使えることを前提とした設計にはしないでください。
実行モード
実行モードが変わると、プログラムの実行に必要な基本メモリ量が変わります。
具体的には、プログラムの実行モードを32ビットモードから64ビットモードに変更して実行する場合、ポインタを扱うために必要となるメモリ量の単位が、4バイトから8バイトになります(OSによっては、整数を扱う際に必要となるメモリ量の単位も異なります)。そのため、同じソースから作られたプログラムの場合であっても、64ビットOS上で64ビットモードのプログラムを使ってアプリケーションを動作させる場合は、32ビットOSまたは64ビットOS上で32ビットモードのプログラムを使ってアプリケーションを動作させる場合よりも、より大きなメモリ量が必要となります。
64ビットモード時のCデータ型モデル
LP64モデル:long型/ポインタが32ビット(4バイト)から64ビット(8バイト)へ変更されます。
P64モデル :ポインタが32ビット(4バイト)から64ビット(8バイト)へ変更されます。
Javaアプリケーションの場合もC/C++アプリケーションの場合と同様、実行モードが変わると、プログラムの実行に必要な基本メモリ量が変わります。
特に、オブジェクトを構成するデータ内容にはポインタを扱う情報も多数あるため、 64ビットモードの場合、1オブジェクトあたりに必要となるメモリ域の大きさは32ビットモードの場合よりも大きくなります。
そのため、通常、64ビットOS上で64ビットモードのJDK/JREを使ってJavaアプリケーションを動作させる場合、32ビットOSまたは64ビットOS上で32ビットモードのJDK/JREを使ってJavaアプリケーションを動作させた場合の設定に対して、1.5~2倍のJavaヒープ量が必要です。