
チューニングのためのJavaVM講座(前編)
Hotspot VMの基本構造を理解する
日本HP コンサルティング・インテグレーション統括本部
ITコンサルティング本部 テクノロジーコンサルティング部
亀井政利
2004/3/11
J2EEがミッションクリティカルな分野に適用されるようになり、Javaのパフォーマンスチューニングの重要性はさらに高まっています。パフォーマンスチューニングにはさまざまなパラメータがありますが、中でもJava VMに関連するチューニングの効果は大きいといわれています。本稿は、Java VMに関連するチューニング手法を学ぶための前提知識を提供することを目的にしています(編集部)。
| 本記事は2004年に執筆されたものです。Javaチューニング全般の最新情報は@IT キーワードINDEXの「Javaパフォーマンス管理」をご参照ください。 |
Java VMに関連するチューニングを行い、J2EEアプリケーションのパフォーマンスを上げるためには、Java VMについて詳しく知る必要があります。本稿は2回に渡ってJava
VMの基本構造と動作原理を詳細に解説しますが、内容を理解するためにはプログラムがコンピュータ上で動作する基本原理とJava VMの基本用語を知っている必要があります。Java
VMの基本用語に関しては、「実行スピードに挑戦するJavaアーキテクチャの変遷をたどる」(Java
Solution)を参考にしてください。また、Itaniumアーキテクチャについては、「明らかになるItanium
2の性能とプラットフォーム」(System Insider)を始めとするSystem
Insiderフォーラム内の関連記事を参考にしてください。本連載の終了後には、Java VMに関連するチューニングテクニックを解説する「Java
VMパフォーマンステクニック」(仮題)の連載が予定されています。
| JavaプログラムはJava VM環境上で動作する |
「Write Once, Run Everywhere」という言葉に象徴されるように、Java言語で書いてJavaコンパイラで作成したJavaプログラム(クラスファイル)は、Java VMが存在する環境であればその下のOSやハードウェアが何であろうと、同じ動作をします。これはJava VMがその下位にあるOSやハードウェアの違いを吸収し、その上位にあるJavaプログラムと共通のインターフェイスを提供していることで実現されています。つまり、どんな環境の下でも、Javaプログラムから見ればJava VMという仮想ハードウェア上で動作するように見えるわけです。
![]() |
| 図1 Javaプログラムに共通の環境を提供するJava VM |
| Javaコンパイラがバイトコードを生成 |
Java言語で書かれたソースコードファイルをJavaコンパイラ(javac)でコンパイルすると、Javaバイトコードを含むクラスファイル(.class)が生成されます(図2参照)。Javaコンパイラは、サン・マイクロシステムズのJDK付属のもの以外にも、Java開発環境を販売するサードパーティーのソフトウェアベンダなどからも提供されています。
![]() |
| 図2 JavaソースファイルからJavaクラスファイルを生成 |
JavaコンパイラによるJavaソースコードのコンパイルと、C/C++コンパイラによるC/C++ソースコードのコンパイルの対比を図3に示します。これを見ると、Javaが当初インタプリタ方式といわれたとはいえ、開発言語としてはPerlなどよりはC/C++に近いものであることが分かります。ただし、C/C++などの言語で作成したソースコードはOSに依存しますし、C/C++コンパイラは特定のプロセッサ専用のコードを生成します。結果として、C/C++コンパイラで生成した実行ファイルはハードウェア(プロセッサ)とソフトウェア(オペレーティングシステム)の両方に依存することになります。それに対してJavaは、特定のハードウェアやソフトウェアへの依存しないコードを生成します。
![]() |
| 図3 JavaコンパイラとC/C++コンパイラ |
| バイトコードとJava VMの関係 |
Javaクラスファイルの中にはそのクラスで定義されているメソッド、フィールド変数、定数などの情報が書かれており、メソッド中にはバイトコードが書き込まれています(図2参照)。バイトコードはその名のとおりオペコードが1バイト固定長で、オペランドは可変長です。
ところで、図4に示すように、Java VM上で実行されるバイトコードは、実プロセッサ上で実行されるマシンコードに対応するものと考えることができます。すなわち、バイトコードにとってJava VMは実プロセッサに該当するものであることから、仮想の実プロセッサという意味でJava仮想マシン(Java Virtual Machine)と呼ばれるわけです。
Java VMはソフトウェアで実装されるため、さまざまなプラットフォームへの移植が可能であり、その結果、Javaバイトコード(つまりJavaプログラム)をさまざまなハードウェア/ソフトウェアプラットフォームで実行させることができます。
![]() |
| 図4 マシンコードとバイトコードの関係は実プロセッサとJava VMの関係に対比できる |
| Java VMは誰が実装しているのか |
Java VMの実装はサン・マイクロシステムズが提供しています。しかし、サン・マイクロシステムズからソースコードライセンスを受けなくても、Java仮想マシン仕様(Java Virtual Machine Specification)を満たせば独自にJava VMを作ることは可能なため、各社が独自のJava VMを作っています。
例えば、IBMのJava VMやBEAのJRockitは独自のJava VMの例です。また、HPのようにサン・マイクロシステムズからソースコードライセンスを受けている場合、プロセッサやOSに依存する部分の実装や、各種パラメータの設定は独自に行っていますが、それ以外の基本部分はサン・マイクロシステムズのコードをそのまま使っています。すなわち、HPのJava VMはサン・マイクロシステムズが提供するJava VMと同一のものということです。現在のJava VMはパフォーマンスを改善したHotSpot VMなので、HPのJava VMはサンのHotSpot VMと基本部分のソースコードは同一です。
| Java VMによるJavaプログラムの実行 |
Java VMは特定のOSとハードウェア上に作られた仮想マシンであり、ソフトウェアにより実装されています。いい換えるとJava VMはJavaの実行環境を提供するソフトウェアエミュレータです。ほかのソフトウェアエミュレータの例としては、Itaniumマシン上のHP-UXでPA-RISC/HP-UXの実行環境を提供するAriesがあります。PA-RISC/HP-UXのアプリケーションをItanium/HP-UXシステム上で実行できるのは、Ariesのおかげです。
Java VM上で動作するJavaプログラムを1つのプロセスとして見ると、Java VMというプログラムが動作しながら、Javaプログラムが同じプロセス内で動作していることになります。Java
VMの仮想メモリ上でのイメージは図5のようになります。Javaプログラムを動作させるにはJavaヒープとJava VMスタックが必要になります。Javaヒープにはクラスファイルを展開している領域とJavaクラスのインスタンスが置かれる領域があります。これに対し、Java
VM自身もプログラムなので実行テキストに加えてCヒープとスタックを持ちます。命令ポインタもバイトコードの命令を指すものとネイティブコードを指すものが存在します。Javaプログラムを実行しているスレッドでは、プロセッサの命令ポインタは主にインタプリタ内かコードキャッシュ内を指しています。
![]() |
| 図5 Javaプログラム実行中のJava VMのプロセスメモリ空間(概略) |
図5の構造を理解しておけばJavaにおける問題発生時の対応に役立ちます。例えば「メモリ不足」という状況になった場合、Cヒープが足りない、Javaプログラムのヒープが足りない、スタックが足りない、OSのスワップが足りないなどさまざまな原因が考えられます。それぞれの領域の使用目的やこれらをどういったオプションやパラメータで設定できるか、また実際のJava VMがOSのリソースをどのように使用しているかなどを理解しておく必要があります。
| Javaヒープとは |
クラスファイルはJava VMのクラスローダによりファイルからメモリへとロードされます。HotSpot VMでは、「-verbose:class」を付けて起動するとロードされたクラスを表示してくれます。図6に示すように、HotSpot VMではクラスの静的情報はJavaヒープのPermanent Generation領域に配置されます。静的情報には、メソッド定義(バイトコード)、フィールド定義、定数、および static 宣言されているフィールドの値などが含まれます。Javaにはクラスを動的にロードする機能があるため、Java VMの起動時以外でもクラスをロードすることができます。JSPはこの仕組みを利用しているため、JSPを動作させているJava VMのPermanent Generation領域の使用量は動的に増えます。クラスをインスタンス化すると、生成されたオブジェクトはJavaヒープのNew/Old Generation領域に置かれます。Javaヒープはどちらの領域もガベージコレクションの対象になります。Javaヒープ内部の詳細は次回のガベージコレクションの回で説明します。
![]() |
| 図6 クラスファイルのメモリへのロード |
| Java VMスタックとは |
Javaに限らず、一般に関数が1つ呼ばれるたびにスタック上にスタックフレームが1つ積まれます。このスタックフレームの中にはローカル変数と次に呼び出す関数に渡す引数の一部が置かれます。Javaにもスタックがあり、Javaバイトコードインタプリタが利用します。Java VMスタックのスタックフレーム内には、ローカル変数と引数に加えてオペランドスタックが置かれます。オペランドスタックは、Java VMがスタックマシンであるために必要なものです。下記のJavaバイトコードはオペランドスタックを利用した演算の例です。これは、0番目と1番目のローカル変数を、(このフレーム内の)オペランドスタック領域に積み、この2つの値を加算します。
iload_0 |
HotSpot VMでは、Java VMスタックとネイティブコードのスタックは同じ領域に連続して存在します。つまりJavaプログラムのフレームとネイティブコードのフレームが1つのスタック内に混在することになります。リスト1はJavaプログラムを実行しているJavaスレッドのスタックトレースをHPのgdbで表示させたものです。
フレーム#14から#0に向かって順番に関数が呼び出されていることを示しています。スレッド生成後にいくつかの処理を行い、途中からJavaアプリケーションを実行しているのが分かります。ここで、#14から#8はJava
VM自身のフレームで、#7から#0がJavaプログラムのフレームです。
| リスト1 Javaプログラム実行中のスタックトレースの例 |
$ gdb /opt/java1.4/bin/IA64N/java -p 4429 |
![]() |
| 図7 リスト1の各フレームで行われている処理 |
#7にあるJavaのMyLoopクラスのmain()メソッドはinterpreted frameと表示されており、main()メソッドがインタプリタで実行していることが分かります。
#5のi2c_adapterは、インタプリタで実行されている関数からコンパイルされている関数を呼び出す際に呼ばれるアダプタ関数で、表示されてはいませんが、c2i_adapterはコンパイルされた関数からインタプリタで実行される関数を呼び出すアダプタ関数です。これらアダプタは主に呼び出す関数の引数の順番を変更します。#6も#5と同様、#4を呼び出すためのスタブ的な役割をします。
#4はcompiled frameと表示されており、JavaのMyLoopクラスのmethod1()メソッドとmethod2()メソッドのコンパイルされたネイティブコードが実行されています。2つのメソッドが1つのフレームに表示されているのはmethod2()がmethod1()にインライン展開されていることを示しています。#3も同様にコンパイルされたメソッドのフレームです。
#2はネイティブメソッドを呼び出すためのスタブで、#1と#0ではネイティブメソッドを実行しています。
| JavaスレッドとJava VMスタック |
初期のJava VMではJava VM内部でスレッド機能をユーザーレベルスレッドとして提供(green_thread)するのがありましたが、HotSpot VMでは、1つのJavaスレッドはそのJava VMの下で動作するOSのスレッドに対応します(native_thread)。UNIXであれば、Javaプログラム実行中にSIGQUITを送って、Windowsであればターミナル上でctrl-breakキーによりスレッドのダンプを表示させることができます。
スタックはスレッドごとに存在します。これらのスレッドは2つのグループに分けることができます。1つはJavaスレッドで、もう1つはJavaシステムスレッドです(図8参照)。JavaスレッドはJavaのアプリケーションを実行しているスレッドですが、JavaシステムスレッドはJavaの実行環境を提供します。インタプリタで実行した関数のスタックフレームのみは、ネイティブコード用に決められているコーリングコンベンション(関数呼び出し規則)と異なるフレームフォーマットになります。リスト1で示したJavaスレッドのスタックトレース例は、図8のstack1に当たります。Javaスレッドのスタックのサイズは、-Xssオプションで指定することができます。このサイズを超えてスタックを伸ばすとJavaの StackOverflowException が通知されます。
![]() |
| 図8 JavaスレッドとJavaシステムスレッドのスタック |
Javaシステムスレッドには、ThreadHelper、VMThread、ReferenceHandler、Finalizer、WatcherThread、SuspendCheckerThread、SignalDispatcher、CompilerThreadなどがあります。この中でもReferenceHandlerとFinalizerはJavaで実装されており、図8のstack3に当たります。
| 1/2 |
|
INDEX |
||
| チューニングのためのJavaVM講座(前編) | ||
| Page1 JavaプログラムはJava VM環境上で動作する Javaコンパイラがバイトコードを生成 バイトコードとJava VMの関係 Java VMは誰が実装しているのか Java VMによるJavaプログラムの実行 Javaヒープとは Java VMスタックとは JavaスレッドとJava VMスタック |
||
| Page2 Javaにおけるコンパイルとは バイトコードの解釈と実行 バイトコードインタプリタが行う処理 動的コンパイラとは 動的にコンパイルされたネイティブコードの例 動的コンパイル関連のオプション 今後のJava VM |
||
| チューニングのためのJava VM講座 |
TechTargetジャパン
- 並列分散処理の常識をHadoopファミリから学ぶ (2012/2/8)
並列分散処理の課題やHadoopの長所/短所、そして短所を補うHadoop関連プロジェクトの構成や概要などを簡単に紹介 - WebLogicサーバ最新版「12c」の気になる4つの特徴 (2012/1/31)
久々にメジャーアップグレードしたJavaアプリケーションサーバについて、製品担当者に軽量インストーラなどの特徴を聞いた - GitHubをもっとソーシャルに使いこなすための7つ道具 (2012/1/23)
ソースコードホスティングのGitHub周辺で便利な新サービスが続々登場しているので、まとめて紹介しよう。特に連動クラウド「fluxflex」が注目だ - 新キャラ登場!スクラムやるならRedmineとALMinium (2011/12/26)
「黒板を“かんばん”にしてたら先生に怒られた(T_T)」「管理はPC内でやればいいのよ」「承知しました」
|
|
キャリアアップ
スポンサーからのお知らせ
- - PR -
イベントカレンダー
- - PR -








