Javaはどのように動くのか~図解でわかるJVMの仕組み

第1回JVMはどのようにメモリ空間を利用するのか

あのWebサービスもJVMを利用している

「Javaは大規模なエンタープライズシステムにしか使われない」

それが常識だと思っていませんか?

たしかに、これまでJava Virtual Machine(JVM)は、他の言語を実行すると遅く、Javaのプログラムを実行する環境にすぎないものでした。ところが、Java 7から実装されたInvokeDynamicにより、JVM上で、RubyやPHPなどさまざまなコンピュータ言語で記述されたプログラムをより高速に実行できるようになりました。

これにより、今までエンタープライズでJava言語で記述されたプログラムを実行するだけの環境であったJVMが、汎用的な実行環境になったと言えます。また、これまでJavaの実行環境として使用されていたノウハウが、他の言語で記述されたプログラムを実行する際にも利用できます。

最近では、TwitterがJVMをアプリケーションの実行環境として選択したことがニュースになりました。Facebookも「JVMを利用したシステムへ移行するのでは」噂になっています

しかし、良いことばかりではありません。今後JVMを利用する機会が増えると、JVMが予期せず止まってしまうトラブルに見舞われたり、性能問題が発生する現場に居合わせる機会も増えると考えられます。

そのような問題を解決したり、より優れたパフォーマンスを引き出すために大事なのが、JVMの仕組みを学ぶことです。

メモリ空間を分けて使用する

まずは、JVMがメモリ空間をどのように利用するかを見ていきましょう。

JVMは、OSから見るとプロセスの1つであり、他のプロセスとは違いがありません。JVMは、OSが保有する広大なメモリ空間の一部を譲り受けて動作します。

JVMは、OSから譲り受けたメモリ空間を、目的別におもに以下の3つに分けて使用します。

Javaヒープ
Javaのプログラム内で使用されるオブジェクトや配列が格納されるメモリ空間です。
Cヒープ
JVMがネイティブライブラリを実行する際に使用するメモリ空間です。
スレッドスタック
JVMが持つスレッドの情報を格納します。

このようにメモリ空間を役割ごとに分割することで、各メモリ空間の仕組みがシンプルになり、管理がかんたんになります。

図1 JVMのメモリ空間
図1 JVMのメモリ空間

なお、JVMには「Javaヒープ」「Cヒープ」という2つのヒープがありますが、一般的に「ヒープ」と言う場合はJavaヒープを示します。本連載でも、単に「ヒープ」という場合はJavaヒープを指すものとします。

Javaヒープは「建物」、Cヒープやスレッドスタックは「庭」

上記の3つのメモリ空間の関係を理解するには、メモリの広大なOSのメモリ空間を「地面⁠⁠、JVMが譲り受けるメモリ空間を「地面の上にある敷地」として考えるとわかりやすいと思います。

敷地には、建物と庭が付きものです。ざっくりと、敷地を「建物」「庭」の2つに分割し、以下のようにとらえてみてください。

建物
⇒JVMの種類や選択したGCの種類で間取りが変わるJavaヒープ
⇒Cヒープやスレッドスタックなど、Javaヒープ以外で使われるメモリ空間
図2 メモリ空間のとらえ方
図2 メモリ空間のとらえ方

また、JVMには、メモリ空間のほかに、アプリケーションや、後述するガベージコレクタ、ファイナライザを実行する「スレッド」と呼ばれるものがあります。スレッドはそれぞれ何かしらの役割があり、複数のスレッドが協調して1つの役割をこなすこともあります。スレッドは「敷地内に住んでいる人」としてイメージするといいでしょう。

図3 スレッドのとらえ方
図3 スレッドのとらえ方

オブジェクトの生成とヒープの関係

Javaにかぎらず、プログラムを動かす場合、ソースコードの中でデータを一時的に保存していく必要があります。そのために必要なのが、変数やオブジェクト、配列などを作成する作業です。

Javaでプログラムを記述していく中で、オブジェクトや配列をnew演算子で生成します。JVM上でこのnew演算子のコードを実行すると、ヒープ中にオブジェクトが生成されます。そしてヒープの一部をオブジェクトに割り当てることで、ヒープが使用されていきます。これはServletやEnterprise Java Bean(EJB)などのプログラムでも同じです。

なお、配列でもヒープを使用しますが、今後は「オブジェクト」という表記だけにします。

図4 オブジェクトの生成
図4 オブジェクトの生成

また、ヒープの空き容量を超えるサイズのオブジェクトを作成することはできません。もし、そのようなオブジェクトを作成してしまうと、割り当てるヒープ領域が足りないため、Out Of Memory Error(OOME)が引き起こされます。

図5 大きすぎるオブジェクトを生成すると
図5 大きすぎるオブジェクトを生成すると

エンタープライズで使用されている環境の多くでは、WebLogicServerやGlassFishサーバなどのアプリケーションサーバ(APサーバ)を使用しますが、それらのAPサーバの内部でもオブジェクトが作成され、ヒープに格納されます。

ヒープはどのように使われていくのか

ソースコードとともに、ヒープがどのように使われていくかを見てみましょう。

Javaでは、以下のようにnew演算子でオブジェクトを生成すると、ヒープの一部がオブジェクトに割り当てられ、ヒープを使用します。

new Object();

プログラム中で生成されたオブジェクトを使用するためには、⁠参照⁠⁠、つまりリンクが必要です。たとえば次の例では、新しく生成したObjectクラスのオブジェクトへのリンクが変数aに格納されます。

Object a = new Object();

オブジェクトへの参照は、以下のような場合に削除されます。

  • 変数をnullで上書きした場合(上記の例では、変数aにnullを代入)
  • 変数がスコープ外へ出て、使用できなくなった場合
図6 変数へnullを代入
図6 変数へnullを代入
図7 変数のスコープ外へ移動
図7 変数のスコープ外へ移動

しかし、どのような方法で参照が削除されても、オブジェクトはヒープに残ったままになります。つまり、そのままでは、不要なオブジェクトでヒープが占有されてしまうことになるのです。

では、どうすればその問題を解決できるのでしょうか?

その答えは次回で解説します。お楽しみに。

おすすめ記事

記事・ニュース一覧