ある程度プログラミングなどの知識があれば、Java仮想マシン (JVM)という言葉を耳にしたことがあると思います。この記事ではJavaを動作される時の実行基盤となるJVMについての解説やインストール、起動引数について解説します。
JVMとは
「JVM」はJava Virtual Machine(Java仮想マシン)の略で、Javaのプログラムを動作するのに必要なソフトウェアです。
マルチプラットフォーム(Windows、Linux、MaxOSなど)で動作するJavaは、JVMが各種OSの違いを吸収することにより、マルチプラットフォームでの実行を可能にしています。
Java仮想マシン (JVM)言いからには、何かの機器やOSをイメージしてしまいますが、そういったものではありません。
なぜJVMが必要なのか
Java以外のコンパイル言語、例えばC言語やC++書かれたプログラムは、ソースコードをコンパイルしてコンピューターが認識できる機械語に翻訳します。
この時、C言語やC++用のコンパイラは「Windows」であれば「Windows用の機械語」へ、「Linux」であれば「Linux用の機械語」へ、それぞれプラットフォームに合わせてコンパイルをします。
例えばCやC++で書いたプログラムでは、下記イメージの通り、コンパイラがコンピュータが直接理解できる命令(マシン語、などと呼ばれます)に変換し、OSによって直接実行されます。
このように、コンパイラがいずれかのプラットフォームに依存しているため、例えば、Windows用のC++コンパイラで生成した実行モジュールは、Windowsでしか動作しません。
もし、C++で作成したアプリを、マルチプラットフォームで動作させるプログラムであれば、開発環境もそのプラットフォームの数だけ必要になります。
それに対しJavaは、JVMがOSの違いを吸収してくれので、一度コンパイルしたモジュールは、どのプラットフォームに置いても動作させることが可能です。
次のイメージは、Javaのソースコードをコンパイルしてから、各プラットフォームで動作させるまで流れを図にしたものです。
まず、Javaのソースコードをコンパイルすると、.classファイルなどの「バイトコード」呼ばれる中間コードに変換します。中間コードはOSが実行できる機械語ではないため、そのままでは実行できません。
次に、作成したバイトコードを、各プラットフォームにインストールされたJVMが読み取り、プラットフォームに合わせた機械語に翻訳し、最後に機械語に翻訳された命令をOSが命令を実行します。
このように、1つのプログラムがOSに依存しないメリットJavaにはあります。
JVMはJava以外の言語でも使われている
JVM = Javaのために開発された環境という認識がある方もいると思いますが、JVM上で実行されプログラミング言語はJava以外にも多数存在し、これらのことをJVM言語と呼びます。
Kotlin
Android開発で注目を浴びているのが、Javaと100%の互換性をもつプログラミング言語の「Kotlin」です。Kotlinで書かれたコードは、Javaでおなじみのクラスファイル(.class)にコンパイルされJVM上で実行されます。
Kotlinは、Javaよりも簡潔で安全なコードを書くことができ、型推論やラムダ式、トレイトなどの特徴的な文法があります。
また、NULL安全が言語機能として取り入れられており、JavaでよくあるNULL例外(NullPointerException)が発生しない仕組みもあります。
Scala
Scalaは、オブジェクト指向と関数型言語の両方の特徴を兼ね備えたプログラミング言語で、Webアプリ開発やスマホアプリ開発で用いられています。
Javaと互換性があるプログラミング言語であるため、Javaのライブラリを全て使用することがが可能で、そのためJavaの豊富なライブラリを活用できるのもScalaの魅力です。
Scalaのフレームワークには「Play Framework」「Finatra」が有名で、Javaと異なるScalaの先進的な構文と、シンプルなフレームワークによりスターアップ企業などで多く採用されています。
日本国内での採用事例としては「SmartNews」「Chatwork」などがScalaを使ってサービスを構築しています。
JVMを監視する
Java、Kotlin、ScalaなどのJVM言語は、各プラットフォームにインストールされたJVM上で実行され、OSから見ると1つのプロセス(java.exe)にすぎません。
JVMは、起動される際に指定されたメモリ量をOSが保有する広大なメモリ空間から譲り受けてヒープ・メモリとして管理します。この、ヒープ・メモリを超える量のメモリが必要な処理や、開放し忘れたメモリが溜まり続けるとメモリ不足によりOutOfMemoryErrorが発生し、アプリが異常終了してしまいます。
JVMを起動する際は、適切なメモリをJVMに割り当てることや、テスト時にメモリの使用量をjconsoleなどのツールを用いて監視し、リソースの開放漏れがないかなどをチェックするようにしましょう。
ヒープ・メモリの構造
まずは、JVMにおけるヒープ・メモリの構造について理解しておきましょう。
ヒープ領域は大きく「New」と「Old」領域に分けられ、ヒープ領域ではありませんが、これに深く関わる「Permanent」領域が存在します。
【New領域】
New領域には、新規に作成されたオブジェクトや、寿命が短いオブジェクトが格納されます。
New領域をさらに細かく分けると「Eden」と「Survivor」領域があり、「Eden」には新規に作成されたオブジェクトが格納され、「Survivor」には「Eden」に格納されてから時間が経ったオブジェクトが格納されます。
New領域で、どこからも参照がなくなり不要となったJavaオブジェクトはGCにより回収されます。
【Old領域】
Old領域には寿命が長いオブジェクトや、一定期間New領域に存在するオブジェクトは、GC処理によってOld世代領域に移動されます。
そして、Old領域に存在し不要になったオブジェクトは、FullGC処理によって回収(開放)されます。このFullGCは非常に重たい処理であり、実行中はJavaの処理が止まってしまうため、FullGCが発生しないようなアプリケーションを作るのが、高速なアプリケーションを作るためのポイントです。
【Permanent世代領域】
Permanent領域にあ、クラスローダーによって「jar」ファイルなどから読み込まれたクラスやメソッドなど定義(メタデータ)を、永続的に管理する領域です。
jcosoleとは
jconsoleは、JVMで稼働するアプリケーションの動作状況を監視できるツールです。jconsoleはJDKに同梱されているため、JDKをインストールしている端末であれば、改めてツールをインストールする必要がありません。
jconsoleは、JDKインストール先\binの中に実行ファイルが格納されており、これをダブルクリックして起動するか、環境変数にJDKのインストール先パスが登録されていれば、コンソール(Windowsの場合はコマンドプロンプト)でjconosoleと入力することでも起動できます。
jconsoleを起動すると、接続画面が表示されローカルまたはリモートプロセスのどちらを監視するかを選択し「接続」ボタンをクリックします。
接続が完了すると次のような画面が表示され、メモリ、スレッド、クラスなどのリソース使用状況が監視できます。
まとめ
Java仮想マシン (JVM)の概要や、ヒープ・メモリの構造や監視の必要性について解説してきました。開発用のPCで動かしている分には、あまりJVMのヒープ・メモリの設定などについて気を配らなくてもよいですが、本番サーバーでの運用ともなると、JVMのヒープ・メモリの設定を適切にする必要があるため注意しておきましょう。
【関連記事】
▶MacでJava開発をする!環境の構築方法を解説
リモートプロセスを監視する場合は、事前にリモートサーバー側に監視用のエージェントをインストールしておく必要があります。