コードを Java 8 から Java 11 に移行するための万能ソリューションはありません。 単純でないアプリケーションの場合、Java 8 から Java 11 への移行にはかなりの作業が必要な場合があります。 潜在的な問題には、削除された API、非推奨のパッケージ、内部 API の使用、クラス ローダーへの変更、ガベージ コレクションの変更などがあります。
一般に、方法は、再コンパイルせずに Java 11 で実行するか、JDK 11 で最初にコンパイルします。 可能な限り迅速にアプリケーションを稼働させることが目標である場合は、Java 11 で実行しようとするのが最善のアプローチです。 ライブラリの場合、目標は、JDK 11 でコンパイルおよびテストされる成果物を発行することです。
Java 11 への移行は努力する価値があります。 Java 8 以降、新機能が追加され、機能強化が行われました。 これらの機能と拡張機能により、起動、パフォーマンス、メモリ使用量が向上し、コンテナーとの統合が向上します。 また、開発者の生産性を向上させる API に追加と変更があります。
このドキュメントでは、コードを検査するためのツールについて説明します。 また、発生する可能性がある問題と、それらを解決するための推奨事項についても説明します。 Oracle JDK 移行ガイドなど、他のガイドも参照する必要があります。 既存のコードを モジュール化 する方法については、ここでは説明しません。
ツールボックス
Java 11 には、潜在的な問題をスニッフィングするために役立つ jdeprscan と jdeps の 2 つのツールがあります。 これらのツールは、既存のクラスまたは jar ファイルに対して実行できます。 再コンパイルしなくても、移行作業を評価できます。
jdeprscan は 、非推奨または削除された API の使用を探します。 非推奨の API の使用は重大な問題ではありませんが、検討が必要です。 更新された jar ファイルはありますか? 非推奨の API の使用に対処するために問題をログに記録する必要がありますか? 削除された API の使用は、Java 11 で実行する前に対処する必要があるブロックの問題です。
jdeps。これは Java クラスの依存関係アナライザーです。
--jdk-internals
オプションと共に使用すると、jdeps は、どのクラスがどの内部 API に依存するかを示します。 Java 11 では内部 API を引き続き使用できますが、使用の置き換えを優先する必要があります。 OpenJDK Wiki ページ Java Dependency Analysis Tool には、よく使用される JDK 内部 API の代替候補が推奨されています。
Gradle と Maven の両方に jdeps プラグインと jdeprscan プラグインがあります。 これらのツールをビルド スクリプトに追加することをお勧めします。
道具 | Gradle プラグイン | Maven プラグイン |
---|---|---|
jdeps | jdeps-gradle-plugin | Apache Maven JDeps プラグイン |
jdeprscan | jdeprscan-gradle-plugin | Apache Maven JDeprScan プラグイン |
Java コンパイラ自体の javac は、ツールボックス内のもう 1 つのツールです。 jdeprscan と jdeps から取得した警告とエラーはコンパイラから出ます。 jdeprscan と jdeps を使用する利点は、サードパーティのライブラリを含む既存の jar ファイルとクラス ファイルに対してこれらのツールを実行できることです。
jdeprscan と jdeps でできないことは、カプセル化された API にアクセスするためのリフレクションの使用に関する警告です。 反射アクセスは実行時にチェックされます。 最終的には、確実に知るために Java 11 でコードを実行する必要があります。
jdeprscan の使用
jdeprscan を使用する最も簡単な方法は、既存のビルドから jar ファイルを与える方法です。 また、コンパイラ出力ディレクトリなどのディレクトリや、個々のクラス名を指定することもできます。
--release 11
オプションを使用して、非推奨の API の最も完全な一覧を取得します。 非推奨の API に優先順位を付ける場合は、設定を --release 8
にダイヤルバックします。 Java 8 で非推奨となった API は、最近非推奨になった API よりも早く削除される可能性があります。
jdeprscan --release 11 my-application.jar
jdeprscan ツールは、依存クラスの解決に問題がある場合にエラー メッセージを生成します。
たとえば、error: cannot find class org/apache/logging/log4j/Logger
のようにします。 依存クラスを --class-path
に追加するか、アプリケーションクラスパスを使用することをお勧めしますが、ツールはスキャンを続行しません。
引数は --class-path です。 クラス パス引数の他のバリエーションは機能しません。
jdeprscan --release 11 --class-path log4j-api-2.13.0.jar my-application.jar
error: cannot find class sun/misc/BASE64Encoder
class com/company/Util uses deprecated method java/lang/Double::<init>(D)V
この出力は、 com.company.Util
クラスが java.lang.Double
クラスの非推奨のコンストラクターを呼び出していることを示します。 javadoc では、非推奨の API の代わりに API を使用することをお勧めします。
どれだけ作業してもerror: cannot find class sun/misc/BASE64Encoder
は解決されません。なぜなら、それは削除された API だからです。 Java 8 以降では、 java.util.Base64
を使用する必要があります。
jdeprscan --release 11 --list
を実行して、Java 8 以降に非推奨となった API を把握します。
削除された API の一覧を取得するには、 jdeprscan --release 11 --list --for-removal
実行します。
jdeps の使用
jdk 内部 API への依存関係を見つけるには、 オプションと共に --jdk-internals
を使用します。 log4j-core-2.13.0.jarは--multi-release 11
であるため、この例ではコマンド ライン オプション が必要です。
このオプションがない場合、 jdeps は複数リリース jar ファイルを見つけた場合に不平を言います。 このオプションは、検査するクラス ファイルのバージョンを指定します。
jdeps --jdk-internals --multi-release 11 --class-path log4j-core-2.13.0.jar my-application.jar
Util.class -> JDK removed internal API
Util.class -> jdk.base
Util.class -> jdk.unsupported
com.company.Util -> sun.misc.BASE64Encoder JDK internal API (JDK removed internal API)
com.company.Util -> sun.misc.Unsafe JDK internal API (jdk.unsupported)
com.company.Util -> sun.nio.ch.Util JDK internal API (java.base)
Warning: JDK internal APIs are unsupported and private to JDK implementation that are
subject to be removed or changed incompatibly and could break your application.
Please modify your code to eliminate dependence on any JDK internal APIs.
For the most recent update on JDK internal API replacements, please check:
https://wiki.openjdk.java.net/display/JDK8/Java+Dependency+Analysis+Tool
JDK Internal API Suggested Replacement
---------------- ---------------------
sun.misc.BASE64Encoder Use java.util.Base64 @since 1.8
sun.misc.Unsafe See http://openjdk.java.net/jeps/260
この出力では、JDK 内部 API の使用を排除するための適切なアドバイスが提供されます。 可能であれば、代替 API が推奨されます。 パッケージがカプセル化されるモジュールの名前は、かっこで囲まれています。 モジュール名は、--add-exports
を明示的に中断する必要がある場合は、--add-opens
またはと共に使用できます。
sun.misc.BASE64Encoder または sun.misc.BASE64Decoder を使用すると、Java 11 で java.lang.NoClassDefFoundError が発生します。 これらの API を使用するコードは、 java.util.Base64 を使用するように変更する必要があります。
モジュール jdk.unsupported からの API を使用しないようにしてください。 このモジュールの API は、推奨される代替候補として JDK Enhancement Proposal (JEP) 260 を参照します。 一言で言えば、JEP 260 は、置換 API が使用可能になるまで内部 API の使用がサポートされると述べています。 コードで JDK 内部 API を使用する場合でも、少なくともしばらくの間は実行が継続されます。 JEP 260 は、一部の内部 API の置換を指しているため、確認してください。 変数ハンドルは、たとえば、sun.misc.Unsafe API の代わりに使用できます。
jdeps は、JDK 内部の使用をスキャンするだけでなく、さらに多くのことができます。 依存関係を分析したり、モジュール情報ファイルを生成したりするのに便利なツールです。 詳細については、 ドキュメント を参照してください。
javac の使用
JDK 11 を使用してコンパイルするには、スクリプト、ツール、テスト フレームワーク、および含まれているライブラリをビルドするための更新が必要になります。
-Xlint:unchecked
の オプションを使用して、JDK 内部 API とその他の警告の使用に関する詳細を取得します。 また、 --add-opens
または --add-reads
を使用して、カプセル化されたパッケージをコンパイラに公開する必要がある場合もあります ( JEP 261 を参照)。
ライブラリでは、パッケージ化を マルチリリース jar ファイルと見なすことができます。 複数リリースの jar ファイルを使用すると、同じ jar ファイルから Java 8 ランタイムと Java 11 ランタイムの両方をサポートできます。 それらはビルドの複雑さを増加させます。 マルチリリース jar をビルドする方法については、このドキュメントの範囲外です。
Java 11 での実行
ほとんどのアプリケーションは、Java 11 で変更せずに実行する必要があります。 最初に試してみるのは、コードを再コンパイルせずに Java 11 で実行することです。 ただ実行するポイントは、実行から発生する警告とエラーを確認することです。 このアプローチは次のような結果をもたらします。
アプリケーションを Java 11 で実行するために必要な最小値に焦点を当てることで、より迅速に実行できます。
発生する可能性があるほとんどの問題は、コードを再コンパイルしなくても解決できます。
コードで問題を修正する必要がある場合は、修正を行いますが、JDK 8 でコンパイルし続けます。 可能であれば、JDK 11 でコンパイルする前に、 バージョン 11 でアプリケーションをjava
するように作業します。
コマンド ライン オプションを確認する
Java 11 で実行する前に、コマンド ライン オプションのクイック スキャンを実行します。 削除されたオプション により、Java 仮想マシン (JVM) が終了します。 このチェックは、GC ログ オプションが Java 8 から大幅に変更されているため、GC ログ オプションを使用する場合に特に重要です。 JaCoLine ツールは、コマンド ライン オプションの問題を検出するために使用するのに適しています。
サード パーティ製ライブラリを確認する
問題の原因として考えられるのは、管理していないサード パーティ製ライブラリです。 サード パーティ製ライブラリをより新しいバージョンに事前に更新できます。 アプリケーションを実行した結果何が起きるかを確認し、必要なライブラリのみを更新することができます。 すべてのライブラリを最新バージョンに更新する場合の問題は、アプリケーションで何らかのエラーが発生した場合に根本原因を見つけにくくすることです。 ライブラリが更新されたためにエラーが発生しましたか? または、ランタイムの変更によってエラーが発生しましたか? 必要なものだけを更新する場合の問題は、解決に数回の反復が必要になる可能性があるということです。
ここでの推奨事項は、可能な限り少ない変更を加え、 別の作業としてサード パーティ製ライブラリを更新することです。 サードパーティのライブラリを更新する場合は、Java 11と互換性のある最新かつ最高のバージョンが必要になることがよくあります。 現在のバージョンがどの程度遅れているかに応じて、より慎重なアプローチを取り、最初の Java 9+ 互換バージョンにアップグレードすることができます。
リリース ノートに加えて、 jdeps と jdeprscan を使用して jar ファイルを評価できます。 また、OpenJDK品質グループは、OpenJDKのバージョンに対する多くの無料オープンソースソフトウェア(NSG)プロジェクトのテストの状態を一覧表示する 品質アウトリーチ Wiki ページを保持しています。
ガベージ コレクションを明示的に設定する
並列ガベージ コレクター (Parallel GC) は、Java 8 の既定の GC です。 アプリケーションが既定値を使用している場合は、コマンド ライン オプション -XX:+UseParallelGC
を使用して GC を明示的に設定する必要があります。
Java 9 の既定値がガベージ ファースト ガベージ コレクター (G1GC) に変更されました。 Java 8 で実行されているアプリケーションと Java 11 で実行されているアプリケーションを公平に比較するには、GC 設定が同じである必要があります。 GC 設定の実験は、アプリケーションが Java 11 で検証されるまで延期する必要があります。
既定のオプションを明示的に設定する
HotSpot VM で実行されている場合、コマンド ライン オプション -XX:+PrintCommandLineFlags
設定すると、VM によって設定されたオプションの値 、特に GC によって設定された既定値がダンプされます。
Java 8 でこのフラグを指定して実行し、Java 11 で実行するときに印刷されたオプションを使用します。
ほとんどの場合、既定値は 8 から 11 まで同じです。 ただし、8 の設定を使用すると、パリティが保証されます。
コマンド ライン オプション --illegal-access=warn
設定することをお勧めします。
Java 11 では、リフレクションを使用して JDK 内部 API にアクセスすると、 無効なリフレクション アクセス警告が発生します。
既定では、警告は最初の不正アクセスに対してのみ発行されます。
--illegal-access=warn
を設定すると、無効な反射アクセスごとに警告が発生します。 オプションが警告に設定された不正アクセスの場合は、さらに多くのケースが表示 されます。 ただし、冗長な警告も多数表示されます。
アプリケーションが Java 11 で実行されたら、java ランタイムの将来の動作を模倣するように --illegal-access=deny
設定します。 Java 16 以降では、既定値は --illegal-access=deny
されます。
クラスローダーの注意事項
Java 8 では、システム・クラス・ローダーを URLClassLoader
にキャストできます。 これは通常、実行時にクラスをクラスパスに挿入するアプリケーションとライブラリによって行われます。 クラス・ローダー階層が Java 11 で変更されました。 システム クラス ローダー (アプリケーション クラス ローダーとも呼ばれます) が内部クラスになりました。
URLClassLoader
にキャストすると、実行時に ClassCastException
がスローされます。 Java 11 には、実行時にクラスパスを動的に拡張する API はありませんが、内部 API の使用に関する明らかな注意事項を含め、リフレクションを使用して行うことができます。
Java 11 では、ブート クラス ローダーはコア モジュールのみを読み込みます。 null 親を持つクラス ローダーを作成すると、すべてのプラットフォーム クラスが見つからない可能性があります。 Java 11 では、このような場合は親クラス ローダーとしてClassLoader.getPlatformClassLoader()
するのではなく、null
を渡す必要があります。
ロケール データの変更
Java 11 のロケール データの既定のソースは 、JEP 252 で Unicode コンソーシアムの共通ロケール データ リポジトリに変更されました。
これは、ローカライズされた書式設定に影響を与える可能性があります。 必要に応じて、システム プロパティ java.locale.providers=COMPAT,SPI
を Java 8 ロケールの動作に戻します。
潜在的な問題
発生する可能性がある一般的な問題の一部を次に示します。 これらの問題の詳細については、リンク先を参照してください。
- 認識されない VM オプション
- 認識されないオプション
- VM の警告: オプションの無視
- VM の警告: オプション <オプション> は非推奨になりました
- 警告: 無効な反射アクセス操作が発生しました
- java.lang.reflect.InaccessibleObjectException
- java.lang.NoClassDefFoundError
- -Xbootclasspath/p はサポートされているオプションではなくなりました
- java.lang.UnsupportedClassVersionError
認識されないオプション
コマンド ライン オプションが削除された場合、アプリケーションは Unrecognized option:
または Unrecognized VM option
の後に問題のあるオプションの名前を出力します。 認識されないオプションを指定すると、VM が終了します。
非推奨になったが削除されていないオプションでは、 VM の警告が生成されます。
一般に、削除されたオプションは置き換えがなく、唯一の手段はコマンド ラインからオプションを削除することです。 例外は、ガベージ コレクション ログのオプションです。 統合 JVM ログ記録フレームワークを使用するために、JAVA 9 で GC ログが再実装されました。 Java SE 11 Tools リファレンスの JVM 統合ログ フレームワークを使用したログ記録の有効化に関するセクションの「表 2-2 レガシ ガベージ コレクション のログ フラグ を Xlog 構成にマッピングする」を参照してください。
VM の警告
非推奨のオプションを使用すると、警告が生成されます。 オプションは、置き換えられた場合、または役に立たなくなった場合に非推奨になります。
削除されたオプションと同様に、これらのオプションはコマンド ラインから削除する必要があります。
警告 VM Warning: Option <option> was deprecated
は、オプションは引き続きサポートされていますが、そのサポートは今後削除される可能性があることを意味します。
サポートされなくなったオプションで、警告 VM Warning: Ignoring option
が生成されます。
サポートされなくなったオプションは、ランタイムには影響しません。
Web ページ の VM オプション エクスプローラー には、JDK 7 以降に Java に追加または Java から削除されたオプションの完全な一覧が用意されています。
エラー: Java 仮想マシンを作成できませんでした
このエラー メッセージは、JVM が 認識できないオプションを検出したときに出力されます。
警告: 違法な反射アクセス操作が発生しました
Java コードでリフレクションを使用して JDK 内部 API にアクセスすると、ランタイムは無効なリフレクション アクセス警告を発行します。
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by my.sample.Main (file:/C:/sample/) to method sun.nio.ch.Util.getTemporaryDirectBuffer(int)
WARNING: Please consider reporting this to the maintainers of com.company.Main
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
これは、モジュールがリフレクションを介してアクセスされているパッケージをエクスポートしなかったことを意味します。 パッケージはモジュールに カプセル化され 、基本的には内部 API です。 この警告は、Java 11 で起動して実行するための最初の作業として無視できます。 Java 11 ランタイムは、レガシ コードが引き続き機能できるように、反射型アクセスを許可します。
この警告に対処するには、内部 API を使用しない更新されたコードを探します。 更新されたコードで問題を解決できない場合は、 --add-exports
または --add-opens
コマンドライン オプションを使用してパッケージへのアクセスを開くことができます。
これらのオプションを使用すると、別のモジュールから 1 つのモジュールのエクスポートされていない型にアクセスできます。
--add-exports
オプションを使用すると、ターゲット モジュールはソース モジュールの名前付きパッケージのパブリック型にアクセスできます。 コードでは、 setAccessible(true)
を使用して、パブリックでないメンバーと API にアクセスする場合があります。 これは ディープ リフレクションと呼ばれます。 この場合は、 --add-opens
を使用して、パッケージの非パブリック メンバーへのアクセス権をコードに付与します。
--add-exports と --add-opens のどちらを使用するかがわからない場合は、--add-exports から始めます。
--add-exports
または--add-opens
オプションは、長期的な解決策ではなく、回避策と見なす必要があります。
これらのオプションを使用すると、JDK 内部 API が使用されないようにするためのモジュール システムのカプセル化が解除されます。 内部 API が削除または変更された場合、アプリケーションは失敗します。
--add-opens
などのコマンド ライン オプションによってアクセスが有効になっている場合を除き、Java 16 では反射アクセスは拒否されます。
将来の動作を模倣するには、コマンド ラインで --illegal-access=deny
を設定します。
上記の例の警告は、 sun.nio.ch
パッケージが java.base
モジュールによってエクスポートされないために発行されます。 つまり、モジュール exports sun.nio.ch;
のmodule-info.java
ファイルにjava.base
はありません。 これは、 --add-exports=java.base/sun.nio.ch=ALL-UNNAMED
で解決できます。
モジュールで定義されていないクラスは、名前 のない モジュールに暗黙的に属し、 ALL-UNNAMED
という名前が付けられます。
java.lang.reflect.InaccessibleObjectException
この例外は、カプセル化されたクラスのフィールドまたはメソッドで setAccessible(true)
を呼び出そうとしていることを示します。
また、無効な反射アクセスの警告が表示される場合もあります。
--add-opens
オプションを使用して、コードにパッケージの非パブリック メンバーへのアクセス権を付与します。 例外メッセージは、 setAccessible を呼び出そうとしているモジュールに対して、モジュールがパッケージを "開いていません" ことを示します。 モジュールが "名前のないモジュール" の場合は、UNNAMED-MODULE
オプションでターゲット モジュールとしてを使用します。
java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.util.ArrayList jdk.internal.loader.URLClassPath.loaders accessible:
module java.base does not "opens jdk.internal.loader" to unnamed module @6442b0a6
$ java --add-opens=java.base/jdk.internal.loader=UNNAMED-MODULE example.Main
java.lang.NoClassDefFoundError
NoClassDefFoundError は、分割パッケージまたは削除されたモジュールの参照によって発生する可能性が最も高いです。
分割パッケージによって発生する NoClassDefFoundError
分割パッケージとは、パッケージが複数のライブラリで見つかった場合です。 分割パッケージの問題の症状は、クラス パス上にあることがわかっているクラスが見つからないということです。
この問題は、モジュール パスを使用する場合にのみ発生します。 Java モジュール システムは、パッケージを 1 つの 名前付き モジュールに制限することで、クラス参照を最適化します。 ランタイムは、クラス検索を実行するときに、クラス パスよりもモジュール パスを優先します。 パッケージがモジュールとクラス パスの間で分割されている場合は、モジュールのみがクラス参照を実行するために使用されます。 これにより、 NoClassDefFound
エラーが発生する可能性があります。
分割パッケージを確認する簡単な方法は、モジュール パスとクラス パスを jdeps に接続し、アプリケーション クラス ファイルへのパスを <path> として使用することです。 分割パッケージがある場合、jdeps は警告を出力します: Warning: split package: <package-name> <module-path> <split-path>
。
この問題は、 --patch-module <module-name>=<path>[,<path>]
を使用して、名前付きモジュールに分割パッケージを追加することで解決できます。
Java EE モジュールまたはCORBAモジュールの使用によって発生するNoClassDefFoundError
アプリケーションが Java 8 で実行されていても、 java.lang.NoClassDefFoundError
または java.lang.ClassNotFoundException
がスローされた場合、アプリケーションは Java EE または CORBA モジュールのパッケージを使用している可能性があります。
これらのモジュールは Java 9 では非推奨となり、 Java 11 では削除されました。
この問題を解決するには、ランタイム依存関係をプロジェクトに追加します。
削除されたモジュール | 影響を受けるパッケージ | 推奨される依存関係 |
---|---|---|
JAVA API for XML Web Services (JAX-WS) | .ws の java.xml | JAX WS RI ランタイム |
XML バインディング用の Java アーキテクチャ (JAXB) | .bind の java.xml | JAXB ランタイム |
JavaBeans アクティベーション フレームワーク (JAV) | java.activation | JavaBeans (TM) アクティベーション フレームワーク |
一般的な注釈 | .ws.annotation の java.xml | Javax Annotation API |
共通オブジェクト要求ブローカー アーキテクチャ (CORBA) | java.corba | GlassFish CORBA ORB |
Java Transaction API (JTA) | java.transaction | Java トランザクション API |
-Xbootclasspath/p はサポートされているオプションではなくなりました
-Xbootclasspath/p
のサポートが削除されました。
--patch-module
を代わりに使用します。
--patch-module オプションは JEP 261 で説明されています。 "Patching module content" というラベルの付いたセクションを探します。
--patch-module は 、javac と java を使用して、モジュール内のクラスをオーバーライドまたは拡張するために使用できます。
--patch-module が実際に行っていることは、パッチ モジュールをモジュール システムのクラス参照に挿入することです。 モジュール システムは、最初にパッチ モジュールからクラスを取得します。 これは、Java 8 でブートクラスパスをプリペンディングする場合と同じ効果です。
UnsupportedClassVersionError(サポートされていないクラスバージョンエラー)
この例外は、以前のバージョンの Java で新しいバージョンの Java でコンパイルされたコードを実行しようとしていることを意味します。 たとえば、JDK 13 でコンパイルされた jar を使用して Java 11 で実行しているとします。
Java バージョン | クラス ファイル形式のバージョン |
---|---|
8 | 52 |
9 | 53 |
10 | 54 |
11 | 55 |
12 | 56 |
13 | 五十七 |
次のステップ
Java 11 でアプリケーションを実行したら、ライブラリをクラス パスからモジュール パスに移動することを検討してください。 アプリケーションが依存しているライブラリの更新バージョンを探します。 モジュール ライブラリ (使用可能な場合) を選択します。 アプリケーションでモジュールを使用する予定がない場合でも、可能な限りモジュール パスを使用してください。 モジュール パスを使用すると、クラス パスよりもクラスの読み込みのパフォーマンスが向上します。