Java プロセスを構成するものを考えてみましょう。
あなたが持っている:
- JVM (C プログラム)
- JNI データ
- Java バイトコード
- Java データ
特に、それらはすべて C ヒープに存在します (当然、JVM ヒープは C ヒープの一部です)。
Java ヒープには、単純に Java バイト コードと Java データが含まれます。しかし、Java ヒープにもあるのは「空き領域」です。
典型的な (つまり、Sun の) JVM は、必要に応じて Java ヒープを拡大するだけで、縮小することはありません。定義された最大値 (-Xmx512M) に達すると、成長を停止し、残ったものを処理します。その最大ヒープが使い果たされると、OutOfMemory 例外が発生します。
Xmx512M オプションがしないことは、プロセス全体のサイズを制限することです。プロセスの Java ヒープ部分のみを制限します。
たとえば、10 MB の Java ヒープを使用するが、500 MB の C ヒープを割り当てる JNI 呼び出しを呼び出す不自然な Java プログラムを作成できます。Java ヒープが小さいにもかかわらず、プロセス サイズがいかに大きいかがわかります。また、新しい NIO ライブラリを使用すると、ヒープの外部にメモリをアタッチすることもできます。
考慮しなければならないもう 1 つの側面は、Java GC は通常、「コピー コレクター」であるということです。つまり、収集しているメモリから「ライブ」データを取得し、メモリの別のセクションにコピーします。コピー先のこの空きスペースは、少なくとも Xmx パラメータに関してはヒープの一部ではありません。これは「新しいヒープ」のようなもので、コピー後にヒープの一部になります (古いスペースは次の GC に使用されます)。512MB のヒープがあり、それが 510MB の場合、Java はライブ データをどこかにコピーします。素朴な考えは、別の大きなオープンスペース (500+MB など) に対するものです。すべてのデータが「ライブ」である場合、コピー先としてそのような大きなチャンクが必要になります。
したがって、最も極端なケースでは、特定のヒープ サイズを処理するには、システムに少なくとも 2 倍の空きメモリが必要であることがわかります。512MB のヒープには少なくとも 1GB。
実際にはそうではないことが判明し、メモリの割り当てなどはそれよりも複雑ですが、ヒープ コピーを処理するために大量の空きメモリが必要であり、これはプロセス全体のサイズに影響を与えます。
最後に、JVM は起動を容易にするために rt.jar クラスを VM にマッピングするなどの楽しいことを行うことに注意してください。それらは読み取り専用ブロックにマップされ、他の Java プロセス間で共有できます。これらの共有ページは、すべての Java プロセスに対して「カウント」されますが、実際には物理メモリは 1 回しか消費されません (仮想メモリの魔法)。
プロセスが成長し続ける理由については、Java OOM メッセージが表示されない場合、それはリークが Java ヒープにないことを意味しますが、他の何か (JRE ランタイム、サードパーティの JNI ライブラリ、ネイティブ JDBC ドライバーなど)。