Java 11 以降に移行する理由
問題は、Java 11 以降のバージョンに移行する必要 があるかどうか ではなく、 いつ行うかです。 今後数年間で Java 8 はサポートされなくなり、ユーザーは Java 11 以降に移行する必要があります。 Java 11 に移行することにはメリットがあり、できるだけ早くそのようにすることをお勧めします。
Java 8 以降、新機能が追加され、機能強化が行われてきました。 API には大幅な追加と変更が行われました。また、起動、パフォーマンス、メモリの使用量が向上する拡張機能も行われています。
Java 11 への移行
Java 11 への移行は、手順を追って行うことができます。 Java 11 で実行するためにコードで Java モジュールを使用する必要は "ありません"。 Java 11 を使用して、JDK 8 で開発およびビルドされたコードを実行できます。 ただし、主に非推奨の API、クラス ローダー、およびリフレクションに関する潜在的な問題がいくつかあります。
Microsoft Java Engineering グループには、Java 8 から Java 11 への移行に関するガイドがあります。 他に役立つガイドとして「Java Platform、Standard Edition Oracle JDK 9 移行ガイド」や「モジュール システムの状態: 互換性と移行」などがあります。
Java 8 と 11 の間の高レベルの変更
このセクションでは、Java バージョン 9 [1]、10 [2]、および 11 [3] で行われたすべての変更を列挙するわけではありません。 パフォーマンス、診断、生産性に影響を与える変更について紹介しています。
モジュール [4]
モジュールは、"クラスパス" で実行されている大規模なアプリケーションでは管理が困難な構成とカプセル化の問題に対処します。 "モジュール" は、Java クラスとインターフェイス、および関連リソースの自己記述型のコレクションです。
モジュールを使用すると、アプリケーションに必要なコンポーネントのみを含むランタイム構成をカスタマイズできます。 このカスタマイズによって、占有領域が小さくなり、アプリケーションを jlink を使用して、デプロイ用のカスタム ランタイムに静的にリンクできます。 この小さい占有領域は、マイクロサービス アーキテクチャで特に役立ちます。
内部的には、JVM は、モジュールを利用してクラスの読み込み効率を高めることができます。 結果として、小さく、軽く、起動が高速なランタイムが実現します。 クラスで必要なコンポーネントがモジュールによりエンコードされるため、アプリケーションのパフォーマンスを向上させるために JVM によって使用される最適化手法が、より効果的になります。
プログラマにとっては、モジュールがエクスポートするパッケージと、それに必要なコンポーネントの明示的な宣言を強制し、リフレクティブ アクセスを制限することによって、モジュールは強力なカプセル化を適用するのに役立ちます。 このレベルのカプセル化により、アプリケーションのセキュリティが向上し、保守が容易になります。
アプリケーションでは引き続き "クラスパス" を使用でき、Java 11 で実行するための要件としてモジュールに移行する必要はありません。
プロファイルと診断
Java フライト レコーダー [5]
Java Flight Recorder (JFR) は、実行中の Java アプリケーションから診断データとプロファイル データを収集します。 JFR は、実行中の Java アプリケーションにほとんど影響しません。 収集したデータは、Java Mission Control (JMC) などのツールを使用して分析できます。 JFR と JMC は Java 8 では商用機能でしたが、どちらも Java 11 ではオープンソースです。
Java ミッション コントロール [6]
Java Mission Control (JMC) は、Java フライト レコーダー (JFR) によって収集されたデータをグラフィカルに表示し、Java 11 でオープンソース。実行中のアプリケーションに関する一般的な情報に加えて、JMC を使用すると、ユーザーはデータをドリルダウンできます。 JFR と JMC を使用して、メモリ リーク、GC のオーバーヘッド、ホット メソッド、スレッドのボトルネック、ブロッキング I/O などのランタイムの問題を診断できます。
統合ログ [7]
Java 11 には、JVM のすべてのコンポーネントに共通のログ記録システムがあります。 この統合されたログ記録システムにより、ユーザーはログに記録するコンポーネントとそのレベルを定義できます。 この詳細なログ記録は、JVM のクラッシュに関する根本原因分析の実行や、運用環境でのパフォーマンスの問題の診断に役立ちます。
オーバーヘッドの少ないヒープ プロファイリング [8]
Java ヒープ割り当てをサンプリングするための新しい API が Java Virtual Machine Tool Interface (JVMTI) に追加されました。 サンプリングのオーバーヘッドは低く、継続的に有効にすることができます。 ヒープ割り当ては Java Flight Recorder (JFR) で監視できますが、JFR のサンプリング メソッドは割り当てに対してのみ機能します。 JFR 実装では、割り当てが見つからない場合もあります。 これに対して、Java 11 のヒープ サンプリングでは、ライブ オブジェクトと dead オブジェクトの両方に関する情報を提供できます。
アプリケーション パフォーマンス監視 (APM) のベンダーは、この新機能を利用し始めており、Java Engineering Group では、Azure パフォーマンス監視ツールでの使用の可能性を調査しています。
StackWalker [9]
ログを記録するときに、現在のスレッドのスタックのスナップショットがよく取得されます。 問題は、ログに記録するスタック トレースの量と、そもそもスタック トレースをログに記録するかどうかです。 たとえば、あるメソッドからの特定の例外についてのみ、スタック トレースを確認したい場合があります。 StackWalker クラス (Java 9 で追加) は、スタックのスナップショットを提供し、プログラマがスタック トレースの使用方法をきめ細かく制御できるようにするメソッドを提供します。
ガベージ コレクション [10]
Java 11 では、次のガベージ コレクターを使用できます: シリアル、パラレル、ガベージファースト、および Epsilon。 Java 11 の既定のガベージ コレクターは、ガベージファースト ガベージ コレクター (G1GC) です。
その他の 3 つのコレクターを補完のために紹介します。 Z ガベージ コレクター (ZGC) は、一時停止の時間を 10 ミリ秒以下に維持しようとする、同時実行の待機時間が短いコレクターです。 ZGC は、Java 11 の試験的な機能として利用できます。 Shenandoah コレクタは、実行中の Java プログラムと同時により多くのガベージ コレクションを実行することで、GC の一時停止時間を短縮する、待機時間の短いコレクターです。 Shenandoah は Java 12 の実験的な機能ですが、Java 11 に移植されています。 コンカレント マーク スイープ コレクター (CMS) は使用できますが、Java 9 以降は非推奨となりました。
JVM では、平均的なのユースケース向けに GC の既定値が設定されます。 多くの場合、これらの既定値とその他の GC 設定は、アプリケーションの要件に従って、最適なスループットまたは待機時間に合わせて調整する必要があります。 GC を適切にチューニングするには、Microsoft Java Engineering Group が提供する、GC に関する深い知識が必要です。
G1GC
Java 11 の既定のガベージ コレクターは、G1 ガベージ コレクター (G1GC) です。 G1GC の目的は、待機時間とスループットのバランスを取ることです。 G1 ガベージコレクターは、高い確率で一時停止時間の目標を満たすことで、高いスループットを達成しようとします。 G1GC は完全なコレクションを回避するように設計されていますが、同時実行コレクションでメモリを十分に高速に再利用できない場合は、フォールバックフル GC が発生します。 完全な GC では、young コレクションと混合コレクションと同じ数の並列ワーカー スレッドが使用されます。
パラレル GC
パラレル コレクターは、Java 8 の既定のコレクターです。 パラレル GC は、複数のスレッドを使用してガベージ コレクションを高速化するスループット コレクターです。
Epsilon [11]
Epsilon ガベージ コレクターは割り当てを処理しますが、メモリを再利用することはありません。 ヒープが使い果たされると、JVM はシャットダウンします。 Epsilon は、短期間のサービスや、ガベージフリーであることが判明しているアプリケーションに向いています。
Docker コンテナーの機能強化 [12]
Java 10 より前では、コンテナーに設定されているメモリと CPU の制約は JVM によって認識されませんでした。 たとえば Java 8 では、JVM によって、最大ヒープ サイズは基になるホストの物理メモリの 1/4 に設定されます。 Java 10 以降では、JVM はコンテナー コントロール グループ (cgroups) によって設定された制約を使用して、メモリと CPU の制限を設定します (下記の注を参照してください)。 たとえば、既定の最大ヒープ サイズは、コンテナーのメモリ制限の1/4 です (例: -m2G の場合 500 MB)。
JVM オプションも追加されました。これにより、Docker コンテナーのユーザーは、Java ヒープに使用されるシステム メモリの量をきめ細かく制御できるようになります。
このサポートは既定で有効になっており、Linux ベースのプラットフォームでのみ使用できます。
Note
cgroup の有効化作業のほとんどは、jdk8u191 の時点で Java 8 に移植されました。 今後の改善は、必ずしも 8 に移植されるとは限りません。
複数リリースの jar ファイル [13]
Java 11 では、Java リリース固有の、複数のバージョンのクラス ファイルを含む jar ファイルを作成できます。 マルチリリースの jar ファイルを使用することで、ライブラリ開発者は複数のバージョンの jar ファイルを配布せずに、複数のバージョンの Java をサポートできるようになります。 これらのライブラリの利用者にとっては、マルチリリース jar ファイルにより、特定の jar ファイルを特定のランタイム ターゲットに一致させる必要があるという問題が解決します。
その他のパフォーマンスの強化
JVM に対する次の変更は、パフォーマンスに直接影響します。
JEP 197: セグメント化されたコード キャッシュ [14] - コード キャッシュを個別のセグメントに分割します。 このセグメント化により、JVM のメモリ占有領域の制御が向上し、コンパイルされたメソッドのスキャン時間が短縮され、コード キャッシュの断片化が大幅に減少し、パフォーマンスが向上します。
JEP 254: コンパクト文字列 [15] - 文字エンコードに応じて、文字列の内部表現を 1 文字あたり 2 バイトから 1 文字あたり 1 バイトまたは 2 バイトに変更します。 ほとんどの文字列には ISO-8859-1/Latin-1 文字が含まれているため、この変更によって、文字列を格納するために必要な領域の量が実質的に半分になります。
JEP 310: アプリケーション Class-Data共有 [16] - Class-Data共有は、アーカイブされたクラスを実行時にメモリ マップできるようにすることで、起動時間を短縮します。 アプリケーション クラス データ共有は、アプリケーション クラスを CDS アーカイブに配置できるようにすることで、クラス データ共有を拡張します。 複数の JVM が同じアーカイブ ファイルを共有すると、メモリが節約され、システム全体の応答時間が短縮されます。
JEP 312: Thread-Localハンドシェイク [17] - グローバル VM セーフポイントを実行せずにスレッドでコールバックを実行できるようになり、グローバル セーフポイントの数を減らすことで VM の待機時間を短縮できます。
コンパイラ スレッドの遅延割り当て [18] - 階層化コンパイル モードでは、VM によって多数のコンパイラ スレッドが開始されます。 このモードは、多くの CPU を搭載したシステムでは既定です。 これらのスレッドは、使用可能なメモリまたはコンパイル要求の数に関係なく作成されます。 スレッドは、アイドル状態 (ほぼすべての時間) であってもメモリを消費するため、リソースの非効率な使用につながります。 この問題に対処するために、起動時に各種類のコンパイラ スレッドを 1 つだけ開始するように実装が変更されています。 追加スレッドの開始と、未使用スレッドのシャットダウンは、動的に処理されます。
コア ライブラリに対する次の変更は、新規または変更されたコードのパフォーマンスに影響を与えます。
JEP 193: 変数ハンドル [19] - オブジェクト フィールドと配列要素に対してさまざまな java.util.concurrent.atomic 操作と sun.misc.Unsafe 操作と同等の操作を呼び出す標準の手段、メモリ順序をきめ細かく制御するためのフェンス操作の標準セット、参照先オブジェクトが厳密に到達可能な状態を維持するための標準到達可能性フェンス操作を定義します。
JEP 269: コレクションの Convenience Factory メソッド [20] - 少数の要素を含むコレクションとマップのインスタンスを簡単に作成できるようにライブラリ API を定義します。 コンパクトで変更できないコレクション インスタンスを作成する、コレクション インターフェイスの静的ファクト リメソッドです。 これらのインスタンスは、本質的に効率的です。 API により、コンパクトに表現され、ラッパー クラスを持たないコレクションが作成されます。
JEP 285: Spin-Wait ヒント [21] - Java がスピン ループ内にあることをランタイム システムにヒントできるようにする API を提供します。 特定のハードウェア プラットフォームは、スレッドがビジーウェイト状態にあることを示すソフトウェアの恩恵を受けます。
JEP 321: HTTP クライアント (Standard) [22]- HTTP/2 と WebSocket を実装し、従来の HttpURLConnection API を置き換えることができる新しい HTTP クライアント API を提供します。
リファレンス
[1] Oracle Corporation、"Java Development Kit 9 リリース ノート"、(オンライン)。 参照先: https://www.oracle.com/technetwork/java/javase/9u-relnotes-3704429.html. (2019 年 11 月 13 日にアクセス)。
[2] Oracle Corporation、"Java Development Kit 10 リリース ノート"、(オンライン)。 参照先: https://www.oracle.com/technetwork/java/javase/10u-relnotes-4108739.html. (2019 年 11 月 13 日にアクセス)。
[3] Oracle Corporation、"Java Development Kit 11 リリース ノート"、(オンライン)。 参照先: https://www.oracle.com/technetwork/java/javase/11u-relnotes-5093844.html. (2019 年 11 月 13 日にアクセス)。
2017年9月22日、Oracle Corporationの「Projectジグソー」。(オンライン)。 参照先: http://openjdk.java.net/projects/jigsaw/. (2019 年 11 月 13 日にアクセス)。
[5] Oracle Corporation,"JEP 328: Flight Recorder" 2018 年 9 月 9 日。 (オンライン)。 参照先: http://openjdk.java.net/jeps/328. (2019 年 11 月 13 日にアクセス)。
2019 年4月25日 (オンライン)。 参照先: https://wiki.openjdk.java.net/display/jmc/Main. (2019 年 11 月 13 日にアクセス)。
[7] Oracle Corporation、"JEP 158: Unified JVM Logging"、2019 年 2 月 14 日。 (オンライン)。 参照先: http://openjdk.java.net/jeps/158. (2019 年 11 月 13 日にアクセス)。
[8] Oracle Corporation、"JEP 331: Low-Overhead ヒープ プロファイリング"、2018 年 9 月 5 日。 (オンライン)。 参照先: http://openjdk.java.net/jeps/331. (2019 年 11 月 13 日にアクセス)。
[9] Oracle Corporation、"JEP 259: Stack-Walking API"、2017 年 7 月 18 日。 (オンライン)。 参照先: http://openjdk.java.net/jeps/259. (2019 年 11 月 13 日にアクセス)。
[10] Oracle Corporation、"JEP 248: G1 を既定のガベージ コレクターにする" 2017 年 9 月 12 日。 (オンライン)。 参照先: http://openjdk.java.net/jeps/248. (2019 年 11 月 13 日にアクセス)。
2018 年9月24日、Oracle Corporation"JEP 318: Epsilon: a No-Op ガベージ コレクター" (オンライン)。 参照先: http://openjdk.java.net/jeps/318. (2019 年 11 月 13 日にアクセス)。
[12] Oracle Corporation、"JDK-8146115: Docker コンテナーの検出とリソース構成の使用の向上" 2019 年 9 月 16 日。 (オンライン)。 参照先: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8146115. (2019 年 11 月 13 日にアクセス)。
2017 年 6 月 22 日、Oracle Corporation "JEP 238: Multi-Release JAR Files"。 (オンライン)。 参照先: http://openjdk.java.net/jeps/238. (2019 年 11 月 13 日にアクセス)。
[14] Oracle Corporation、"JEP 197: Segmented Code Cache"、2017 年 4 月 28 日。 (オンライン)。 参照先: http://openjdk.java.net/jeps/197. (2019 年 11 月 13 日にアクセス)。
2019 年5月18日、Oracle Corporation"JEP 254: Compact Strings"。 (オンライン)。 参照先: http://openjdk.java.net/jeps/254. (2019 年 11 月 13 日にアクセス)。
[16] Oracle Corporation、"JEP 310: アプリケーション Class-Data共有"、2018 年 8 月 17 日。 (オンライン)。 参照先: https://openjdk.java.net/jeps/310. (2019 年 11 月 13 日にアクセス)。
2019年8 月21日、Oracle Corporation「JEP 312: Thread-Localハンドシェイク」。 (オンライン)。 参照先: https://openjdk.java.net/jeps/312. (2019 年 11 月 13 日にアクセス)。
[18] Oracle Corporation、"JDK-8198756: コンパイラ スレッドの遅延割り当て"、2018 年 10 月 29 日。 (オンライン)。 参照先: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8198756. (2019 年 11 月 13 日にアクセス)。
[19] Oracle Corporation、"JEP 193: 可変ハンドル"、2017 年 8 月 17 日。 (オンライン)。 参照先: https://openjdk.java.net/jeps/193. (2019 年 11 月 13 日にアクセス)。
[20] Oracle Corporation,"JEP 269: Convenience Factory Methods for Collections, June 26, 2017. (オンライン)。 参照先: https://openjdk.java.net/jeps/269. (2019 年 11 月 13 日にアクセス)。
[21] Oracle Corporation、"JEP 285: Spin-Wait ヒント"、2017 年 8 月 20 日。 (オンライン)。 参照先: https://openjdk.java.net/jeps/285. (2019 年 11 月 13 日にアクセス)。
[22] Oracle Corporation、"JEP 321: HTTP Client (Standard)"、2018 年 9 月 27 日。 (オンライン)。 参照先: https://openjdk.java.net/jeps/321. (2019 年 11 月 13 日にアクセス)。