前回は、Java 25のJEPのうち初心者向け機能と小さいオブジェクトを扱う機能を紹介しました。今回はその続きです。
まずはProject Leyden関連のJEPです。
Project Leyden
Project Leydenは、Javaの起動時間を短縮することを目標にしたプロジェクトです。
さくらばは、てっきりGraalのネイティブイメージを取り込むだけなのかと思っていたのですが、そうではなく、様々な手段を用いて起動時間とピークパフォーマンスに達するまでの時間を短縮するための機能を提供するようです。
Java 24ではJEP 483が導入されましたが、Java 25はこの続きになるJEPが2つ導入されました。
JEP 514: Ahead-of-Time Command-Line Ergonomics
JEP 483はクラスロード、クラスの解析、クラス間のリンク、static初期化を行った後のイメージをキャッシュとして保存して、起動時にそれを読み込むことによって起動時間を短縮させます。
このキャッシュ(AOTキャッシュと呼ばれます)を作るには、トレーニング実行を行って、その後キャッシュ作成するという2段階が必要でした。
しかし、起動時オプションを変えて、2回実行するというのはちょっとめんどう...
ということで、JEP 514は1度の実行でトレーニング実行とAOTキャッシュ作成を行ってしまいましょうというものです。
AOTキャッシュを作成するには以下のように実行します。
- AOTキャッシュ作成
$ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App ...
AOTキャッシュを使って実行するのは、JEP 483と同じです。
- 本番実行
$ java -XX:AOTCache=app.aot -cp app.jar com.example.App ...
もちろん、JEP 483の2段階でAOTキャッシュを作成することも可能です。
JEP 515: Ahead-of-Time Method Profiling
HotSpot VMはアプリケーションを実行しながら、その片手間にプロファイリングもやっています。プロファイルイングの結果から、実行中にメソッドをネイティブコードにコンパイルするなどの最適化を行っています。
逆にいうと、ピークパフォーマンスに達するまでに時間がかかるということです。
Leydenで起動時間を短縮するのが必要なユースケースでは、なおさらピークパフォーマンスに達する時間が問題になりそうです。
そこで、JEP 515では、AOTキャッシュを作成するトレーニング実行中に作成したプロファイリングの解析結果をAOTキャッシュに含めるようになりました。
AOTキャッシュを使って実行すると、プロファイリングを行わずにすぐにネイティブコンパイルなどの最適化を行うことができるので、ピークパフォーマンスに到達する時間を大幅に短縮することができます。
Project Leydenでは、この後、JEP 516: Ahead-of-Time Object Caching with Any GCやまだドラフトですがJEP draft: Ahead-of-Time Code Compilationなどが控えています。
これらのProject Leydenの機能に関して、9月のJJUGナイトセミナーでOracleのじゅくちょー阪田さんが解説してくれたのですが、その資料がまだ公開されていない...
もし、LeydenのAOTキャッシュに興味があるのであれば、資料が公開されるのを待ちましょう!
安全性、セキュリティ
JEP 510の鍵導出関数はとりあえずおいておいて、サポートできなくなったポーティングは削除しましょうというのがJEP 503です。
JEP 503: Remove the 32-bit x86 Port
32bit版のWindowsのサポートが今月終了するということで、Java 24で32bit版のWindowsのポーティングが削除されました。また、x86のポーティングも削除予定になっていました。
そして、Java 25でx86のポーティングが削除されました。
サポートされないOSに対しては安全性の問題もあるので、削除はしかたないですね。
モニタリング
OpenJDKでのモニタリングとプロファイリングといえば、JDK Flight Recorder (JFR)ですね。
Java 25では、このJFR関連のJEPが3つあります。1つはExperimentalなので後述することにして、残りの2つについて簡単に紹介します。
とはいうものの、1つは内部実装の話なので、機能という感じではないですけど。
JEP 518: JFR Cooperative Sampling
非同期にスレッドのスタックトレースをサンプリングする部分の実装を見直して、安定性を高めたというJEP。
使い方の変更はないので、Java 25のJFRであれば安定してスタックトレースを取得できるようになるはずです。
JEP 520: JFR Method Timing & Tracing
メソッドがコールされたタイミングや回数などをサンプリングではなく、実測で調べるためのJFRのイベントが追加されました。
追加されたイベントはjdk.MethodTimeingとjdk.MethodTraceの2種類です。
これらのイベントを使用することで、メソッドがコールされたタイミングとメソッドの処理時間、そしてスタックトレースを取得することができます。
その他
Standard JEPの残った2つは、Project Loom関連のScoped Valueと、GCのShenandoah GCに関連したJEPです。
JEP 506: Scoped Values
ThreadLocalクラスはミュータブルでいろいろと問題のあるクラスですが、それをある程度置き換えることができるのがScopedValueクラスです。
すべてではないのはScopedValueクラスがイミュータブルなクラスなので、ThreadLocalクラスでsetメソッドを使っているようなケースは置き換えられないからです。
使い方は簡単で、ScopedValueインスタンスをnewInstance()メソッドで作成しておいて、そこにstaticメソッドのwhere()メソッドで保持する値をバインドさせます。
where()メソッドの返り値の型はScopedValue.Carrierクラスです。このScopedValue.Carrierクラスのrun()メソッド、もしくはcall()メソッドで実行するタスク内だけでScopedValueオブジェクトにバインドしたデータを使用できます。
つまり、run()メソッドとcall()メソッドで指定する関数だけがスコープになるわけです。
たとえば、時間を共有したい場合を考えてみます。
// スコープで共有するScopedValueオブジェクト
final ScopedValue<LocalDateTime> TIME = ScopedValue.newInstance();
void task1() {
// get()メソッドで共有したデータを取得
System.out.println("Task1: " + TIME.get());
}
void task2() {
// get()メソッドで共有したデータを取得
System.out.println("Task2: " + TIME.get());
}
void main() {
Runnable r = () -> {
// whereメソッドでTIMEに現在時刻をバインド
// runメソッドで指定するラムダ式の中だけでバインドしたデータを共有
ScopedValue.where(TIME, LocalDateTime.now())
.run(() -> {
task1();
task2();
});
};
try (var executor = Executors.newCachedThreadPool()) {
executor.submit(r);
}
}
とはいうものの、ThreadLocalクラスやScopedValueクラスを使ったデータの共有はできるだけ避ける方がよいです。共有したデータのせいでスケールしないことも多いですし、バグも発生しやすいです。
ScopedValueクラスを安易に使うよりは、もう一度設計を見直すことの方が賢明だと思います。
たとえば、上のコードでもtask1()メソッドとtask2()メソッドの引数として時間を渡した方が分かりやすいですよね。共有せずに済むのであれば、共有しない設計にすべきです。
JEP 521: Generational Shenandoah
Red Hatが進めていたShenandoah GCは、以前のZGCのように世代別GCではなかったのですが、ZGCの後を追うように世代別GCになりました。
Shenandoahで世代別モードにするには、まず実行時オプションの-XX:+UseShenandoahGCでGCにShenandoahを指定します。そして、同じく実行時オプションで-XX:ShenandoahGCMode=generationalを指定します。
Preview
ここからはお試し機能。まずは、Preview JEPからです。
Preview JEPは4種類。JEP 507はJava 25でStandardになると予想していたんですけど、Previewでしたね。残念。
JEP 470: PEM Encodings of Cryptographic Objects (Preview)
1つ目のPreview JEPは鍵導出関数に関するAPIです。
JEP 502: Stable Values (Preview)
StableValueクラスはfinalなフィールドに対して遅延初期化を行うためのクラスです。
それにしても、java.langパッケージにScopedValueクラスとStableValueクラスという似たようなクラスができると混乱するよなぁ... と思っていたら、Java 26がターゲットのJEP 526ではLazyConstantクラスに名前が変わりました!
LazyConstantクラスに関してはJJUG CCC 2025 Fallでプレゼンすることになったので、その時に詳しく解説します。
JEP 505: Structured Concurrency (Fifth Preview)
Structured ConcurrencyはなかなかStandard JEPになりませんが、複数の非同期実行のタスクの結果をまとめることなどに使用するAPIです。
Java 25で5回目のPreviewですが、APIに変更があったためJava 26でもう一度Previewということになっています(JEP 525)。
Java 24まではStructuredTaskScopeクラスのインスタンシエーションにはnewを使用していたのですが、Java 25でファクトリーメソッドのopen()メソッドが導入されています。
また、全部成功したらとか、1つでも失敗したらなどの条件を表すために、Java 24まではStructuredTaskScopeクラスのサブクラスで表していましたが、JEP 505ではStructuredTaskScope.Joinerインタフェースに委譲するようになりました。この方が分かりやすいですし、今後条件の拡張しやすくていいですね。
JEP 507:Primitive Types in Patterns, instanceof, and switch (Third Preview)
パターンマッチングにプリミティブ型を使用できるようにするのがJEP 507です。
JEP 507は変更なしだったのですが、まだドラフトなのですが次のJEPで変更が入ったので、Standardになるのは当分先になってしまいました。残念。
Experimental
Experimental JEPは1つだけで、JFRの機能追加に関してです。
JEP 509: JFR CPU-Time Profiling (Experimental)
CPUの計測を行うJFRのイベントが追加されました。ただし、Linuxだけです。
イベントはjdk.CPUTimeSampleです。
今のところ、新しいJEPは提案されていないようなので、Java 26でStandard JEPになるかもしれません。
Incubator
最後のIncubator JEPは、Value Classが導入するまでずっとIncubatorのままのあれです。
JEP 508: Vector API (Tenth Incubator)
ベクトル計算を行うためのVector APIですが、JEP 508ではいくつかの変更がありました。ただ、Value Classが導入されるまでIncubatorのままなので、今それを調べてもなぁ... と調べるモチベーションが上がりません😰
まとめ
Java 25はLTSということもあり、Standard JEPが多いリリースでした。
特にJEP 512のメインクラスの省略は意外に便利です。また、JEP 506のScoped Valueも正式化したので、今後ThreadLocalの置き換えが進むかもしれません。
ところで、Java 25とは関係ないのですが、ずっとドラフトのままだったValue Classがサブミットされて、通常のJEPにやっとなりました。JEP 401: Value Classes and Objectsです。
まだ、ターゲットバージョンが決まっていないのですが、早ければJava 26からPreviewが始まるかもしれません。
いつ導入されるのか全然わからなかったValue Classですが、やっと一歩踏み出した感じですね。
0 件のコメント:
コメントを投稿