Java アプリケーションの開発

     前  次    目次     
コンテンツの開始位置

Java アプリケーションの開発

このドキュメントでは、Oracle JRockit JVM で実行する Java アプリケーションを記述するためのガイドラインを示します。ここで提供する情報は包括的なものではありませんが、一般的な落とし穴を回避するのに役立ちます。Oracle は java の「Write once run everywhere」という概念を損なうことはありません。この節では、どの Java プログラムにとっても有用なガイドラインを示しますが、これらのガイドラインは、概して Jvm 同士、特に Sun Microsystems の HotSpot JVM と JRockit JVM の間で切り替えを行うときに非常に重要です。

ベスト プラクティスのコーディングの内容は以下のとおりです。

 


関連する仕様を読む

Java 言語仕様と Java API 仕様をよく読んで、規定されていない動作には依存しないようにします。

Oracle JRockit JDK は Java 仮想マシン仕様や Java API 仕様などのさまざまな仕様に基づいています。これらの仕様には多数の実装があり、JRockit JDK もその 1 つです。これらのドキュメントで指定されていない特別の動作は想定しないようにする必要があります。規定されていない動作は SUNJDK と JRockit JDK で異なることがあります。また、Sun JDK の個々のリリースや、JRockit JDK のリリースによっても異なる場合があるので注意が必要です。

これらの仕様は以下のサイトにあります。

仕様は、JDK ベンダに JDKs を最適化する自由を与えるように記述されているため、規定されていない特定の動作があります。上記の仕様には規定されていない部分が多数あることを理解しておく必要があります。以下の例で、そのような規定されていない要素を 4 つ紹介します。

例 1: リフレクション

java.lang.Classgetmethods() メソッドに関する java api 仕様では、「返された配列内の要素は、ソートされていたり、特定の順序になっていることはありません」と明記されています。

例 2: リフレクション (2)

java.lang.reflect.MethodtoString() メソッドではアクセス修飾子 native を使用できます。そのため、この呼び出しの結果が JVM 実装同士で同じであるということに依存しないでください。Java API 仕様のクラスの一部には JRockit JDK と Sun JDK のどちらかで native として実装されているものがあります。ある JVM のネイティブ実装が別の JVM でもネイティブであるという保証はありません。

例 3: シリアライゼーション

java.lang.ObjectInputStream クラスの defaultReadObject() メソッドに関する Java API 仕様では、デシリアライズされるフィールドの順序が規定されていないため、このような順序を予期することはできません。

例 4: ファイナライザ

JVM 仕様には、finalize メソッドをオーバーライドしているクラスでは、そのクラスに対するガベージ コレクションの実行前に必ず finalize メソッドが実行される、と記述されています。実際にどの時点でファイナライザ メソッドを実行するかの判断は各 JVM 実装に任されています。ガベージ コレクションの対象となることがまったくないオブジェクトでは、ファイナライザ メソッドは 1 回も実行されません。そのため、アプリケーションでは、ファイナライザが実行されるという前提で、ソケットやその他のファイル ハンドルなど、特定のリソースの解放処理をファイナライザに任せていると、さまざまな種類のリソース不足の問題が発生するおそれがあります。

JRockit JVM では、独立したスレッドでファイナライザ メソッドの実行を処理します。ファイナライザが独立したスレッドで実行される場合、次のような状況が生じます。

アプリケーションが System.runFinalization() を呼び出すと、2 番目のファイナライザ スレッドが作成されます。これにより、1 番目のファイナライザ スレッドが未処理のファイナライザを呼び出せるようになるため、ガベージ コレクタによってアクセスできないと判断されてからファイナライザが実行されるまでの時間を短縮することができます。保留中のファイナライザがなくなると、2 番目のファイナライザ スレッドは終了します。この 2 番目のファイナライザは通常の優先順位で開始されます。

ファイナライザが通常のロックのために競合している場合、2 番目のファイナライザ スレッドを作成すると、パフォーマンスが低下するおそれがあります。

 


非推奨の安全でないメソッドを使用しない

すでに非推奨となっている多数のメソッドは本質的に安全ではないため、使用しないでください。コンパイル時に -deprecation オプションを指定すると、非推奨のメソッドが使用されているメソッドを確認することができます。詳細については、以下を参照してください。

http://java.sun.com/j2se/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html

 


ファイナライザの使用を最小限に抑える

ファイナライザは実行の順序に暗黙的に依存することがよくあるため、エラーが発生しやすくなります。この順序は JVM 間でも、同じ JVM 上の連続した実行同士でも異なります。また、ファイナライザを使用すると、ファイナライザの実行を処理し、オブジェクトをより長くアクティブにするためにメモリ管理システムに余分な負荷がかかるため、本質的にパフォーマンスに悪影響が及びます。

ファイナライザの使用方法 (および使用しない方法) の詳細については、以下を参照してください。

http://access1.sun.com/techarticles/weak.references.html
http://www.memorymanagement.org/glossary/r.html#reference.object

 


スレッドの優先順位に依存しない

java.lang.Thread.setPriority を使用する場合は注意してください。スレッドの優先順位に依存すると、望まないまたは予期しない結果を引き起こす可能性があります。スケジューリング アルゴリズムは CPU 時間の優先順位の低いスレッドを選択しないまま、そのスレッドを永久に実行しない可能性があるためです。さらに、その結果はオペレーティング システムや JVM 間で異なる場合があります。

Java API 仕様には、「各スレッドには優先順位が付けられています。優先順位の高いスレッドは、優先順位の低いスレッドよりも優先して実行されます」と記述されています。

setPriority() メソッドによって設定される優先順位は、スレッドスケジューリング アルゴリズムで使用されるパラメータであり、CPU 時間を実行スレッドに分配するものです。このアルゴリズムは JVM またはオペレーティング システムによって制御されます。このアルゴリズムは通常オペレーティング システム間で異なり、オペレーティング システムと JVM の各リリース間でも異なる場合がある点に注意することが大切です。

 


内部的な sun.* または jrockit.* クラスを使用しない

Oracle JRockit JDK に含まれるクラスは、bea.*、java.*javax.*org.*sun.*com.bea.jvm.* および jrockit.* の各パッケージ グループに分類されます。sun.*jrockit.* および com.bea.jvm.* パッケージ以外は Java プラットフォームの標準部分であり、今後もサポートされます。com.bea.jvm.* パッケージのクラスには、公式にサポートされているクラスやドキュメントに情報が記載されているクラスだけでなく、サポート対象外の内部的なクラスも含まれています。このうち、使用しないように注意する必要があるのは、ドキュメントに記載されていない非公式の com.bea.jvm.* クラスです。一般に、Java プラットフォームに含まれていない非標準のパッケージは、JVM ベンダと OS プラットフォーム (Windows、Linux など) によって異なることがあり、JDK のバージョンごとに予告なくいつでも変更される可能性があります。sun.* および jrockit.* パッケージを直接使用しているプログラムは 100% Pure Java ではありません。

詳細については、以下の sun.* パッケージに関する注意を参照してください。

http://java.sun.com/products/jdk/faq/faq-sun-packages.html

 


ハッシュ化を使用する場合はユーザ定義クラスの java.Object.hashCode をオーバーライドする

JRockit JVM における hashcode の現在のデフォルト実装では、オブジェクトの値として JVM によって決定された値が返されます。値はオブジェクトのメモリ アドレスを使用して作成されます。ただし、ガベージ コレクション中にオブジェクトが移動される場合、この値は再利用できるため、2 つの異なるオブジェクトに対する同じハッシュ コードを取得することが可能です。また、同じ値を表す 2 つのオブジェクトは、まったく同一のオブジェクトである場合にのみ同じハッシュ コードを持つことが保証されています。この実装はハッシュ化を行う上ではあまり便利ではありません。そのため、クラスのオブジェクトを java.util.Hashtable または java.util.HashMap に格納する必要がある場合は、派生クラスで hashCode() をオーバーライドしてください。

 


スレッドの同期は慎重に行う

共有データにアクセスするスレッドを同期していることを確認してください。JVM を変更すると同期化のバグが発生することがよくあります。ロック、ガベージ コレクション、スレッド スケジューリングなどの実装が大きく異なる可能性があるためです。

注意 : 同期化の問題 (デッドロックや競合状態) の原因がコードに含まれている場合、そのバグは他の JVM を使用していた時点ですでに存在していたと考えられます。他の JVM の使用時に同様の同期化の問題が発生したことがなかったならば、それは偶然運が良かっただけです。

 


標準のシステム プロパティのみを想定する

java.lang.System.getProperties() または java.lang.System.getProperty() を呼び出す場合は、返される標準のシステム プロパティにのみ依存する必要があります。VM が異なると、拡張プロパティの別のセットを返す可能性があります。非標準のプロパティが返されないようにしてください。

JVM は起動時に、システム プロパティ リストに多数の標準プロパティを挿入します。これらのプロパティとその値の意味は、Java API 仕様で示されています。これ以外の非標準のプロパティには依存しないようにしてください。

http://java.sun.com/j2se/1.5.0/docs/api/java/lang/System.html#getProperties()

 


Java プロセスの数を最小限に抑える

アプリケーションを設計するときに、複数のプロセス (JVM インスタンス) を実行するか、単一のプロセス (JVM インスタンス) の内部で複数のスレッドまたはスレッド グループを実行するかを選ぶ場合があります。ほとんどの場合、1 つの物理マシンごとにできる限り少ない JVM インスタンスを使用する方が効率的です。

 


System.gc() を呼び出さない

java.lang.System.gc() を呼び出さないでください。一般に、JRockit JVM のガベージ コレクタは、ガベージ コレクションの実行時期を System.gc() よりも効果的に決定します。実際、アプリケーションが System.gc() を繰り返し呼び出すと、パフォーマンスが低下する可能性があります。メモリ使用状況やガベージ コレクションの休止時間などで問題がある場合は、JRockit JVM のメモリ管理システムを適切にコンフィグレーションしてください。Jrockit のガベージ コレクションメモリ管理、および個々のトランザクションの速度を高めるチューニング(すべて Oracle JRockit 診断ガイドの一部) を参照してください。

このメソッドは、JRockit JVM と他の JVMs とでは動作が異なる可能性があることに留意してください。JRockit JVM で System.Gc() を呼び出すと、世代別ガベージ コレクタを使用している場合はナーサリのコレクションが実行され、一世代のコレクタを使用している場合は古い領域のコレクションが実行されます。

以下の 3 つのコマンドライン オプションに従うと、System.gc() の動作を変更できます。

 


オブジェクトの割り当ては慎重に行う

オブジェクトの割り当ては、ガベージ コレクションを引き起こします。可能である場合は、オブジェクトの割り当てと再割り当てを行わないようにします。たとえば次のような場合です。

 


ネイティブ コード (JNI) のシグナルは慎重に使用する

JRockit JVM では、さまざまな目的で内部的にシグナルが使用されています。シグナルを使用するネイティブ ライブラリを使う場合は、以下のガイドラインに従う必要があります。

シグナル チェーンを使用する

Java で実行されるコード内でシグナルを利用する場合はシグナル チェーンを使用する必要があります。シグナル チェーンの機能を使用すると、独自のシグナル ハンドラをインストールするネイティブ コードと Java の相互運用性を高めることができます。シグナル チェーンを実現するには、リンク時に libjsig.so をリンクする方法と、実行時に LD_PRELOAD 環境変数を使用する方法があります。これらのチェーンの設定方法の詳細については、次の URL でアクセスできる Sun Microsystems のドキュメント「Signal Chaining」を参照してください。


http://java.sun.com/j2se/1.5.0/docs/guide/vm/signal-chaining.html

SIGUSR1 および SIGUSR2 を使用しない

JRockit JVM では、SIGUSR1 および SIGUSR2 の 2 つのシグナルが VM の内部的な目的で使用されています。これらのシグナルは Oracle JRockit JVM の機能にとって不可欠の重要な要素であり、無効にはできません。そのため、ユーザのネイティブ ライブラリでは SIGUSR1 は使用しないでください。これらのシグナルの使用は JVM の障害につながり、多くの場合、シグナルを受信したスレッドのハング (SIGUSR1 の場合) や JVM のクラッシュ (SIGUSR2 の場合) が発生する原因となります。

シグナルを受信する場合があるという前提で確認を行う (EINTR をチェックする)

システム ルーチン (sleep など) を呼び出すネイティブ コードは、その関数から失敗の戻り値 (-1) が返された場合には、errno の値として EINTR が設定されていないか必ず確認する必要があります。この値が設定されている場合は、通常、そのシステム ルーチンの呼び出しを単に再試行するのが適切な対処です (一部例外があるので、実際に使用しているシステムのプログラミング マニュアルで確認してください)。JRockit JVM は、アタッチされているスレッドに対して定期的な間隔で SIGUSR1 を送信するので、コード内のあらゆる箇所でシグナルを受信する可能性があることに留意してください。

-Xrs オプションは必要性をよく検討した上で使用する

-Xrs (「シグナルの削減」) コマンドライン オプションを指定すると、SIGINT、SIGTERMSIGHUP、および SIGQUIT に対するシグナル マスクは JVM によって変更されず、これらのシグナルのシグナル ハンドラはインストールされません。-Xrs を指定すると、結果的に以下の 2 つの影響が生じます。

JVM によるオペレーティング システムのシグナルの使用が減るため、このオプションは「シグナルの削減」と呼ばれます。


  ページの先頭       前  次