クラス・ローダーAPIの修正によるデッドロックの回避

  • バージョン .04:2009年3月30日

  • 著者:Valerie Peng、Jeff Nisewanger、Karen Kinnear

  • 対象読者:コミュニティ・レビュー

目的

  • クラス・ローダーAPIを修正する目的は、カスタム・クラス・ローダーが非循環式のクラス・ローダー委譲モデルに従っていない場合に簡単にデッドロックが発生するという、顧客の抱える重大な問題を解決することにあります。

目的外

  • APIの修正を必要とするその他のクラス・ローダーRFEについては、デッドロック問題に関する今回の修正とは別に、個別のRFEで対応します。

  • このプロポーザルは、パフォーマンス向上を目的とした再構築については取り上げていません。

要因

  • SDN投票数が817に達したトップWebバグ(2008年4月、4670071):java.lang.ClassLoader.loadClassInternal(String)の制限がきつすぎる。

  • すべての人に対して効果がある優れた回避策がない中で、重要な顧客に問題が発生している。

技術設計上の制約事項

  • 非階層型のクラス委譲トポロジでデッドロックが発生しないようにする必要があります。

  • 下位互換性を持たせる必要があります。

    • 既存のクラス・ローダーの動作は変更されることなく機能する必要があります。

    • それには、findClass(...)、loadClass(String)、loadClass(String, boolean)をオーバーライドするクラス・ローダーを含みます。

    • loadClass(String)を使用した直接的なクラス・ローダーの起動や、loadClass(String, boolean)をコールするクラス・ローダーの起動を実行する既存のコードの動作は、変更されることなく機能する必要があります。

  • 一時的なリスクを伴うフラグの組み合わせ(-XX:+UnlockDiagnosticVMOptions -XX:+UnsyncloadClass)を引き続きサポートする必要があります。

    • 顧客に新しいメカニズムが導入され次第、-XX:+UnsyncloadClassフラグは廃止の予定です。

  • 一般的な回避策

    • 現在、一部の顧客は、クラス・ローダーのロックに対して明示的にwait()を発行することでデッドロックを回避しています。 この回避策は多数の顧客の問題を解決するには十分とは言えませんが、これらの顧客が新しいメカニズムへ移行する機会を得るまで、サポートを続ける必要があります。

  • カスタム・クラス・ローダーを移行し非同期処理を利用できるようにするためのガイダンスを定義する必要があります。

  • 現在、クラス・ローダー処理の随所でクラス・ローダーのロックが発生しますが、クラス・ローダーにはマルチスレッドの非同期クラス・ローディングの処理能力は求められていません。 しかし、デッドロックの回避メカニズムでは、マルチスレッド・セーフの認識が必要になります。 マルチスレッドのクラス・ローディングをサポートするには慎重な考察と計画が必要になることから、新しいメカニズムでは、クラス・ローダーがマルチスレッド・セーフであることを明示的に示す方法を提供する必要があります。

  • したがって、 カスタム・クラス・ローダーがデッドロックの候補になっている場合、これらのバグを修正するには、カスタム・クラス・ローダーの作成者によるコード変更が必要になります。

  • 新しいメカニズムを採用するクラス・ローダーは、マルチスレッドを使用した同時クラス・ローディングが安全であることを明示的に示す必要があるため、クラス・ローダーのマルチスレッド・セーフは自動的に継承されてはなりません。

  • 新しいメカニズムを採用するクラス・ローダーは、古いJRE上で実行できる必要があります。 したがって、たとえば、設計はこのメカニズムが存在するかどうかを検出することができ、かつ存在しないインタフェースやクラスの継承に依存してはなりません。

  • 目的:カスタム・クラス・ローダーの作成者によるコード変更は最小限に抑えること。

  • 目的:findClass(String)またはloadClass(String)/loadClass(String, boolean)のいずれをオーバーライドしているかによって、カスタム・クラス・ローダーの作成者に求める考察や変更の程度が変わる場合は、findClass(String)をオーバーライドしている作成者の負担を軽くすること。 findClass(String)をオーバーライドしているカスタム・クラス・ローダーの方が多く、また、一般に、loadClass(...)をオーバーライドする場合、より複雑なクラス・ローダー処理がすでに必要とされています。

クラス・ローダーのデッドロックの問題

クラス・ローダーとVM間のクラス・ローディング・インタラクションの概要

クラス・ローディングには、VMとユーザー・レベルのクラス・ローダーとの間の連携処理が必要になります。 特にVMがコンスタント・プールの解決を実行している場合、VMは要求されたクラスを見つけて定義するために、ユーザー・レベルのクラス・ローダーを呼び出す必要があります。 その一方で、クラスのロードを受け持つクラス・ローダーはVMを呼び出して、クラスがすでにロードされているかどうかを特定し(findLoadedClass)、バイトコード・ストリームに基づいてクラスを定義する必要があります(defineClass)。

現在の推奨されるロジックの詳細を以下に示します。

                 ユーザー・レベルのクラス・ローダー                               VM

                                                            コンスタント・プールの解決
                                                            はじめにクラス・ローダーのロックを取得
  -->private synchronized loadClassInternal(String)    <--- loadClassInternal(String)の呼出し
  |     public loadClass(String)                             
  |       protected synchronized loadClass(String,boolean) 

  |         protected final findLoadedClass(...)             --->  VM SystemDictionaryキャッシュの検索
  |                                                                (これ以上のクラス・ローディングはトリガーできない)
  |         delegate (e.g. parent.loadClass(String))
  |         protected findClass(String)  
  |           reads in bytes

  |           protected final defineClass(...)               ---> 再帰的に
  |                                                               loadClassInternal(String)を呼び出す
  --------------------------------------------------   <---       スーパークラスとスーパーインタフェースを解決   

カスタム・クラス・ローダーの作成者は、バイトコード・ストリームがどこで、またはどのように取得されるかを特定できるようにするため、findClass(String)を自由にオーバーライドできます。 また、委譲方法を変更するために、loadClass (String)やloadClass(String, boolean)をオーバーライドするカスタム・クラス・ローダー作成者もいます。

現在、クラス・ローディング・インタラクションの多くが、クラス・ローダーのロックで同期化されます。 これは、DAGベースの委譲階層を想定したクラス・ローダー委譲に適しています。

任意のクラス・ローダーに委譲する機能が顧客から求められています。 現在、クラス・ローダーが決まった順序付けがなく互いに委譲を行うと、デッドロックが発生する可能性があります。

デッドロック・シナリオのサンプル:非ツリー・ベースの委譲階層

クラス階層:
  クラスAはクラスBを継承
  クラスCはクラスDを継承

クラス・ローダーの委譲階層:

カスタム・クラス・ローダーCL1:
  クラスAを直接ロード

  クラスBの処理をカスタム・クラス・ローダーCL2に委譲

カスタム・クラス・ローダーCL2:
  クラスCを直接ロード
  クラスDの処理をカスタム・クラス・ローダーCL1に委譲

スレッド1:
  CL1を使用してクラスAをロード(CL1をロック)

    defineClass Aによって
      loadClass Bをトリガー(CL2のロックを試行)

スレッド2:
  CL2を使用してクラスCをロード(CL2をロック)
    defineClass Cによって
      loadClass Dをトリガー(CL1のロックを試行)

ソリューション案

                 ユーザー・レベルのクラス・ローダー                               VM
                                                              コンスタント・プールの解決
                                                              ParallelCapableの場合:クラス/クラス・ローダーの組み合わせをロック
                                                                 それ以外の場合は、クラス・ローダーのロックを取得
       *廃止*: private synchronized loadClassInternal    

  |-->public loadClass(String)                          <---  loadClass(String)の呼出し
  |      call loadClass(String,boolean) (*synchronizedではない*)
  |         *ParallelCapableである場合:* 
  |           *synchronize on a class-name-based-lock*
  |           *そうでない場合 synchronize on "this" (下位互換性)*

  |         protected final findLoadedClass(...)             --->  VM SystemDictionaryキャッシュの検索
  |                                                                (これ以上のクラス・ローディングはトリガーできない)
  |         delegate (e.g. parent.loadClass(String))
  |         protected findClass(String)  
  |           reads in by

  |           protected final defineClass(...)               ---> 再帰的に
  |                                                               loadClass(String)を呼び出す
  --------------------------------------------------   <---       スーパークラスとスーパーインタフェースを解決 

APIの修正

Java API

  • java.lang.ClassLoader内の新しいprotected staticメソッド、boolean registerAsParallelCapable()

    • クラスのパラレル・ローディングをサポートするjava.lang.ClassLoaderのサブクラスに対するクラスの初期化中に一度呼び出されます。

    • クラス・ローダー・クラスをパラレル対応クラスとして登録するには、このprotected staticメソッドが起動されている時点で、そのスーパークラスもパラレル対応クラスとして登録されている必要があります。 これは、このクラス・ローダーが継承するすべてのメソッドを明示的にマルチスレッド・セーフにするためのもっとも簡単な方法です。 登録されていない場合、リクエストは無視されます。

    • クラス・ローダーがparallelCapableとして正しく登録されると、registerAsParallelCapable()はtrueを返します(誤って2回呼び出された場合を含む)。 処理が失敗すると、falseが返されます。

  • java.lang.ClassLoader内の新しいprotectedメソッド、Object getClassLoadingLock(String className)

    • クラス・ローディング処理にロック・オブジェクトを返します。

    • 下位互換性を維持するため、このメソッドのデフォルト実装の動作は次のとおりになります。

      • このClassLoaderオブジェクトがパラレル対応として登録されている場合、このメソッドは、指定されたクラス名に関連付けられた専用オブジェクトを返します。
      • それ以外の場合、メソッドはこのClassLoaderオブジェクトを返します。
  • java.lang.ClassLoader APIの変更:

    • private loadClassInternal()は廃止し、下位互換性を維持するため一時的に継続します。

    • 次のメソッドからsynchronizedキーワードを削除します。

      • protected synchronized Class<?> loadClass(String name, boolean resolve)メソッド

      • public synchronized void setDefaultAssertionStatus(boolean enabled)

      • public synchronized void setPackageAssertionStatus(String packageName, boolean enabled)

      • public synchronized void setClassAssertionStatus(String className, boolean enabled)

      • public synchronized void clearAssertionStatus()

Early Accessテスト向けのVMフラグ

  • AlwaysLockClassLoader - デフォルト値はfalse。 マルチスレッドのパラレル処理にバグのあるParallelClassLoader向けに提供されます。 製品に含まれる予定です。

  • AllowParallelDefineClass - デフォルト値はfalse。 2つのスレッドが同じクラス/クラス・ローダーの組み合わせをパラレルで定義しようとした場合、linkageErrorが投げられ、クラス定義が重複します。 このフラグを変更した場合、defineClassのパラレル・リクエストが可能になり、最初のリクエスタの結果が使用されます。 これには、JVMSの明確化が必要になります。 Early Access版のフィードバック用に実験的に含まれているものであり、製品に含まれる予定はありません。

  • MustCallLoadClassInternal - デフォルト値はfalse。 顧客が実際にこのコールに依存している(おそらくはスタック上に見つかるものと考えている)場合に備えて、提供されます。 実施される処理は、loadClassInternal(String)の呼出しのみである点に注意してください。 パラレル対応のクラス・ローダー・インスタンスに対しては、VMとloadClassInternal(String)のいずれもクラス・ローダー・ロックを取得しません。 廃止予定のloadClassInternal(String)が問題なく削除されるまでの1~2リリースにおいてのみ、製品に含まれる予定です。

Java <-> VMインタフェースの変更

  • VMは常に、loadClassInternal(String)ではなくloadClass(String)を呼び出します(注:下位互換性、-XX:+MustCallLoadClassInternalフラグのオーバーライド)。

  • クラス・ローダー・インスタンスがParallelCapableクラスでない場合のみ、VMはクラス・ローダー・ロックを取得します。

  • java.lang.ClassLoaderのインスタンス・コンストラクタは新しいクラス・ローダーのクラスを検索し、それがパラレル対応として登録されていれば、問合せのためにVMのprivateインスタンス・フィールドであるparallelLockMapにNULL値以外を設定します。

クラス・ローダーに必要な変更

  • java.lang.ClassLoader:

    • クラスの初期化中に、パラレル対応クラス・ローダー・リストに自身を追加します。

    • スレッド・セーフな同時実行性を実現するように、コードを修正します。

      • クラス・ローダーがパラレル対応である場合、内部メソッドがクラス・ローダー・オブジェクトで同期化されないようにします。

      • 異なるクラスをロードするマルチスレッドによって実行される場合、すべてのクリティカル・セクションがマルチスレッド・セーフであるようにします。

    • protected loadClass(String, boolean)の変更:

      • synchronizedキーワードを削除します。

      • "java.lang.ClassLoader"がパラレル対応のクラス・ローダーでない場合、下位互換性を維持するため"java.lang.ClassLoader"で同期化します。

      • それ以外の場合、クラス名に基づいたロックで同期化します。

      • protected loadClass(String, boolean)での同期化により、同じクラス名/クラス・ローダーの組み合わせに対して、defineClass(...)がパラレルで複数回呼び出されることはありません。

    • AssertionStatus関連のAPI

      • 次のメソッドのsynchronizedキーワードを内部の同期ロジックで置換します。

        • public synchronized void setDefaultAssertionStatus(boolean enabled)

        • public synchronized void setPackageAssertionStatus(String packageName, boolean enabled)

        • public synchronized void setClassAssertionStatus(String className, boolean enabled)

        • public synchronized void clearAssertionStatus()

      • 内部の同期ロジック:

        • "java.lang.ClassLoader"がパラレル対応のクラス・ローダーでない場合、下位互換性を維持するため"java.lang.ClassLoader"で同期化します。

        • それ以外の場合、アサーション関連のすべてのフィールドに対して、専用の内部ロック・オブジェクトで同期化します。

    • 新しいprotected staticメソッド、registerAsParallelCapable()の追加

      • java.lang.ClassLoaderのインスタンスであるすべてのスーパークラスがParallelCapableとして正しく登録されている場合、呼出し元のクラスもParallelCapableとして登録されます。

  • java.security.SecureClassLoader

    • クラスの初期化中にregisterAsParallelCapable()を起動します。

    • スレッド・セーフな同時実行性を実現するための修正は必要ありません。

  • java.net.URLClassLoader

    • クラスの初期化中にregisterAsParallelCapable()を起動します。

    • スレッド・セーフな同時実行性を実現するように、コードを修正します。

カスタム・クラス・ローダーに推奨されるモデル

  • 過去にデッドロックが発生していないカスタム・クラス・ローダーは、変更する必要はありません。

  • 非階層型の委譲モデルをサポートしているカスタム・クラス・ローダーや、デッドロックが発生する可能性のあるカスタム・クラス・ローダーに対しては、新しいメカニズムを適用できます。 この新メカニズムを採用する場合は、デッドロックが発生する可能性のあるすべてのカスタム・クラス・ローダーを修正する必要があります。

  • 現在のクラス・ローダーは、多くの場合、エンクロージング・メソッドであるloadClassInternalとprotected loadClass(String, boolean)によって提供されるクラス・ローダー・ロックの同期に依存しています。 クラス・ローダー・ロックに対する現在のデッドロックを解決するには、よりきめ細かいロック処理が必要になります。 パラレル対応として正しく登録されているクラス・ローダー・クラスの場合、クラスをロードするよう呼び出された際に、java.lang.ClassLoaderクラスが現在のクラス・ローダー・オブジェクトの同期を取ることは今後はありません。 代わりに、java.lang.ClassLoaderは各クラス名に対して一意な独自のプライベート・ロックを使用します。 これによって、同じクラス・ローダー・インスタンスを使用して異なるクラスを同時にローディングできるようになります。 このロック・ロジックはprotected loadClass(String, boolean)メソッドに含まれており、オーバーライドされていない限り、継承を通じてカスタムのパラレル・クラス・ローダーにも適用されます。

  • デッドロックを回避する必要のあるカスタム・クラス・ローダーに推奨される修正:

    • 1. 必須:クラスの初期化子で、java.lang.ClassLoader registerAsParallelCapable()を呼び出します。

      • この登録によって、このクラス・ローダー・クラスのすべてのインスタンスが、同時クラス・ローディングに対してマルチスレッド・セーフとなるよう指定されます。

      • 登録に成功すると、java.lang.ClassLoaderの同じクラス・ローダー・インスタンスに対して、複数クラスを同時にローディングできるようになります。

    • 2. 必須:マルチスレッド・セーフになるように、コードを修正します。

      • 内部のロック・スキームを決定します。たとえば、java.lang.ClassLoaderはクラス名に基づくロック・スキームを使用しています。

      • クラス・ローダー・ロックに対するすべての同期を削除します。

      • 異なるクラスをロードするマルチスレッドに対して、すべてのクリティカル・セクションがマルチスレッド・セーフであるようにします。

    • 3. 必須:このカスタム・クラス・ローダーを継承するすべてのクラス・ローダー・クラスに対しても、クラスの初期化子でregisterAsParallelCapableを呼出し、同時クラス・ローディングに対してマルチスレッド・セーフであるようにします。

    • 4.a registerAsParallelCapable()を呼び出し、findClass(String)をオーバーライド(推奨されるオーバーライド)するクラス・ローダーの場合:

      • その他の修正は必要ありません。

    • 4.b registerAsParallelCapable()を呼び出し、protected loadClass(String, boolean)またはpublic loadClass(String)をオーバーライドするクラス・ローダーの場合:

      • 可能な限り、オーバーライドの対象をfindClass(String)に変更することを推奨します。

      • 同じクラス名に対してprotected defineClass(...)メソッドが一度しか呼び出されないようにするには、よりきめ細かいロック・スキームの実装が必要になります。 その1つは、java.lang.ClassLoaderのprotected loadClass(String, boolean)メソッドのクラス名ベースのロック・メカニズムを採用する方法です。

内部実装の詳細

もっとも重要な点は、デッドロックの修正が必要であるすべてのクラス・ローダーを変更し、マルチスレッド・セーフにすることです。つまり、これらのクラス・ローダーが同時に複数のクラスをロードできるようにします。 基本的なアプローチは、クラス・ローダー自体に対する同期を削除し、クリティカル・セクションに対して詳細なロックを実装する方法です。 クラス・ローダーがパラレル対応である場合、クラス・ローダー・ロックでの同期をなくすることが重要です。 クラス・ローディングは暗黙的にトリガーされることが多い(newInstanceなどによって)という事実や、VMとクラス・ローダー間のインタラクションを考慮すると、クラス・ローダー・ロックの取得にはデッドロック発生リスクが付きまといます。

カスタム・クラス・ローダーを作成するために顧客が継承しているすべてのJREクラス・ローダーで、registerAsParallelCapable()を呼び出す必要があります。 異なるクラス名/クラス・ローダーの組み合わせを同時にクラス・ローディングできるように、これらすべてのクラスをマルチスレッド・セーフにすることで、パラレル対応されたカスタム・クラス・ローダーによって継承されたメソッドはスレッド・セーフになります。 Sunは、顧客に対し、これらの変更に責任を持って取り組み、対象となるクラスのドキュメントにその変更を追加する必要があります。 また一部では、JREクラス・ローダーで、従来のツリー・ベースの階層以外で委譲を実施する必要があります。 次にその例を挙げます。

  • java.lang.ClassLoader

  • java.security.SecureClassLoader

  • java.net.URLClassLoader

  • 拡張クラス・ローダー:sun.misc.Launcher$ExtClassloader

  • sun.misc.launcher$AppClassLoader

変更の必要がないクラス・ローダーは次のとおりです。

  • デッドロックを回避する代替メカニズムを持つクラス・ローダー
    • javax.management.loading.MLet、PrivateMLetは、 登録順序に基づく非循環式の委譲モデルを提供しています。

  • 顧客による継承が不可能なクラス・ローダー

    • sun.applet.AppletClassLoader
    • sun.plugin.javascript.JSClassLoader
    • sun.plugin.security.PluginClassLoader
    • sun.plugin2.applet.JNLP2ClassLoader
    • sun.plugin2.applet.Applet2ClassLoader
    • sun.plugin2.applet.Plugin2ClassLoader
    • com.sun.jnlp.JNLPClassLoader

VM動作の変更

この問題の修正に長い時間がかかったことを心よりお詫びします。 時間のかかった理由の1つは、最初に以下のVM修正を実施する必要があったことです。

  • 循環性検出における問題の修正(JDK6)

  • クラス・ローディングをパラレル処理するためのVMの共通クラス解決ロジックの変更(HotSpot 10、JDK 6u4)

特定のクラス・ローダー・ケースにおけるVM処理の詳細:

  • 従来のクラス・ローダー

    • 従来のクラス・ローダーを使用してクラスをロードしている場合、クラス・ローダーのオブジェクト・ロックをロックし、loadClass(String)を呼び出します。

  • -XX:+UnlockDiagnosticOptions -XX:+UnsyncloadClass - 廃止

    • クラス・ローダーのクラス・ローダー・オブジェクト・ロックはありません。loadClass(String)を呼び出します。

      • クラス全体のパラレル解決、スーパークラスのパラレル・ロード、defineClass()のパラレル・コールが可能になります。

      • defineClass()のパラレル処理:同じクラス/クラス・ローダーの組み合わせに対して、同時に2つのクラス定義リクエストがあった場合、2番目のリクエスタは最初のリクエスタを待機し、linkageErrorを投げる代わりに最初のリクエスタの結果を返します。

  • parallelCapableのクラス・ローダー

    • クラス・ローダーのクラス・ローダー・オブジェクト・ロックはありません。loadClass(String)を呼び出します。

      • クラス全体のパラレル解決、スーパークラスのパラレル・ロードが可能になります。

      • パラレルのdefineClassリクエストによって、LinkageErrorsが投げられます(Early Access版の試験用、AllowParallelDefineClassフラグを参照)。

      • JDK7以前のJDKでは、parallelCapable登録がVMに無視されます。
  • ブートストラップ・クラス・ローダー

    • グローバル・ロックはありません。クラス全体のパラレル解決が可能になります。

      • スーパークラスのパラレル・ロードの完了を待機します。

      • パラレルのdefineClassリクエストによって、LinkageErrorsが投げられます。

  • クラス・ローダー・ロックの中断

    • 特別処理:クラス全体の解決とスーパークラスのロードの両方において、 最初のリクエスタが処理を完了するまで、その他のリクエスタは待機します。

検討されたその他の代替案

  • マーカー・インタフェース

    • プロポーザル:マーカー・インタフェースを提供することで、クラス・ローダーがこれを実装してパラレル対応であることを宣言できるようにします。

    • 選択されなかった理由:

      • パラレル対応のクラス・ローダーを拡張したカスタム・クラス・ローダーは、継承によって自動的にパラレル対応になります。 祖先クラス・ローダーのいずれかが変更されてパラレル対応になった場合、子クラス・ローダーが期せずしてパラレル対応になる可能性があるため、これは望ましくありません。

      • 一部のクラス・ローダーは、さまざまなJREバージョン間で実行できる必要があります。 これらに対して、古いJREに存在しない新しいインタフェースを実装することはできません。

  • アノテーション

    • プロポーザル:クラス・ローダーがパラレル対応であることを示すアノテーションを使用します。

    • 選択されなかった理由:

      • アノテーションはVMの実行時動作を変更しないように明確に設計されたものであり、情報提供のみを目的としています。つまり、アノテーションをVM向けのマクロ言語として使用することは意図されていません。 仮にアノテーションを理解しないVM上で実行された場合も、動作は一貫している必要があります。

  • 登録メカニズムを含まない、新しいloadClassXXX(String) API

    • プロポーザル:protected method loadClass(String, boolean)からsynchronizedキーワードを削除し、新しいprotectedメソッドとしてloadClass2(String)を追加します。loadClass2(String)はloadClass(String, boolean)を呼び出す前に、デフォルトでクラス・ローダー・ロックを取得します。パラレル・ローディングをサポートするクラス・ローダーは、loadClass2(String)をオーバーライドして独自の同期メカニズムを提供する必要があります。 publicメソッドloadClass(String)とVMの両方が、クラスをロードするためにloadClass2(String)を呼び出します。

    • 選択されなかった理由:

      • クラス・ローダーがパラレル・クラス・ローディングをサポートしているかどうかを、VMから確認する方法はありません。 デッドロックを防止するため、VMでパラレルのクラス解決とパラレルのスーパークラス・ロードを実行する必要がありますが、 下位互換性を維持するには、VMでパラレルのクラス解決とパラレルのスーパークラス・ロードを実行できないようにする必要があります。

      • 現在、クラス・ローディングを実行する方法は複数あり、これらは下位互換性を維持するため引き続きサポートされる必要があります。

        • protected synchronizedメソッドloadClass(String, boolean)を呼び出すカスタム・クラス・ローダーも、引き続き動作する必要があります。 したがって、loadClass(String, boolean)が既存の(パラレル対応でない)クラス・ローダーを呼び出す際、今後もクラス・ローダー・ロックを取得する必要があります。 同じことがloadClass(String)にも当てはまります。
  • 登録メカニズムと新しいloadClassXXX(String, boolean) API

    • プロポーザル:登録メカニズムに加えて、新しいprotectedメソッドloadClassUnsync(String, boolean)を導入します。このメソッドは基本的に、protectedメソッドloadClass(String, boolean)と同じように動作しますが、synchronizedキーワードが指定されていません。VMがpublicメソッドloadClass(String)を呼び出すと、クラス・ローダーがパラレル対応しているかどうかによって、loadClass(String, boolean)またはloadClassUnsync(String, boolean)がディスパッチされます。

    • 選択されなかった理由:

      • 熱心に検討され、実際にプロトタイプも作成されました。 この方法は技術的には可能ですが、デッドロックを回避する必要のあるカスタム・クラス・ローダーの変更が最小限で済み、その他の変更は必要ない点から、考え出された案の中では現在のプロポーザルがもっとも簡単であると思われます。

      • 現在のプロポーザルで必要なカスタム・クラス・ローダーの変更と同じ変更に加えて、追加の変更が必要になります。

        • パラレル対応したすべてのクラス・ローダーは、loadClassUnsync(String, boolean)をオーバーライドし、独自の同期メカニズムを提供する必要があります。つまり、現在使用されているようなクラス名に基づく同期は提供されません。

  • インスタンスごとのregisterAsParallelCapable():

    • プロポーザル:クラス・レベルでのパラレル対応を登録する代わりに、同じクラス・ローダーに対する異なるインスタンスごとにパラレル対応かどうかを登録できるようにします。

    • 選択されなかった理由:不要。

      • この登録は、クラス・ローダーの実装がマルチスレッド・セーフかどうかを示すためのものであり、 この情報はクラス単位で意味をなします。 カスタム・クラス・ローダーの作成者が望めば、その実装でパラレル対応のクラス・ローディング機能を無効化したいインスタンスに対して、クラス・ローダー・オブジェクトのロックを元に戻すことができます。 parallelCapableとして登録することで、このような選択肢が得られます。 パラレル対応しているかどうかの追跡は、クラス・ローダーのクラス・インスタンスに基づくものであり、クラス名には基づきません。 したがって、同じクラス名でもクラス・ローダーが異なれば、別々に追跡されます。

  • パラレルの重複クエストに対する、新しいLinkageError

    • プロポーザル:パラレル重複リクエストを処理する1つの方法として、現在のように"duplicate"エラーを返す代わりに、新しいLinkageErrorサブクラスを定義して、はじめに定義されたクラスに戻すという方法が提案されてきました。 このプロポーザルが意図しているのは、defineClass()の呼出し元で最初のクラスと要求されたクラスが一致しているかどうかをチェックし、一致している場合はfindLoadedClass()を実行して、以前に定義されたクラスを使用するという方法であると考えられます。

    • 選択されなかった理由:このプロポーザルが抱える問題点は、処理されていない最初のバイト・ストリームが実際にはVMにキャッシュされておらず、オーバーヘッドを発生させてまでキャッシュする価値はないという点です。

  • すべてのスーパークラスがパラレル対応する必要があるという要件をオーバーライドする機能

    • プロポーザル:一部のカスタム・クラス・ローダー作成者は、特定のクラス・ローダーがparallelCapableである場合、すべてのスーパークラスがパラレル対応している(そのように登録されていない場合を含む)ことが保証されるようにしたいと考えています。

    • 選択されなかった理由:

      • スーパークラスの作成者は、パラレル対応に伴って将来的な変更でマルチスレッド・セーフをサポートするよう期待されていることを知る由もありません。したがって、文書化されていない非公式な下位互換性が維持されない可能性が大いにあります。

  • パラレル対応という用語の代わりに非同期という用語を使用

    • プロポーザル:用語として非同期を使用します。

    • 選択されなかった理由:

      • 非同期という用語は非同期I/Oを類推させ、クラス・ローディングが後から実行されるという意味にも取れるため、最適な選択肢とは言えません。

未解決の課題

  • JVMSとdefineClass(...)の動作の変更

    • プロポーザル:クラス・ローダーがパラレル対応しており、同じクラス/クラス・ローダーの組み合わせに対して2つのdefineClass(...)リクエストが同時に発生した場合、重複定義のLinkageErrorを投げる代わりに、最初のdefineClass()の定義を優先するようにdefineClass()の動作を変更するという提案があります。 この場合、すべてのdefineClass(...)リクエストでClassFileFormatErrorのバイト・ストリームが解析されます。 また、最初のリクエスタが定義プロセスを完了するまで後続のリクエスタは待機し、最初のリクエストの結果を使用します。

    • プロトタイプでは、-XX:+AllowParallelDefineClassフラグが提供されているため、実際に使用した上でフィードバックをお寄せいただけます。

    • フィードバック:現在のところ、両方のデフォルトが要求されています。 プロトタイプが提供されたら、追加のフィードバックをお寄せください。

▲ ページTOPに戻る