クラスの共有によるパフォーマンスの向上

IBM JRE に追加された最新のクラス共有機能の詳細を探る

Java™ SE 6 対応 IBM® JRE の最新リリースでは、バージョン 5 で導入されたクラスを共有する機能が強化されています。この記事ではパフォーマンス・アナリストである Adam Pilkington と Graham Rawson が、アプリケーションの起動時間とメモリー使用量における改善をはじめ、IBM JRE での変更内容について詳しく解説します。

Adam Pilkington, Software Engineer, WSO2 Inc

Photo of Adam PilkingtonAdam Pilkington は IBM Java Technology Centre に勤務する Java パフォーマンス・アナリストです。現在は Java 6 での WebSphere Application Server パフォーマンスを専門に取り組んでいます。2006年に IBM に入社する前は、英国の大手金融サービス組織で J2EE テクニカル・アーキテクトとして働いていました。彼は数学およびコンピューター・サイエンスの学位を持っています。


developerWorks 貢献著者レベル

Graham Rawson, Software Engineer, WSO2 Inc

Graham RawsonGraham Rawson は IBM Java Technology Centre の Java パフォーマンス・アナリストで、英国ハーズリーにある IBM Laboratory を拠点とする Java Performance チームのリーダーを務めています。IBM ではこれまで 24 年間、ハーズリーで開発されたCICS Transaction Server および Java 技術をサポートするさまざまな役割を果たしてきました。彼は University of East Anglia (英国ノリッジ) で化学の理学士号 (優等学位) を取得し、University of Oxford (英国) でソフトウェア・エンジニアとしての資格を取得しました。



2008年 9月 30日

Java プラットフォーム SE 対応 IBM JRE のバージョン 5 で初めて導入された共有クラス・インフラストラクチャーが強化されたことにより、起動時間およびメモリー・フットプリントの領域での Java アプリケーションのパフォーマンスが改善されています。この記事では最新リリースでの変更内容を詳しく調べ、その利点を、クライアント・サイドとサーバー・サイドのオペレーティング環境として Eclipse と Apache Tomcat を例に用いて明らかにします。ここでは読者の皆さんが自分で試すことができるようにインストール手順も紹介しますが、前提として、対象読者は Eclipse と Apache Tomcat について、そして IBM のクラス共有について十分な知識を持っている必要があります。IBM の共有クラス機能に馴染みのない方には、まずは関連する基本概念を説明している記事、「Java Technology, IBM style: Class sharing」を読むことをお勧めします。

Linux® および AIX® をお使いの場合、Java 6 対応 IBM JRE の実装をダウンロードすれば、すぐにここに記載するサンプルに従うことができます。Windows® 向け実装は、単独でのダウンロードは用意されていませんが、事前ビルド済みの Eclipse ダウンロードが用意されています。ダウンロードするには、IBM への登録 (無料) が必要となります。

IBM の新たな共有クラス機能

Java 5 対応 IBM JRE では、キャッシュによって JVM 間でのクラス共有を可能にしました。Java 6 対応 IBM JRE では、このキャッシュを永続化し、コンパイル済みコードを共有するために利用できるようになっています。さらに、キャッシュのなかで項目を保管する方法も効率化されています。

共有クラス

Java 5 対応 IBM JRE で初めて導入された Java 仮想マシン (JVM) 間でのクラス共有機能は、Java 6 対応 IBM JRE でも引き続きサポートされ、強化されています。JVM がクラスをロードすると、クラスはキャッシュに入れられます。その後、該当するクラスが要求されたときには、可能であれば対応する JAR ファイルからクラスをロードする代わりに、キャッシュ内のクラスがその要求を満たすことになります。

キャッシュの最大サイズは、リスト 1 に示すコマンドライン・オプションを使って制御することができます。ただし、キャッシュの最大サイズはオペレーティング・システムの共有メモリーに関する制約に依存することに注意してください。

リスト 1. コマンドライン・オプションによる最大キャッシュ・サイズの設定
Running java -X will show the following option ...

Arguments to the following options are expressed in bytes.
Values suffixed with "k" (kilo) or "m" (mega) will be factored accordingly.
:
-Xscmx<x>       set size of new shared class cache to <x>
:

AOT (Ahead of Time) によるコードの保管

JVM は通常、プログラムの実行中に Java メソッドをネイティブ・コードにコンパイルします。このネイティブ・コードはプログラムが実行されるたびに生成されます。そこで、Java 6 対応 IBM JRE の SR1 JVM では、Java メソッドを AOT (Ahead of Time) コンパイル技術でコンパイルし、現行の JVM で使用するだけでなく、共有クラス・キャッシュに保管できるネイティブ・コードの作成機能を導入しました。Java プログラムが起動されると共有クラス・キャッシュに AOT コードが取り込まれます。この共有クラス・キャッシュを使用して起動された別の JVM はキャッシュに保管された AOT コードを使用することができるため、起動時間が短縮されます。この時間短縮は、コンパイルに必要な時間が節約されること、そしてメソッドを AOT コードとして実行すると短時間で処理できることによるものです。AOT コードはネイティブ・コードであるため、一般には解釈されたコードよりも実行に要する時間が短くなります (ただし、JIT で生成されたコードほど実行時間が短縮される可能性はありません)。

共有クラス・キャッシュで AOT コードに許可する最小占有量と最大占有量は、コマンドライン・オプションを使用して定義することができます (リスト 2 を参照)。保管可能な AOT コードの最大量を指定しない場合、そのデフォルト設定はキャッシュ全体になりますが、それによってキャッシュが AOT コードでいっぱいになることはありません。AOT コードはキャッシュ内の既存のクラスからしか生成できないためです。

リスト 2. コマンドライン・オプションによるキャッシュ内での AOT コードのサイズ制御
Running java -X will show the following options ...

Arguments to the following options are expressed in bytes.
Values suffixed with "k" (kilo) or "m" (mega) will be factored accordingly.
:
-Xscminaot<x>   set minimum shared classes cache space reserved for AOT data to <x>
-Xscmaxaot<x>   set maximum shared classes cache space allowed for AOT data to <x>

図 1 に、キャッシュ・スペースが共有クラスと AOT コードによって占有されていく様子、そしてキャッシュ・スペースの設定によって共有クラスと AOT コードに使用可能なスペースのうち共用部分のサイズがどのように制御されるかを図解します。

図 1. 共有クラス・キャッシュの配分例
共有クラス・キャッシュの配分例

AOT コードの詳細については、この後すぐに説明します。

クラス圧縮

共有クラス・キャッシュを最大限有効に使用するため、JVM は圧縮技術を用いて保管可能なクラスの数を増やします。クラス圧縮は自動的に行われ、コマンドライン・オプションを使用して変更することはできません。

永続キャッシュ

Java 5 対応 IBM JRE での共有クラス・キャッシュは、複数の JVM が同じキャッシュを共有できるように共有メモリー・セグメントを使用して実装されていましたが、このキャッシュが存続されるのはオペレーティング・システムがリブートされるまででした。つまり、リブート後に最初に起動された JVM は、キャッシュをビルドし直さなければならないということです。Java 6では、キャッシュのデフォルト実装が変更され、メモリー・マップト・ファイルを使用するようになっています。この変更により、オペレーティング・システムが再起動してもキャッシュは永続するようになっています。

AOT の詳細

AOT コンパイラーは、Java 6 対応 IBM JRE で初めて追加されたコンパイル・メカニズムです。IBM JRE の前のバージョンでは、Java メソッドを実行する方法が 2 つありました。1 つは該当するメソッドを構成するそれぞれの Java バイトコードを解釈するという方法、そしてもう 1 つは、ネイティブ・マシン・コードとして実行するために JIT (Just-in-Time) コンパイラーという JRE コンポーネ ントを使用してコンパイルし、最適化する方法です。JIT はコードを動的にコンパイルします。コンパイル・プロセスはメソッドが実際に実行されるときに行われるため、どのコンパイル技術を用いるかは実際のメソッドの分析結果に応じて実行中に決定されます。

AOT コードとは何か

AOT コードとは、AOT コンパイルによって生成される Java メソッドのネイティブなコード・バージョンのことです。JIT コンパイルとは異なり、AOT コンパイルは実行中の Java メソッドの動的な分析結果に応じた最適化を採用していません。一般に、メソッドは AOT でコンパイルしたネイティブ・コードとして実行したほうが、解釈された Java バイトコードとして実行するよりも時間がかかりませんが、JIT でコンパイルしたネイティブ・コードほど高速になることはありません。

AOT コンパイルの第一の目的は、プリコンパイルしたバージョンの Java メソッドを提供することによって、アプリケーションの起動に要する時間を短縮することです。実行に使用可能なネイティブ・コード・バージョンの Java メソッドを作成するには、JIT でコンパイルしたコードを生成するよりも、プリコンパイルされた AOT メソッドを共有クラス・キャッシュからロードしたほうが遥かに時間を短縮できます。AOT でコンパイルされたコードを素早くロードすることにより、JVM が Java メソッドを解釈してネイティブ・コード・バージョンを使用できるようにするまでの時間は短くなります。AOT でコンパイルされたメソッドは JIT コンパイルの対象にもなるため、AOT コードとして最初に実行された後、JIT によってさらに最適化される場合もあります。

共有クラスの一部としての AOT

生成された AOT コードは共有キャッシュの 1 つの領域に保管されます。それ以降は、この共有クラス・キャッシュを使用するその他すべての JVM がこのメソッドを AOT コードとして実行できるため、コンパイルのコストは発生しません。

この実装は、リアルタイム JVM とは異なります。「リアルタイム Java、第 1 回: リアルタイム・システムに Java 言語を使用する」で説明しているように、リアルタイム JVM での AOT コードのコンパイルは、ユーティリティー (jxeinajar) によって実行され、その保管先は jar ファイルとなります。

JVM が実行する AOT コードは共有されるのではなく、共有クラス・キャッシュからコピーされます。各 JVM が AOT 実行可能ファイルのコピーを持つことには変わりないため、フットプリントに関する直接的なメリットはありません。しかし、コンパイルを繰り返すことなくコードを再利用できることから、メモリーと CPU を節約できることになります。

AOT 診断

アプリケーションがどのメソッドを AOT でコンパイルしたのか、そして共有クラス・キャッシュ内でこれらのメソッドが占めるスペースを把握するには、以下の 3 つのコマンドライン設定を使用することができます。

  • -Xjit:verbose: JIT によって実行された AOT コンパイルをレポートするためのコマンドライン・オプションです。
  • -Xshareclasses:verboseAOT: 共有クラス・キャッシュに対して読み取り、保管が行われた AOT コードをレポートするためのコマンドライン・オプションです。
  • -Xshareclasses:printAllStats: 保管されている AOT コードや占有スペースを含め、共有クラス・キャッシュの統計値を一覧にして表示するためのコマンドライン・オプションです。

共有クラス・キャッシュをクリアしてランタイム・オプション、-Xjit:verbose および -Xshareclasses:verboseAOT を適用した後に Tomcat サーバーを初めて呼び出すと、出力はリスト 3 のようになります。

リスト 3. -Xjit:verbose および -Xshareclasses:verboseAOT の適用
+ (AOT cold) java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder; 
Storing AOT code for ROMMethod 0x02359850 in shared cache... Succeeded.
+ (AOT cold) sun/misc/URLClassPath$JarLoader.ensureOpen()V @ 0x0147BF9C-0x0147C106 Q_SZ=3
Storing AOT code for ROMMethod 0x023CBFC4 in shared cache... Succeeded.
+ (AOT cold) java/util/jar/JarFile.getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry; 
Storing AOT code for ROMMethod 0x023CE38C in shared cache... Succeeded.

Tomcat サーバーを初期化した後にコマンド java -Xshareclasses:printAllStats で取得した共有クラス・キャッシュの統計には、共有クラス・キャッシュに保管済みのメソッドが示されます (リスト 4 は出力からの抜粋です)。

リスト 4. java -Xshareclasses:printAllStats による共有クラス・キャッシュに保管されたメソッドの表示
1: 0x43469B8C AOT: append
	for ROMClass java/lang/StringBuilder at 0x42539178.
1: 0x43469634 AOT: ensureOpen
	for ROMClass sun/misc/URLClassPath$JarLoader at 0x425AB758.
1: 0x434693A8 AOT: getEntry
	for ROMClass java/util/jar/JarFile at 0x425ADAD8.

リスト 5 に示されているように、その後、共有クラス・キャッシュを使用して Tomcat サーバーを起動すると、AOT でコンパイルされたメソッドが検出され、メソッドのコンパイルが繰り返されることなく、コンパイル済みのメソッドがキャッシュからそのままロードされることになります。

リスト 5. AOT でコンパイルされたメソッドの検出とロード
Finding AOT code for ROMMethod 0x02359850 in shared cache... Succeeded.
(AOT load) java/lang/StringBuilder.append(Ljava/lang/String;)Ljava/lang/StringBuilder;
Finding AOT code for ROMMethod 0x023CBFC4 in shared cache... Succeeded.
(AOT load) sun/misc/URLClassPath$JarLoader.ensureOpen()V
Finding AOT code for ROMMethod 0x023CE38C in shared cache... Succeeded.
(AOT load) java/util/jar/JarFile.getEntry(Ljava/lang/String;)Ljava/util/zip/ZipEntry;

AOT コンパイルはヒューリスティック判定を行って、起動に要する時間をさらに短縮するメソッドの候補を選択します。したがって、アプリケーションの起動が繰り返されるにつれ、他のメソッドも AOT でコンパイルされる可能性があります。

AOT でコンパイル済みのメソッドが再コンパイルに必要な基準を満たす場合、そのメソッドはさらに JIT でもコンパイルされることになります。ただし、AOT コンパイルはアプリケーションの起動時に必要なメソッドを選択することを目的とする一方、JIT コンパイルの目的は頻繁に使用されるメソッドを最適化することです。そのため、AOT でコンパイルされたメソッドが頻繁には呼び出されず、JIT コンパイルをトリガーするまでには至らない場合も考えられます。

リスト 6 は、-Xjit:verbose を使用して SPECjbb2005 ベンチマークを実行した場合の出力からの抜粋です。この抜粋には、com/ibm/security/util/ObjectIdentifier.equalsjava/math/BigDecimal.multiply という 2 つのメソッドの AOT コンパイルがレポートされています。前者のメソッドでは JIT コンパイルが行われていませんが、それよりも使用頻度が高い java/math/BigDecimal.multiply メソッドは 2 度、JIT でコンパイルされ、最終的な最適化レベルは hot となっています。

SPECjbb2005 の起動フェーズはそれほど長くないため、コンパイルされる AOT メソッドは少数しかありません。AOT コンパイルは、AOT の総体的な目的であるアプリケーションの起動に要する時間の短縮を反映した最適化レベル、cold で実行されることに注意してください。

リスト 6. -Xjit:verbose によってレポートされた最適化
+ (AOT cold) com/ibm/security/util/ObjectIdentifier.equals(Ljava/lang/Object;)
Storing AOT code for ROMMethod 0x118B8AF4 in shared cache... Succeeded.

+ (AOT cold) java/math/BigDecimal.multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal; 
Storing AOT code for ROMMethod 0x119D3C60 in shared cache... Succeeded.
+ (warm) java/math/BigDecimal.multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal; 
+ (hot) java/math/BigDecimal.multiply(Ljava/math/BigDecimal;)Ljava/math/BigDecimal;

コマンド java -Xshareclasses:printAllStats による共有クラスの統計には、AOT でコンパイルされた各メソッドとキャッシュに入れられた各共有クラスが一覧表示されます。この要約データから、共有クラス・キャッシュのサイズが正しく設定されているかどうかを理解することができます。例えば、リスト 7 にはキャッシュの合計サイズ 16776844 バイトのうち 40 パーセントが使用されていること、そしてその内訳として、5950936 バイトは 1668 の ROMClass により使用され、683772 バイトは AOT でコンパイルされた 458 のメソッドにより使用されていることが示されています。

リスト 7. キャッシュ詳細
base address       = 0x424DE000
end address        = 0x434D0000
allocation pointer = 0x4295E748

cache size         = 16776844
free bytes         = 9971656
ROMClass bytes = 5950936 AOT bytes = 683772 
Data bytes         = 57428
Metadata bytes     = 113052
Metadata % used    = 1%

# ROMClasses = 1668# AOT Methods = 458
# Classpaths       = 7
# URLs             = 0
# Tokens           = 0
# Stale classes    = 0
% Stale classes    = 0%

Cache is 40% full

リスト 8 は、コマンドライン・オプション -Xshareclasses:destroyAll によって生成された出力です。ここには、破棄されたキャッシュが示されています。このコマンドライン・オプションによる実行結果は Could not create the Java virtual machine (Java 仮想マシンを作成できませんでした) というメッセージになっていますが、心配する必要はありません。このメッセージはこのコマンドライン・オプションに付き物だからです。

リスト 8. キャッシュの破棄
Attempting to destroy all caches in cacheDir C:\...\javasharedresources\ 
 
JVMSHRC256I Persistent shared cache "eclipse" has been destroyed 
Could not create the Java virtual machine.

メモリー使用量の測定

メモリー使用量という点で共有クラスがもたらすメリットを確認するには、さまざまなパフォーマンス・ツールを使用できます。どの特定のツールを使用するかは、基礎となるオペレーティング・システムによって決まります。メモリー使用量を調べる際に注意しなければならないのは、キャッシュはメモリー・マップト・ファイルによって提供され、そのコンテンツは複数の仮想マシンで共有できるという点です。そのため、メモリー使用量を判断するために使用するツールは、共有メモリー (複数の JVM がアクセスして共有できるメモリー) と専用メモリー (単一の JVM のみがアクセスできるメモリー) とを区別できるものでなければなりません。

Virtual Address Dump ユーティリティー (Windows)

アプリケーション (この場合は共有クラス・キャッシュ) のメモリー使用量に関する情報を表示するには、Microsoft® リソース・キットに含まれる Virtual Address Dump ユーティリティー (vadump) を使用できます。vadump は大量の情報を生成しますが、今回調べるのは、ワーキング・セットのサイズに関してレポートされる数値だけです。この数値から、アプリケーションのメモリー使用量を判断することができます。コマンド vadump -os -p <pid> を実行すると、特定のプロセス ID についてのワーキング・セットが表示されます。

生成される要約情報には、プロセスが使用するメモリーに関する大量の情報が含まれます。共有クラスの使用によってメモリー使用量が改善されることを明らかにするために私たちが注目したのは、Grand Total Working Set (ワーキング・セット合計) の数値、そしてこの合計の内訳である Private (専用)、Shareable (共有可能)、Shared (共有済) の各数値がクラス・データの共有によってどのように影響されるかです。リスト 9 に、vadump による要約出力の一例を記載します。共有クラスはメモリー・マップト・ファイルとして実装されているため、これらの共有クラスが占有するメモリーは Mapped Data (マップト・データ) 行に出力されます。

リスト 9. vadump の出力例
vadump -os -p 5364
Category                        Total        Private Shareable    Shared
                           Pages    KBytes    KBytes    KBytes    KBytes
      Page Table Pages        29       116       116         0         0
      Other System             8        32        32         0         0
      Code/StaticData       2079      8316      5328       140      2848
      Heap                    87       348       348         0         0
      Stack                    4        16        16         0         0
      Teb                      1         4         4         0         0
      Mapped Data             95       380         0        24     356
      Other Data              61       244       240         4         0

      Total Modules         2079      8316      5328       140      2848
      Total Dynamic Data     248       992       608        28       356
      Total System            37       148       148         0         0
Grand Total Working Set     2364      9456      6084       168      3204

vadump コマンドに設定するプロセス ID を判断するには、以下に示すように Windows タスク マネージャを使用することができます。

  1. タスク マネージャを開き、プロセス・タブを選択します。
  2. PID という名前の表示列を見つけます (この列が表示されていない場合は、表示 > 列の選択をクリックし、図 2 に示すように PID のチェック・ボックスを選択してください)。
  3. プロファイルを作成するプロセスの名前を見つけたら、その PID 列の値を見てください。これが、vadump に渡すプロセス ID です。
図 2. タスク マネージャでのプロセス ID 情報を表示できるようにする
タスク マネージャでのプロセス ID 情報を表示できるようにする

Linux での top コマンドによるメモリー使用量の測定

Linux ユーザーには、メモリー使用量を確認するためのツールが多数用意されています。そのうち、共有クラスの効果を明らかにするという目的に適しているのは top コマンドです。出力を読みやすくするため、コマンドラインにプロセス ID を指定して、バッチ・モードで top コマンドを実行します。リスト 10 に、このコマンドラインと、コマンドを実行した結果表示されるデータの一例を記載します。

リスト 10. top コマンドラインとその出力例
top -b -n 1 -p <pid>
	
top - 13:33:41 up 18 days,  9:30,  1 user,  load average: 0.00, 0.00, 0.00
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0% us,  0.0% sy,  0.0% ni, 100.0% id,  0.0% wa,  0.0% hi,  0.0% si
Mem:   8157972k total,   311312k used,  7846660k free,    56448k buffers
Swap:  2104472k total,        0k used,  2104472k free,   141956k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
 7073 root      15   0 41616  13m 2228 S  0.0  0.2   5:43.70 X

上記のうち、私たちにとって最も興味のあるデータは以下のとおりです。

  • VIRT — 仮想イメージ (KB): タスクが使用した仮想メモリーの合計量です。この数値には、すべてのコード、データ、共有ライブラリーに加え、スワップアウトされたページが含まれます。
  • RES — 常駐サイズ (KB): タスクが使用した非スワップ物理メモリーです。
  • SHR — 共有メモリー・サイズ (KB): タスクが使用した共有メモリーの量です。この数値は単に、他のプロセスと共有される可能性のあるメモリーを反映します。

共有クラス機能を使用した Eclipse の構成

共有クラスを使用することによってもたらされるフットプリントおよび起動に要する時間の改善を説明するため、これから実際に存在する 2 つのアプリケーションでの影響を測定します。一方はクライアント・サイドのデスクトップ・アプリケーションを代表する Eclipse、そしてもう一方はサーバー・サイドのアプリケーションを代表する Apache Tomcat です。

記事の冒頭で述べたように、現時点では、Windows 向け Java 6 対応 IBM SDK および Java プラットフォーム用ランタイムのスタンドアロン・インストールはありません。Linux や AIX ではなく Windows をお使いの場合は、Eclipse 用に事前にバンドルされたパッケージをダウンロードしてください。

Linux または AIX をお使いの場合には、スタンドアロンの Java 6 対応 IBM SDK 実装をダウンロードし、Eclipse プロジェクト・サイト (「参考文献」を参照) から必要な Eclipse バージョンをダウンロードします。その後、Eclipse インストール手順に従って、Eclipse が Java 6 対応 IBM SDK を使用するように設定します。

Eclipse のインストールが完了したら、以下の追加ステップを実行してください。

  1. プラグインのクラス共有を有効にするため、OSGI プラグイン・アダプター (「参考文献」を参照) を Eclipse プラグイン・ディレクトリーにインストールします。
  2. SampleView.jar (「ダウンロード」を参照) をダウンロードして Eclipse プラグイン・ディレクトリーにインストールします。このプラグインは Eclipse の起動に要する時間を測定しやすくするため、IBM JVM トレースに接続して、ビューの初期化時にトレース・ポイントを出力します。IBM JVM トレースを使用して起動に関する統計値を表示する方法については、次のセクションで説明します。
  3. workspace1 および workspace2 という名前の 2 つのワークスペースを作成します。こうすると、異なるワークスペースを指す一方、同じクラス・キャッシュを共有する Eclipse の 2 つのインスタンスを起動することができます。

Tomcat をまだセットアップしていない場合は、上記のステップの他に Tomcat をセットアップする必要もあります。それには、Apache Tomcat Web サイトからこのアプリケーションをダウンロードし、パッケージを解凍して running.txt ファイルに記載された手順に従えばよいだけです。

パフォーマンスの比較

これまでのセクションで説明したツールとアプリケーションを使用して、共有クラスによってもたらされるパフォーマンスの向上を測定します。可能な場合にはできるだけ共有クラス機能を切り分けて、結果を解釈しやすいようにします (つまり可能であれば、共有クラス機能を切り分けるために、その他の機能を無効にします)。

Eclipse のパフォーマンス: フットプリント

フットプリントを確認する方法として、異なるワークスペースを使用することによって複数の Eclipse インスタンスを同じ 1 つの Windows システムで同時に実行しました。比較用の vadump データを収集するために、以下の 3 通りの方法で Eclipse を起動しました。

  • 共有クラス機能を有効にしないで、Eclipse を通常通りに起動。
  • 最初はクリアした共有クラス・キャッシュを使用して Eclipse を起動。
  • 2 つ目の Eclipse インスタンスは、上記と同じ共有クラス・キャッシュを使用して起動。

Eclipse で共有クラスを有効にするには、正しい JVM オプションを設定した新しい起動コマンドラインを作成する必要があります。ただ単に新しいショートカットを作成するのではなく、リスト 11 に示すようにバッチ・ファイルを使用して Eclipse を起動します。このバッチ・ファイルは以下の機能を実行します。

  • Eclipse を構成するときに作成したワークスペースに対応する 1 または 2 のいずれかの値をコマンドラインの唯一のパラメーターとして取ります。
  • ワークスペース 1 が指定された場合は、既存の共有クラス・キャッシュをすべてクリアします。
  • Eclipse の終了後に、キャッシュの統計を出力します。
リスト 11. Eclipse の起動に使用するバッチ・ファイル
@echo off
rem batch file to start Eclipse using the specified workspace
SET ECLIPSE_HOME=C:\java\eclipse\IBMEclipse\eclipse
SET JVM=C:\java\eclipse\IBMEclipse\ibm_sdk50\jre\bin\java.exe
SET WNAME=C:\java\eclipse\workspace%1
SET SC_OPTS=-Xshareclasses:name=eclipse,verbose 
SET VMARGS=%SC_OPTS%

echo Clearing shared classes cache
if %1==1 %JVM% -Xshareclasses:destroyAll

echo JVM version
%JVM% -version

echo Starting Eclipse
%ECLIPSE_HOME%\eclipse.exe -nosplash -data %WNAME% -vm %JVM% -vmargs %VMARGS%
%JVM% -Xshareclasses:name=eclipse,printStats

リスト 12 は、Eclipse インスタンスが共有クラスを使用していない場合の vadump レポートです。この vadump 出力のなかで興味の対象となるフィールドは、Shareable KBytes、Shared KBytes、Grand Total Working Set (KBytes) です。

リスト 12. Eclipse が共有クラスを使用していない場合の vadump 出力
Category                        Total        Private Shareable    Shared
                           Pages    KBytes    KBytes    KBytes    KBytes
      Page Table Pages        54       216       216         0         0
      Other System            28       112       112         0         0
      Code/StaticData       4199     16796     11500      1052      4244
      Heap                  9400     37600     37600         0         0
      Stack                   98       392       392         0         0
      Teb                     21        84        84         0         0
      Mapped Data            130       520         0        36       484
      Other Data            5337     21348     21344         4         0

      Total Modules         4199     16796     11500      1052      4244
      Total Dynamic Data   14986     59944     59420        40       484
      Total System            82       328       328         0         0
Grand Total Working Set    19267     77068     71248      1092      4728

リスト 13 に記載するのは、リスト 11 のバッチ・ファイルを使用して Eclipse を起動した場合の vadump 出力です。この出力には、約 4MB のクラス (4116 KByte の共有可能マップト・データとしてレポート) がキャッシュに入れられたため、その分、全体的なワーキング・セットのサイズが増加したことが示されています。強調表示されたエントリーは、このメモリーが他のプロセスと共有可能としてマークされたことを意味します。vadump の出力を比較するときに念頭に置いておくべきことは、いずれの出力も Eclipse の起動時に生成されるものですが、レポートされる数値には多少の違いがあるという点です。

リスト 13. 共有クラス・キャッシュを使用して Eclipse を初めて起動した場合の vadump 出力および統計
Category                        Total        Private Shareable    Shared
                           Pages    KBytes    KBytes    KBytes    KBytes
      Page Table Pages        54       216       216         0         0
      Other System            28       112       112         0         0
      Code/StaticData       4256     17024     11676      1072      4276
      Heap                  8631     34524     34524         0         0
      Stack                  103       412       412         0         0
      Teb                     20        80        80         0         0
      Mapped Data           1155      4620         0      4116       504
      Other Data            5386     21544     21540         4         0

      Total Modules         4256     17024     11676      1072      4276
      Total Dynamic Data   15295     61180     56556      4120       504
      Total System            82       328       328         0         0
Grand Total Working Set    19633     78532     68560      5192      4780

Current statistics for cache "eclipse":


base address       = 0x42B0E000
end address        = 0x43B00000
allocation pointer = 0x42E0B958

cache size         = 16776844
free bytes         = 12005976
ROMClass bytes     = 4001256
AOT bytes          = 625428
Data bytes         = 57043
Metadata bytes     = 87141
Metadata % used    = 1%

# ROMClasses       = 1334
# AOT Methods      = 480
# Classpaths       = 4
# URLs             = 0
# Tokens           = 0
# Stale classes    = 0
% Stale classes    = 0%

Eclipse の別のインスタンスを起動した後、そのインスタンスで vadump を実行すると、リスト 14 のとおり、メモリー使用量には一見してほとんど違いがないように見えますが、よく調べてみると、実際には 4MB のメモリー (4564 KByte の共有マップト・データとしてレポート) が別のプロセスと共有されていることがわかります。vadump (およびタスク マネージャ) は、プロセスが共有メモリーを使用する場合には Grand Total Working Set に共有メモリーも計上します。したがって、2 つ目の Eclipse インスタンスのフットプリントは 4MB 減っていることになります。これは、最初の Eclipse インスタンスが作成してデータを取り込んだクラス・キャッシュを共有しているためです。

ここに記載しているのは、最小限のプラグインがインストールされた Eclipse を起動した場合の結果です。これより多くのプラグインがインストールされていれば、共有クラス・キャッシュに入れられるクラスも多くなり、それに従って起動に要する時間も短縮されることになります。

リスト 14. 既存の共有クラス・キャッシュを使用して Eclipse を 2 回目に起動した場合の vadump 出力
 Category Total Private
Shareable    Shared
                           Pages    KBytes    KBytes    KBytes    KBytes
      Page Table Pages        54       216       216         0         0
      Other System            29       116       116         0         0
      Code/StaticData       4254     17016     11676         0      5340
      Heap                  8684     34736     34736         0         0
      Stack                   98       392       392         0         0
      Teb                     20        80        80         0         0
      Mapped Data           1150      4600         0        36      4564
      Other Data            5261     21044     21040         4         0

      Total Modules         4254     17016     11676         0      5340
      Total Dynamic Data   15213     60852     56248        40      4564
      Total System            83       332       332         0         0
Grand Total Working Set    19550     78200     68256        40      9904

Eclipse のパフォーマンス: 起動

クラスを共有するとフットプリントが小さくなるだけでなく、クラスがディクスからではなくキャッシュからロードされるため、起動に要する時間も短縮されることになります。さらに、キャッシュの AOT コードを使用するということも、起動に要する時間の短縮に寄与します。Eclipse の起動に要する時間を測定するには、IBM JVM トレースを利用してロード時のメッセージを出力するカスタム・ビュー (この記事で前に説明) を使用します。さらにリスト 11 の Eclipse 起動バッチ・ファイルを変更して、JVM トレースを有効に設定し、以下のトレース・イベントを記録するようにする必要もあります。

  • トレースの初期化: トレースが開始されるのは JVM の起動とほぼ同時で、ただしクラスがロードされる前です。このタイミングを起動に要する時間を比較する際の開始点とします。
  • サンプル・ビュー・メッセージ: ビューが初期化されると出力される最初のメッセージは、Eclipse の起動完了を示すメッセージとして見なされます。このタイミングを起動に要する時間の終了点とします。

リスト 15 は、変更後のバッチ・ファイルです。追加された JVM トレース構成行は太字で示してあります。

リスト 15. トレースが有効に設定された Eclipse 起動用バッチ・ファイル
@echo off
rem batch file to time Eclipse startup
SET ECLIPSE_HOME=C:\java\eclipse\IBMEclipse\eclipse
SET WNAME=C:\java\eclipse\workspace%1
SET JVM=C:\java\eclipse\IBMEclipse\ibm_sdk60\jre\bin\java.exe
SET TRACE_OPTS=-Xtrace:iprint=tpnid{j9trc.0},iprint=SampleView
SET SC_OPTS=-Xshareclasses:name=eclipse,verbose 
SET VMARGS=%SC_OPTS% %TRACE_OPTS%

echo Clearing shared classes cache
if %1==1 %JVM% -Xshareclasses:destroyAll

echo JVM version
%JVM% -version

echo VM arguments
echo %VMARGS%

echo Starting Eclipse
%ECLIPSE_HOME%\eclipse.exe -nosplash -data %WNAME% -vm %JVM% -vmargs %VMARGS% 

%JVM% -Xshareclasses:name=eclipse,printStats

リスト 16 とリスト 17 に、共有クラスを使用しないで Eclipse を起動した場合、続いて共有クラスを有効にして起動した場合の出力を記載します。ご覧のように、起動に要する時間は 1 秒近く短くなっており、25 パーセント短縮されたことになります。最初のEclipse 起動はキャッシュの設定に使用されるため、共有クラスを使用して実行した場合のリストに記載されている時間は、2 回目の Eclipse 起動に関しての結果です。必要最小限の Eclipse バージョンではキャッシュに保管されるデータは 4MB だけなので、規模を大きくした一層複雑な Elipse ベースのアプリケーションの場合、クラスの共有によって起動に要する時間が短縮される余地は十分にあります。

リスト 16. 共有クラスを使用しない場合の Eclipse の起動
09:47:55.296*0x41471300   j9trc.0         - Trace initialized for VM = 00096238
09:47:59.500 0x41471300SampleView.2         - Event id 1, text = Mark
09:47:59.500 0x41471300SampleView.0         > Entering getElements(Object parent)
09:47:59.500 0x41471300SampleView.1         < Exiting getElements(Object parent)

Startup = 4.204 seconds
リスト 17. 共有クラスが有効になった場合の Eclipse の起動
09:30:40.171*0x41471300   j9trc.0         - Trace initialized for VM = 000962A8
[-Xshareclasses verbose output enabled] 
JVMSHRC158I Successfully created shared class cache "eclipse" 
JVMSHRC166I Attached to cache "eclipse", size=16777176 bytes
09:30:43.484 0x41471300SampleView.2         - Event id 1, text = Mark
09:30:43.484 0x41471300SampleView.0         > Entering getElements(Object
parent) 09:30:43.484 0x41471300SampleView.1         < Exiting
getElements(Object parent)

Startup = 3.313 seconds

Tomcat のパフォーマンス: フットプリント

ここまでは、クライアント・サイドの環境で、共有クラスを使うことによって起動に要する時間の短縮およびフットプリントの縮小を実現することを検討してきましたが、共有クラスを使うことによるメリットは、サーバー・サイドの環境でも同じく実現します。前に述べたように、サーバー・サイドのサンプル・アプリケーションとして使用するのは Tomcat です。Tomcat で IBM JVM を使用するための特別なステップはありません。共有クラスを利用するために必要な唯一のステップとして、Tomcat が特定のコマンドライン・オプションで JVM を起動するために使用する JVM_OPTS 環境変数に適切な値を設定するというステップがあるだけです (リスト 18 を参照)。

リスト 18. Tomcat 用の JVM オプションの設定
export JAVA_OPTS="-Xmx32m -Xms32m -Xshareclasses:name=tomcat,verbose"

プラットフォームに依存しない共有クラスの効果を実証するため、IBM JVM と Tomcat には両方とも Linux バージョンを使用しました。

Linux で Tomcat のフットプリントを測定するには、前述のとおり top コマンドが最適なツールとなります。この例では、共有クラスを有効にしない状態で (JVM_OPTS 環境変数から "-Xshareclasses:name=tomcat,verbose" を除去)、Tomcat を起動するときに 1 回、そして共有クラスを有効にして起動するときにもう 1 回 top を実行しました。続いて、Tomcat の2 つ目のインスタンスを起動し、同じクラス・キャッシュを共有する 2 つの Tomcat プロセスについてメモリー使用量の違いを top によって明らかにしました。リスト 19、リスト 20、リスト 21 に、それぞれの場合の top 出力を記載します。リスト 22 は、共有クラス・キャッシュの統計です。

リスト 19. 共有クラスを使用しない場合の Tomcat フットプリント
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.1% us,  0.0% sy,  0.0% ni, 99.9% id,  0.0% wa,  0.0% hi,  0.0% si
Mem:   8157972k total,  1727072k used,  6430900k free,   101152k buffers
Swap:  2104472k total,        0k used,  2104472k free,  1370944k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
24595 jbench    25   0 66744  54m 8400 S  0.0  0.7   0:03.71 java
リスト 20. 共有クラスを使用した場合の Tomcat フットプリント
Tasks:   1 total,   0 running,   1 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0% us,  0.0% sy,  0.0% ni, 99.9% id,  0.1% wa,  0.0% hi,  0.0% si
Mem:   8157972k total,  1728800k used,  6429172k free,   101152k buffers
Swap:  2104472k total,        0k used,  2104472k free,  1376084k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
24621 jbench    17   0 78440  56m  14m S  0.0  0.7   0:04.04 java
リスト 21. 同じクラス・キャッシュを共有する 2 つの Tomcat インスタンスのフットプリント
Tasks:   2 total,   0 running,   2 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0% us,  0.0% sy,  0.0% ni, 100.0% id,  0.0% wa,  0.0% hi,  0.0% si
Mem:   8157972k total,  1766440k used,  6391532k free,   101152k buffers
Swap:  2104472k total,        0k used,  2104472k free,  1376084k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
24621 jbench    17   0 78440  56m  14m S  0.0  0.7   0:04.08 java 
24674 jbench    16   0 77600  51m  14m S  0.0  0.6   0:02.28 java
リスト 22. Tomcat が使用したキャッシュについての現行の統計
 base address       = 0x76D0E000
end address        = 0x77D00000
allocation pointer = 0x77186268

cache size         = 16776852
free bytes         = 10085680
ROMClass bytes     = 5911028
AOT bytes          = 621280
Data bytes         = 57051
Metadata bytes     = 101813
Metadata % used    = 1%

# ROMClasses       = 1634
# AOT Methods      = 452
# Classpaths       = 6
# URLs             = 0
# Tokens           = 0
# Stale classes    = 0
% Stale classes    = 0%

Cache is 39% full

上記の結果で共有クラスを使用しない場合と使用した場合の Tomcat のフットプリントを見比べてみても、共有クラスを実行したことによる利点はすぐには見えてきません。それは、共有クラスを利用した場合に、メモリー使用量の数値が増加しているためです。しかし、これらの数値を少し掘り下げて検討すると、実態が明らかになってきます。

  • SHR は 8400KB から 14MB へと約 6MB 増加しています。これは、共有クラス・キャッシュに保管されたデータのサイズです。
  • RES は 54MB から 56MB にわずかに増加しています。これは、共有クラスをサポートするために必要なインフラストラクチャー (オブジェクト・ライブラリーなど) によるものです。
  • VIRT も増加していますが、これは SHR と RES の増加を合計した値だからです。

2 つ目の Tomcat インスタンスを起動し、top を実行してメモリー使用量を調べてみると、2 つ目のインスタンス (リスト 21 のプロセス 24674) の共有メモリー量 (14MB の SHR としてレポート) は 1 つ目の Tomcat インスタンスと同じですが、RES サイズは 5MB 減少し (56MB から 51MB に減少)、さらに仮想メモリーも減っていることがわかります。Windows の vadump と同様に、top は共有される可能性のあるメモリーを正確に特定しますが、共有メモリーに実際に接続されている他のプロセスを表示することはしません。この例の場合、両方の Tomcat インスタンスが同じクラス・キャッシュを使用しているため、全体的なフットプリントは減少しています。このテストで Tomcat サーバーが使用したのは、使用可能なキャッシュの半分にも達していないことになります。リスト 22 には、6MB を多少下回る 5911028 バイトの共有可能 ROMClass データがキャッシュに置かれたことが示されています。つまり、キャッシュ内のクラスを共有することによって、フットプリントがさらに減る余地があるということです。

Tomcat のパフォーマンス: 起動

共有クラスを有効にすると、Tomcat の起動に要する時間も短縮されます。起動に要する時間の測定には、<TOMCAT_HOME>/logs に置かれているログ・ファイル、catalina.out に書き込まれた時間の値を使用します。さらに詳しい比較をするうえでの基準を設定するため、まずは共有クラスを有効にしないで Tomcat を起動します。リスト 23 に、Tomcat の起動に要した時間のレポートを記載します (簡潔にするため、起動プロセス中に書き込まれたその他のログの行は省略しています)。

リスト 23. 共有クラスを無効にした場合の Tomcat の起動に要した時間
 24-Apr-2008 13:01:08 org.apache.catalina.startup.Catalina 
start INFO: Server startup in 1138 ms

上記の時間に対して、共有クラスを有効にした場合に起動に要した時間のレポート (リスト 24 を参照) を比較します。

リスト 24. AOT コードを使用して共有クラスを有効にした場合の Tomcat の起動に要した時間
 24-Apr-2008 13:06:57 org.apache.catalina.startup.Catalina 
start INFO: Server startup in 851 ms

ご覧のように、共有クラスによって Tomcat の起動に要する時間は 1138ms から 851msに短縮されています。つまり、起動に要する時間が 25 パーセント短縮されたということです。この改善は、クラス共有と AOT コードを併用することによってもたらされています。AOT コードによってどれだけの時間が短縮されるかを確認するには、-Xnoaot コマンドライン・オプションにより AOT コードの使用を無効にしてから (リスト 25 を参照)、起動に要する時間を調べてください。

リスt 25. AOT を使用しない場合の起動に要する時間の増加
 24-Apr-2008 13:03:50 org.apache.catalina.startup.Catalina 
start INFO: Server startup in 950 ms

リスト 25 に示された時間の増加は、AOT コードを共有クラス・キャッシュに保管する機能により、Tomcat の起動に要する時間の短縮という点に関して貴重なメリットがもたらされることを明らかにしています。

まとめ

この記事では、共有クラスによって Java アプリケーションの起動に要する時間の短縮、そしてメモリー使用量の削減の両方を実現できることを説明しました。その具体的な例として、Tomcat と Eclipse を使用して共有クラスのサポートによるフットプリントの縮小および起動に要する時間の短縮を数値で表す方法を示しました。当然、すべてのアプリケーションが同じように振る舞うわけではないので、同じメリットが得られるとは限りません。しかし、例として使用した単純な構成でさえも、起動に要する時間は顕著に短縮される結果となっています。

複数のアプリケーションが同じレベルの IBM SDK を実行している場合、それだけ共有するクラスも多くなり、したがって得られるメリットも大きくなりますが、単一のアプリケーションであっても、共有クラス・キャッシュを使用すれば起動に要する時間にある程度のメリットがもたらされます。

この記事では、Windows の場合には vadump、Linux の場合には top などのツールによって共有メモリーを二重に計算し、クラス共有によるメモリーの節約量をより正確に測定する方法も紹介しました。これらのツールがメモリーの使用に関する詳細を完全に示すわけではありませんが、行間を読み取る方法については、この記事の説明で理解してもらえたと思います。


ダウンロード

内容ファイル名サイズ
Source code samplej-sharedclasses.jar6KB

参考文献

学ぶために

製品や技術を入手するために

  • IBM developer kits for the Java platform:よく使用されている IBM の代表的なプラットフォームで Java 2 Platform, Standard Edition アプレットおよびアプリケーションを作成、テストするための開発者用キットが用意されています。
  • IBM JVM を備えた Windows 向け Eclipse IDE をダウンロードしてください。
  • Tomcat Web サーバーをダウンロードしてください。
  • Eclipse プロジェクトについて調べてください。
  • Windows 2003 対応 vadump をダウンロードしてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=348832
ArticleTitle=クラスの共有によるパフォーマンスの向上
publish-date=09302008