Java 24が2025/3/18にリリースされました。
Java 24 / JDK 24: General Availability
The Arrival of Java 24
オラクル、Java 24をリリース | Oracle 日本
LTSではないですが多くのJEPが入っています。Preview機能も次のLTSであるJava 25では標準化しそうなものが多く、予習のために触っておくのがいいと思います。
JDKをインストールせずに言語やライブラリの新機能を試したい場合にはJava Playgroundが便利です。
https://dev.java/playground/
イベント
新機能解説で東京と福岡で登壇します。
東京では3/26(水)に品川のマイクロソフトのオフィスで19時から行います。(終了しました)
【オフライン】JJUGナイトセミナー「Java 24 リリース記念イベント」3/26(水) 開催 - 日本Javaユーザーグループ/Japan Java User Group | Doorkeeper
福岡では4/11(金)にLINEヤフー博多深見オフィスで19時から行います。
Java 24リリースイベント@福岡 - connpass
資料
詳細はこちら
JDK 24 Release Notes
Java SE 24 Platform JSR 399
OpenJDK JDK 24 GA Release
APIドキュメントはこちら
Overview (Java SE 24 & JDK 24)
追加されたAPIまとめはこちら
https://docs.oracle.com/en/java/javase/24/docs/api/new-list.html
APIの差分はこちら。
https://cr.openjdk.org/~iris/se/24/build/latest/java-se--jdk-23-ga--jdk-24%2B36/
ディストリビューション
MacやLinuxでのインストールにはSDKMAN!をお勧めします。
Oracle OpenJDK以外に無償で商用利用できるディストリビューションとしては、次のようなものがあります。
Microsoft Buildは非LTSはお休み。
どのディストリビューションを使えばいいかわからないときは、とりあえずAdoptium Temurinが無難です。
アップデートは4月に24.0.1が、7月に24.0.2がリリースされることになります。
開発組織
関わった開発組織は、こんな感じになってます。Java 23に比べると、AmazonとAlibabaが増えてるかな。あとArmがちょっと増えてるのはArm Windows対応だろうか。
JEP
大きめの変更はJEPでまとまっています。
https://openjdk.org/projects/jdk/24/
今回は24個のJEPが取り込まれました。正式採用は14個です。プレビューからの正式化はClass-File APIとStream Gatherersの2つです。プレビューは10個で、そのうち3つが新たに入ったものです。
今回は次の6つのカテゴリにまとめてます
404: Generational Shenandoah (Experimental)
450: Compact Object Headers (Experimental)
472: Prepare to Restrict the Use of JNI
475: Late Barrier Expansion for G1
478: Key Derivation Function API (Preview)
479: Remove the Windows 32-bit x86 Port
483: Ahead-of-Time Class Loading & Linking
484: Class-File API
485: Stream Gatherers
486: Permanently Disable the Security Manager
487: Scoped Values (Fourth Preview)
488: Primitive Types in Patterns, instanceof, and switch (Second Preview)
489: Vector API (Ninth Incubator)
490: ZGC: Remove the Non-Generational Mode
491: Synchronize Virtual Threads without Pinning
492: Flexible Constructor Bodies (Third Preview)
493: Linking Run-Time Images without JMODs
494: Module Import Declarations (Second Preview)
495: Simple Source Files and Instance Main Methods (Fourth Preview)
496: Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism
497: Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm
498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe
499: Structured Concurrency (Fourth Preview)
501: Deprecate the 32-bit x86 Port for Removal
24個というのは、半年リリースがまわりだしたJava 10以降では最大ですね。
言語機能
言語機能の変更としては、新しいものはなく正式化されたものもないですが、Java 25 LTSに向けて慎重に評価している状況のようです。Primitive Types in Patterns...以外は、Java 25で正式化しそう。
488: Primitive Types in Patterns, instanceof, and switch (Second Preview)
492: Flexible Constructor Bodies (Third Preview)
494: Module Import Declarations (Second Preview)
495: Simple Source Files and Instance Main Methods (Fourth Preview)
488: Primitive Types in Patterns, instanceof, and switch (Second Preview)
Java 23でプレビューとして入りましたが、変更なく2ndプレビューになりました。3rdプレビューのJEPが用意されているので、Java 25LTSでの正式化はなさそう。
instanceof
instanceofでプリミティブを使うとき、値が欠落しないかどうかが判定基準になります。
jshell> int i = 128
i ==> 128
jshell> i instanceof byte
$2 ==> false
jshell> i = 127
i ==> 127
jshell> i instanceof byte
$4 ==> true
doubleでも精度落ちするかどうかが判定されます。
jshell> double d = 1
d ==> 1.0
jshell> d instanceof int
$2 ==> true
jshell> d = 1.2
d ==> 1.2
jshell> d instanceof int
$4 ==> false
ただし、ラッパークラスのオブジェクトの場合はその基本型のときだけtrueになります。
jshell> Long num = 123L
num ==> 123
jshell> num instanceof int
| エラー:
| 不適合な型: java.lang.Longをintに変換できません:
| num instanceof int
| ^-^
jshell> num instanceof long
$7 ==> true
そしてパターンマッチとして使えるわけですね。
jshell> long num = 123
num ==> 123
jshell> var out = new ByteArrayOutputStream()
out ==>
jshell> if (num instanceof byte b) out.write(b)
jshell> out.toByteArray()
$21 ==> byte[1] { 123 }
jshell> num = 1234
num ==> 1234
jshell> if (num instanceof byte b) out.write(b)
jshell> out.toByteArray()
$24 ==> byte[1] { 123 }
switch
このパターンマッチがswitchでも使えるということなんですけど、定数パターンもあるので、switchですべての型が使えるようになったということになります。
jshell> long num = 1234
num ==> 1234
jshell> switch (num) { case int n -> "いんと"; default -> "それ以外";}
$25 ==> "いんと"
jshell> num = 30_0000_0000L
num ==> 3000000000
jshell> switch (num) { case int n -> "いんと"; default -> "それ以外";}
$27 ==> "それ以外"
if式のようなこともできますね。
boolean flag = isFoo();
var s = switch (flag) {
case true -> "たった";
case false -> "おりた";
}
492: Flexible Constructor Bodies (Third Preview)
Java 22でStates before superだったものがJava 23で名前が変わり、大きな変更なく3rdプレビューになりました。PreviewのとれたJEPも用意されているので、Java 25で正式化しそうです。
スーパークラスのコンストラクタ呼び出しの前にthis
を使わないステートメントが書けるようになります。
例えばListを2つコンストラクタにとるクラスがあるとします。
class Foo {
Foo(List l1, List l2) {
}
}
このコンストラクタを継承するとき、Listをふたつ渡す必要があります。
class Bar extends Foo {
Bar() {
super(List.of("abc", "def"), List.of("abc", "def"));
}
}
同じものを渡しているので共通化したいですが、super
の前でステートメントを実行できないので迂回を考える必要があります。
class Bar extends Foo {
private Bar(List l) {
super(l, l);
}
Bar() {
this(List.of("abc", "def"));
}
}
これを、次のように書けるようになります。
class Bar extends Foo {
Bar() {
var param = List.of("abc", "def");
super(param, param);
}
このように、パラメータの検証や構築、共有がある場合に、自然なコードで書けるようになります。
次のようなフィールドのアクセスをsuper
の前に行うとエラーになります。
class Bar extends Foo {
List<String> param;
Bar() {
param = List.of("abc", "def");
super(param, param);
}
}
test.java:11: エラー: スーパータイプのコンストラクタの呼出し前はparamを参照できません
param = List.of("abc", "def");
^
494: Module Import Declarations (Second Preview)
Java 23でプレビューとして入り、2つの変更と共に2ndプレビューになりました。PreviewのとれたJEPも用意されているので、Java 25で正式化されそう。
モジュールごとのimportができるようになりました。java.baseをimportしたらだいたいいけるってなりそうです。あと、外部ライブラリのモジュール対応が進みそうです。
Java 23のときからの変更で、依存のあるモジュールもimportされるようになりました。そのためjava.seをimportすると、Java SEのすべてのAPIがimportされます。
次のようにすると、java.baseモジュールに属するjava.utilもjava.ioもimportされることになります。
import module java.base;
JShellでも--enable-preview
をつけるとデフォルトでjava.baseがimportされるようになります。
例えば、java.timeはimportされていなかったのでLocalDateをそのまま使うとエラーになります。
>jshell
| JShellへようこそ -- バージョン23
| 概要については、次を入力してください: /help intro
jshell> LocalDate.now()
| エラー:
| シンボルを見つけられません
| シンボル: 変数 LocalDate
| 場所: クラス
| LocalDate.now()
| ^-------^
--enable-preview
を付けるとimport不要で使えます。
>jshell --enable-preview
| JShellへようこそ -- バージョン23
| 概要については、次を入力してください: /help intro
jshell> LocalDate.now()
$1 ==> 2024-10-11
SwingでGUIプログラムをする場合にはjava.desktop
モジュールをimportします。
jshell> import module java.desktop;
jshell> var f = new JFrame("hello")
f ==> javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden, ... tPaneCheckingEnabled=true]
jshell> var b = new JButton("OK")
b ==> javax.swing.JButton[,0,0,0x0,invalid,alignmentX=0 ... xt=OK,defaultCapable=true]
ただ、そうするとList
がjava.util.List
とjava.awt.List
でかぶるので、あいまいになってそのままで使えなくなります。
jshell> List a = null;
| エラー:
| Listの参照はあいまいです
| java.awtのクラス java.awt.Listとjava.utilのインタフェース java.util.Listの両方が一致します
| List a = null;
| ^--^
この場合にはjava.util.List
など実際に使うほうをimportするなどが必要です。
jshell> import java.util.List
jshell> List a = null
a ==> null
495: Simple Source Files and Instance Main Methods (Fourth Preview)
Java 23で3rdプレビューになっていたImplicitly Declared Classes and Instance Main Methodsが名前をSimple Source Files and Instance Main Methodsと変えて4thプレビューになりました。プレビューの取れたJEPも用意されているため、Java 25では標準機能になりそうです。名前はCompact Source Files...になるみたい。こういった仕様名のこだわりもいいですね。
パブリックスタティックヴォイドメインの呪文から解放されるやつです。
Javaがパブリックスタティックヴォイドメインの呪文から解放される - きしだのHatena
Javaでは単純なハローワールドを書くために次のようなコードが必要でした。
public class Hello {
public static void main(String[] args) {
System.out.println("Hello Java!");
}
}
これが次のように書けるようになります。
void main() {
println("Hello Java!");
}
プレビュー機能なので、--enable-preview
が必要です。javac
では--source 23
も必要になります。ソースを直接実行する場合はjava
コマンドでも--source 23
が必要です。
>more Hello.java
void main() {
println("Hello Java!");
}
>java --enable-preview --source 23 Hello.java
ノート: Hello.javaはJava SE 23のプレビュー機能を使用します。
ノート: 詳細は、-Xlint:previewオプションを指定して再コンパイルしてください。
Hello Java!
public
やclass
、static
などのキーワードが消え、[]
という謎の記号も消えました。
プログラムを勉強するときに、まずやりたいことは処理を書くことです。
クラスは書いた処理をうまく構成するための仕組みなので、処理が書けないうちに勉強してもあまり意味がありません。
public
などアクセス指定はプログラムが大きくなったときに不適切な要素を使ってしまわないための仕組みなので、入門時のサンプルでは不要です。
static
を説明するにはクラスやインスタンスの理解が必要になりますが、処理が書ける前に勉強するには早すぎます。
配列も変数を知らないうちに勉強できるものでもなく、入門時のサンプルで引数args
を使うことはあまりありません。
その結果「よくわからないしきたり」のまま放置されがち、というか放置せざるを得ない状態で「System.out.pritlnというのは~」という説明をすることになりますが、クラス名とファイル名が違うので動かせなくてハマってそこまでたどりつけなかったりもします。
ということで、初期に学習するべきことに集中できるようにするために、次のように制約が緩和されました。
- クラスの定義が不要になる
- mainメソッドはインスタンスメソッドでよくなる
- mainメソッドの引数を省略できる
- mainメソッドがpublicじゃなくてもよくなる
「メソッドも不要でいいのでは?」となると思いますが、現状ではステートメントとメソッドを同レベルで書く仕組みがないため、新たにローカルメソッドのような仕組みが必要になり、「初期に学習するべきことに集中できるようにするため」としては影響範囲が大きいので残されています。
このあたりは、次のデザインノートにまとめられています。
https://openjdk.org/projects/amber/design-notes/on-ramp
mainメソッドはインスタンスメソッドでもよくなる
mainメソッドにstaticをつけなくてもよくなります。そして、mainメソッドにstaticをつけなくてもいいということは、そこから呼び出すメソッドなどにもstaticをつけなくていいということになるので、少し大きめのサンプルが書きやすくもなります。
public class Hello {
public void main(String[] args) {
foo();
}
void foo() {
System.out.println("Hello");
}
}
mainメソッドの引数を省略できる / mainメソッドがpublicじゃなくてもよくなる
書かなくてよさそうなものを書かずにすむのですっきりします。
mainメソッドをprivateにすることはできません。protectedは可能です。
「public static void main(String[] args)」を何も見ずに書けるようになったときにJavaに馴染んだ満足感があったので、それがなくなるのは寂しいですが、単なるノスタルジーなのでなくていいと思います。
クラスの定義が不要
クラスを知らなくていいことの他に、クラス名を考えなくていいとかインデントが一段浅くなるとか、中カッコが一組だけになるので間違いが減るとか、いろいろ入門がやりやすくなります。
クラスを省略してmainメソッドにインスタンスメソッドを使うと、new Object(){}
で囲まれることになりました。
new Object() {
void main() {
println("Hello Java");
}
}.main();
ただ、クラス定義を省略する場合、ファイル名にstatic.javaなどキーワードを付けるとエラーになります。クラス定義がある場合、java
コマンドで直接実行であれば問題ないです。
>java --enable-preview --source 23 static.java
static.java:3: エラー: 不正なファイル名: static
void main() {
^
エラー1個
エラー: コンパイルが失敗しました
java.lang.IOがstatic importされる
java.lang.IO
というクラスが導入されました。このクラスには、print
、println
、readln
の3つのstaticメソッドが定義されています。
クラス定義を省略して暗黙のクラスを使う場合、これらのメソッドがstaticインポートされるので、System.out
を書く必要がなくなります。
(Java 25ではimportされない模様)
ただし、クラスを定義する場合には自動インポートは行われないので、System.out
を書くか明示的にインポートが必要です。
先ほどの例でSystem.out
を書いていましたが、java.io.IO
をstaticインポートすることで省略できます。
import static java.io.IO.*;
public class Hello {
public void main(String[] args) {
foo();
}
void foo() {
println("Hello");
}
}
java.baseがmodule importされる
暗黙のクラスを使う場合、java.baseモジュールもimportされます。
こういうコードはimportなしで書けるようになります。
void main() {
var data = List.of("test", "data");
println(data.stream().collect(Collectors.joining(" ")));
}
API
APIの変更としてはセキュリティ系を除いて7つのJEPがありますが、新しく入ったのはJNI使用制限の準備とUnsafeでのメモリアクセス時の警告です。JEPになっていない変更が少しあります。
472: Prepare to Restrict the Use of JNI
484: Class-File API
485: Stream Gatherers
487: Scoped Values (Fourth Preview)
489: Vector API (Ninth Incubator)
498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe
499: Structured Concurrency (Fourth Preview)
小さいもの
JEPになっていないAPI変更で、動きがわかりやすいものを挙げます。
Reader.of(CharSequence)
文字列を読み込むReaderを生成します。
var r = Reader.of("hogehoge\nfoobar");
var br = new BufferedReader(r);
println(r.readLine());
// hogehoge
Unicode 16サポート
Java 11でのUnicode 10サポートまではJEPがつくられていましたが、最近はだまって対応が進んでますね。
https://bugs.openjdk.org/browse/JDK-8319993
API以外の仕様のドキュメント
External specificationsというページがAPIドキュメントに追加されています。
https://docs.oracle.com/en/java/javase/24/docs/api/external-specs.html
472: Prepare to Restrict the Use of JNI
Java 22のJEP 454でForeign Memory & Function API(FFM)が標準化され、そのときFFMに関してはネイティブ呼び出しが可能かどうか指定する--enable-native-access
などのオプションが用意されました。これらのオプションをJNIでも有効にします。
JNIの非推奨や削除は目的ではないようです。
JNIを利用する場合に、アプリケーション開発者による明示的な許可を求めることができるようにする準備です。
現状では、アプリケーション開発者による許可がないJNI呼び出しは警告が出るようです。将来的には例外を出すことがデフォルトになります。
例えば次のようにして、ネイティブアクセスを完全に許可できますが、これはFFMで導入されたものです。
java --enable-native-access=ALL-UNNAMED
--illegal-native-access
で許可されないネイティブアクセスがあったときの処理を指定できます。allow
は素通り。warn
が警告で現在のデフォルトですが将来的には削除されるようです。deny
は例外送出で今後のバージョンでのデフォルトになります。
--illegal-native-access=allow
--illegal-native-access=warn
--illegal-native-access=deny
484: Class-File API
Javaクラスファイルを解析、変更、生成するためのAPIです。Java 23で2ndプレビューになっていましたが、少し変更されて正式化しました。
ASMやJavassistなど、同様の機能を実装するライブラリがありますが、Javaではjavacという標準のクラスファイル生成ツールがあります。また、内部的にASMを使っている部分もあります。
ただ、ASMのように外部ライブラリを使うと、こういった外部ライブラリの新機能対応は新バージョンがリリースされるまで正式化しません。そうすると、Javaの新機能をJava内部で使いたいとき、そのASMは非公式であるかひとつ前のバージョン対応であるかということになってしまいます。
そこで、Java標準としてクラスファイル操作のAPIを提供するということになりました。
485: Stream Gatherers
Java 22でプレビューとして導入され、Java 23でそのまま2ndプレビューになりましたが、変更なくJava 24で正式機能になりました。
Streamでは、前の値を参照するような処理や、処理順序を前提とした処理が行えません。そのため、前の値にどんどん手を加えてリストを作ったり、移動平均をとったりといったことができませんでした。
そういった、順番を保証して前の値を踏まえた処理が行えるようにするのが、Gathererです。
デザインノートはこちら
https://cr.openjdk.org/~vklang/Gatherers.html
Collectorと同じで自前のGathererを実装するのは大変そうですが、CollectorsのようにGatherersが用意されています。ここでは、Gatherersだけ紹介します。
Streamが使えそうで使えなかった処理に、Streamが使える場合が増えそうです。
JShellを使っていますが、見やすいように改行しているので、そのとおりに入力はできません。試すときは改行せず一行で入力してください。
windowFixed
要素を指定した個数分まとめてListにします。
jshell> IntStream.range(0, 10).boxed()
.gather(Gatherers.windowFixed(3))
.toList()
$1 ==> [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]]
windowSliding
スライディングウィンドウです。指定した要素分を、要素をずらしながらとっていってListをつくります。
jshell> IntStream.range(0, 10).boxed()
.gather(Gatherers.windowSliding(3))
.toList()
$2 ==> [[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4, 5],
[4, 5, 6], [5, 6, 7], [6, 7, 8], [7, 8, 9]]
mapConcurrent
指定した個数分、並列に処理を行いながらmapします。このとき並列処理にはVirtualThreadが使われます。
jshell> IntStream.range(0, 10).boxed()
.gather(Gatherers.mapConcurrent(4, n -> n * 2))
.toList()
$3 ==> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
処理結果としてはmapと同じになります。
jshell> IntStream.range(0, 10).boxed()
.map(n -> n * 2)
.toList()
$4 ==> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
scan
計算を追加していきます。
jshell> IntStream.range(0, 10).boxed()
.gather(Gatherers.scan(() -> "*", (s, n) -> s + n))
.toList()
$7 ==> [*0, *01, *012, *0123, *01234, *012345,
*0123456, *01234567, *012345678, *0123456789]
fold
処理順を前提としたreduce処理です。
jshell> IntStream.range(0, 10).boxed()
.gather(Gatherers.fold(() -> "*", (s, n) -> s + n))
.toList()
$8 ==> [*0123456789]
reduceではBinaryOperatorをとるので、要素と結果の型が同じになる必要があります。foldはBiFunctionをとるので、値と結果の型が同じである必要はありません。
jshell> IntStream.range(0, 10).boxed().reduce(0, (a, b) -> a + b)
$9 ==> 45
Collectors.reducingと違うのは、parallel streamでも処理順が保証されることです。
jshell> IntStream.range(0, 10).parallel().boxed()
.collect(Collectors.reducing(
"*", n -> "" + n, (s, t) -> s + t))
$12 ==> "*0*1*2*3*4*5*6*7*8*9"
あと、終端処理が必要になりますね。
jshell> IntStream.range(0, 10).parallel().boxed().gather(Gatherers.fold(() -> "*", (s, n) -> s + n)).findAny().get()
$14 ==> "*0123456789"
487: Scoped Values (Forth Preview)
Java 20でIncubateになりJava 21でプレビュー、そのままJava 22で2ndプレビューになりましたが、少し変更が加わって3rd Previewになりました。さらにcallWhere、runWhereメソッドがScopedValueクラスから削除されて4th Previewになりました。
同じスレッド内で値を共有したいときThreadLocal
を使いますが、値の変更が可能であったり子スレッドに値が引き継がれたり少し重いので、より限定された仕組みを提供する、ということのようです。
つまり、値を引数でひっぱりまわすのは面倒なのでグローバル変数的にstaticフィールドを使いたい程度のモチベーションで値を共有化するときに、スレッドセーフのためのThreadLocalは重すぎる、という感じですね。
たとえば次のような処理があります。
void start() {
proc1("test");
}
void proc1(String str) {
System.out.println(str);
proc2(str);
}
void proc2(String str) {
System.out.println(str);
}
これを、全部のメソッドにいちいち引数を設定して値をひきまわるのは面倒なのでフィールドを使おう、という場合。
String str;
void start() {
str = "test";
proc1();
}
void proc1() {
System.out.println(str);
proc2();
}
void proc2() {
System.out.println(str);
}
これは複数スレッドから呼び出されると正しく動かないことがあります。
スレッドセーフにするためにThreadLocal
を使っていました。
final ThreadLocal<String> VAR = new ThreadLocal<>();
void start() {
VAR.set("test");
proc1();
}
void proc1() {
System.out.println(VAR.get());
proc2();
}
void proc2() {
System.out.println(VAR.get());
}
しかし、引数を書いて値を持ちまわっていくのめんどいね、くらいのモチベーションで使うにはThreadLocal
は重過ぎるので、軽量な値共有手段としてScopedValue
が導入されます。
final ScopedValue<String> VAR = new ScopedValue<>();
void start() {
ScopedValue.where(VAR, "test")
.run(() -> proc1());
}
void proc1() {
System.out.println(VAR.get());
proc2();
}
void proc2() {
System.out.println(VAR.get());
}
489: Vector API (Ninth Incubator)
AVX命令のような、複数のデータに対する計算を同時に行う命令をJavaから利用できるようになります。
使うためには実行時やコンパイル時に--add-modules jdk.incubator.vector
をつける必要があります。
Java 16でインキュベータとして導入されたPanamaプロジェクトの残る片割れですが、Java 23からいくつか変更されて9th Incubatorになりました。Project Valhallaのvalue classを使いたいようで、関連JEPがpreviewになるまではIncubatorのままということです。
MemorySegment
にはバイト配列に対してVectorアクセスができていましたが、プリミティブ配列にVectorアクセスできるようになったようです。
基本的な使い方は次のようになります。
import jdk.incubator.vector.*;
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;
void vectorComputation(float[] a, float[] b, float[] c) {
for (int i = 0; i < a.length; i += SPECIES.length()) { // SPECIES.length() = 256bit / 32bit -> 8
VectorMask<Float> m = SPECIES.indexInRange(i, a.length); // 端数がマスクされる
// a.lengthが11でiが8のとき最初の3つしか要素がないので [TTT.....]
// FloatVector va, vb, vc;
FloatVector va = FloatVector.fromArray(SPECIES, a, i, m);
FloatVector vb = FloatVector.fromArray(SPECIES, b, i, m);
FloatVector vc = va.mul(va).
add(vb.mul(vb)).
neg();
vc.intoArray(c, i, m);
}
}
利用できるのは次の6つの型です。それぞれに対応するVector型があって、これが基本になります。
型 | bit幅 | Vector |
---|---|---|
byte | 8 | ByteVector |
short | 16 | ShortVector |
int | 32 | IntVector |
long | 64 | LongVector |
float | 32 | FloatVector |
double | 64 | DoubleVector |
ただ、利用するにはVectorSpeciesが必要です。利用したいVectorにSPECIES_*という定数が用意されているので、それを使います。*は一度に計算するbit数ですね。
jshell> FloatVector.SP
SPECIES_128 SPECIES_256 SPECIES_512
SPECIES_64 SPECIES_MAX SPECIES_PREFERRED
MAXではそのハードウェアで使える最大、PREFERREDは推奨ビット数だけど、同じになるんじゃないのかな。ここでは256bitが推奨されて、floatが8個同時に計算できるようになっていますね。
jshell> FloatVector.SPECIES_PREFERRED
$11 ==> Species[float, 8, S_256_BIT]
ハードウェアで使えるbit数は搭載CPUに依存しますが、普通のIntel/AMDであれば256、XEONとか つよつよCPUなら512かな。M1は128でした。ハードウェアでサポートされないbit数を使おうとするとソフトウェア処理になるので遅くなります。
実際のVectorはfrom*というメソッドで取得します。fromArray、fromByteArray、fromByteBufferが用意されています。インキュベータに入る前はfromValuesがあったのですが、なくなってますね。
Vectorを得られたら、用意されたメソッドで計算します。ひととおりの算術命令はあります。
jshell> va.
abs() add( addIndex(
bitSize() blend( broadcast(
byteSize() castShape( check(
compare( compress( convert(
convertShape( div( elementSize()
elementType() eq( equals(
expand( fma( getClass()
hashCode() intoArray( intoMemorySegment(
lane( lanewise( length()
lt( maskAll( max(
min( mul( neg()
notify() notifyAll() pow(
rearrange( reduceLanes( reduceLanesToLong(
reinterpretAsBytes() reinterpretAsDoubles() reinterpretAsFloats()
reinterpretAsInts() reinterpretAsLongs() reinterpretAsShorts()
reinterpretShape( selectFrom( shape()
slice( species() sqrt()
sub( test( toArray()
toDoubleArray() toIntArray() toLongArray()
toShuffle() toString() unslice(
viewAsFloatingLanes() viewAsIntegralLanes() wait(
withLane(
ところで、こういったメソッド呼び出しの内部でAVX命令などを呼び出すのでは遅くなるんではという気がしますが、実際にはJVM intrinsicsという仕組みでJITコンパイラがこれらのメソッド呼び出しをネイティブ関数呼び出しに置き換えます。
498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe
Foreign memory APIが正式化されて代替手段が整ったことで、sun.misc.Unsafe
でのメモリアクセスAPIがJava 23で非推奨になりました。
今回のJava 24では実行時警告が出るようになっています。
Java 25 LTSではそのまま残り、Java 26以降で例外が発生するようになり、その後で削除されるという流れが予定されています。
499: Structured Concurrency (Fourth Preview)
Java 19でIncubatorとして含まれていましたが、Java 21、22、23から変更なく4th Previewになりました。ただ、5th previewのJEPが用意されているので、Java 25 LTSでの正式化はなさそうです。
並列処理では、複数の処理を実行するときに、両方が終われば正常終了とか、どちらか片方が終われば終了だとか、どちらか一方でも例外が発生したら終了だとか、同時に行う処理で連動することがあります。
しかし、これを既存のjoin
やwait
などで制御しようとすると、実際にはjoin
からwait
へのGo Toを書くようなコードになって、処理が追えなくなります。
そこで導入されるのが構造化並列性といいます。
こんな感じ。詳しくはあとで書きます!(Java 19のときから言ってる・・・)
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Subtask<String> user = scope.fork(() -> findUser());
Subtask<Integer> order = scope.fork(() -> fetchOrder());
scope.join() // Join both forks
.throwIfFailed(); // ... and propagate errors
// Here, both forks have succeeded, so compose their results
return new Response(user.get(), order.get());
}
}
Subtask
はSupplier
を継承しているので、Supplier
として扱うほうがいいかもしれません。
JVM
JVMの変更として、セキュリティ関連以外では7つJEPが導入されています。
404: Generational Shenandoah (Experimental)
450: Compact Object Headers (Experimental)
475: Late Barrier Expansion for G1
479: Remove the Windows 32-bit x86 Port
483: Ahead-of-Time Class Loading & Linking
490: ZGC: Remove the Non-Generational Mode
491: Synchronize Virtual Threads without Pinning
404: Generational Shenandoah (Experimental)
Shenandoah GCに世代別GCを試験機能として導入します。
450: Compact Object Headers (Experimental)
JVM内でオブジェクトを保持するときに、64bitプラットフォームでは、96-128bit、つまり12-16バイトをヘッダとして使っているのですが、これを64bit、8バイトにするものです。
このことによってヒープサイズは減り、データの局所性があがります。キャッシュに乗りやすくなって性能があがるということですね。
現在はデフォルトで無効で、-XX:+UnlockExperimentalVMOptions -XX:+UseCompactObjectHeaders
というVMオプションをつけることで有効化されます。
今後、デフォルト有向にしたあと、現在の96-128bitヘッダに関するコードを削除する方向です。
475: Late Barrier Expansion for G1
G1GCでのバリア処理をC2コンパイラの初期ではなく最適化のあとにすることで、オーバーヘッドが減り効率化し、G1GCの実装を簡素化します。
479: Remove the Windows 32-bit x86 Port
Windows 32bit対応はJava 21でDeprecateになったため、すでにビルドをリリースしているディストリビューションはないので、今回はコードの削除です。
483: Ahead-of-Time Class Loading & Linking
クラスがJVMにロードされた状況を保存し使いまわすことで、JVMの起動やピーク性能に至る時間を短くします。
まずは、アプリケーションを一旦起動してクラスのローディングデータを記録します。ここではデータがapp.aotconfファイルに記録されます。
java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf \
-cp app.jar com.example.App ...
そのあと、記録データからキャッシュファイルを作成します。ここではapp.aotファイルにキャッシュが作成されます。
java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf \
-XX:AOTCache=app.aot -cp app.jar
そして実行時にキャッシュを利用します。
java -XX:AOTCache=app.aot -cp app.jar com.example.App ...
QuarkusでRESTだけいれたプロジェクトを試すと、こんな感じのファイルができました。
2025/03/21 11:23 35,192,832 code.aot
2025/03/21 11:22 578,141 code.aotconf
起動時間を雑にみてみると、1秒かかっていたのが0.35秒になる感じ。かなり速いですね。
Spring Bootだと2.85秒かかっていたのが2.60秒くらい、コンスタントに0.2秒弱速くなる感じかな。Spring Boot自体の起動が遅いので、効果は体感できないですね。。
また、QuarkusやSpring Bootではユーザーアプリケーションや拡張機能がカスタムクラスローダーで読み込まれますが、JEP-483では標準クラスローダーだけがサポートされるので、そういった部分はキャッシュされません。
今後の課題となっています。
490: ZGC: Remove the Non-Generational Mode
Java 23でZGCでの世代別GCがデフォルトになっていましたが、非世代別モードが削除されます。
491: Synchronize Virtual Threads without Pinning
仮想スレッドでsynchrinizedブロックを動かすとき、プラットフォームスレッドに仮想スレッドをピン止めしていました。そうすると、そのプラットフォームスレッドは他の仮想スレッドで利用できず、プラットフォームスレッドの枯渇やデッドロックが発生しました。
ピン止めしないようにすることで、そういった問題を改善します。
恐らく、こういった問題が起きにくくなります。
Netflixが仮想スレッドを採用:パフォーマンスと落とし穴に関するケーススタディ - InfoQ
Security
今回からセキュリティ関連をわけました。量子耐性暗号としてモジュール格子暗号に関する機能が追加されたのが見どころ
478: Key Derivation Function API (Preview)
486: Permanently Disable the Security Manager
496: Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism
497: Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm
478: Key Derivation Function API (Preview)
暗号鍵を安全に生成するためのキー導出機能(KDF)のAPIです。
Java 21でJEP 452 として導入されたKey Encapsulation Mechanism(KEM)で、公開鍵暗号のもとで安全に共有鍵を交換する仕組みが導入されました。それにあわせて、今回のKDFの導入で、ハイブリッド公開鍵暗号の実装の準備が整います。
ハイブリッド公開鍵暗号では、短いセッション鍵を一旦生成してKEMでカプセル化して送信、共有されたセッション鍵を使ってお互いにKDFで対照鍵を生成してデータ交換に利用、という流れになります。
486: Permanently Disable the Security Manager
セキュリティマネージャーはAppletやJava Web Startのために使われてましたが、サーバーサイドでは全く使われず、Java 17のJEP 411 で非推奨になっていました。
それにもかかわらず、ネットワークやIO、Swingなどではセキュリティマネージャー対応のコードが必要で、メンテナンスが大変ということで、今回は完全に無効にして、セキュリティマネージャー依存のコードを削除できるようにします。
-Djava.security.manager
でエラーが出るようになり、System.setSecurityManager
は例外を投げます。
496: Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism
量子コンピュータではShorのアルゴリズムを使って素因数分解が高速にできてしまうので、素因数分解の計算困難性を利用するRSAなどは量子耐性がないと言われています。
そこで、量子耐性のあるモジュール格子暗号を使ってキー交換をする仕組みが導入されます。
また、これらの量子耐性アルゴリズムは、メンテナンスされているLTSバージョンにバックポートされるようです。
https://xtech.nikkei.com/atcl/nxt/column/18/00001/10396/:title
497: Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm
量子耐性のあるモジュール格子暗号を使って電子署名を行うアルゴリズムが導入されます。
ツール
ツール関連ではJEPはひとつですが、JEPになっていない変更がちょこちょこあります。
493: Linking Run-Time Images without JMODs
JEPになってない変更
javaコマンドからいくつかのオプションが削除
次のオプションが削除されています。
-t, -tm, -Xfuture, -checksource, -cs, -noasyncgc
https://bugs.openjdk.org/browse/JDK-8339918
javaコマンドのいくつかのオプションが非推奨に
次のオプションが非推奨になっています。Java 25で削除かな。
-verbosegc, -noclassgc, -verify, -verifyremote, -ss, -ms, -mx
これらは省略形なので、ちゃんと書けという話っぽい
*Deprecated | *Replacement |
---|---|
-verbosegc | -verbose:gc |
-noclassgc | -Xnoclassgc |
-verify | -Xverify:all |
-verifyremote | -Xverify:remote |
-ss | -Xss |
-ms | -Xms |
-mx | -Xmx |
JARツールで展開先を指定可能に
-C
と-dir
オプションが追加されて、展開先を指定できるようになりました。
jar xf foo.jar -C /tmp/bar/
jar --extract --file foo.jar --dir /tmp/bar/
JARツールでファイル上書きを抑制
k
と-keep
オプションが指定されて、ファイル上書きしないようにできます。
jar xkf foo.jar
jar --extract --keep-old-files --file foo.jar
jstatdが非推奨に
jstatd
コマンドが非推奨になっています。Java 25で削除かな
jrunscriptコマンドが非推奨に
jrunscript
コマンドが非推奨になっています。これもJava 25で削除かな。
https://bugs.openjdk.org/browse/JDK-8341134
493: Linking Run-Time Images without JMODs
jlinkコマンドでカスタムJREを作るときにjmodファイルを使わないようにすることで、JDKサイズを25%削減する、らしい。
デフォルトは無効で、ディストリビューションによって有効にされるという話。
未検証。あとで書く
JDK
Windowsでのx86 32bitがコードまで消されました。次は他のプラットフォームも。
501: Deprecate the 32-bit x86 Port for Removal
501: Deprecate the 32-bit x86 Port for Removal
ということで、32bit x86向けのコードを消すために、一旦非推奨になります。
各ディストリビューションでも、32bit版の提供が終わったようです。
JEP-503で32bit x86コードは削除されます。
arm 32bitは非推奨になったわけでもないけど、いつのまにか各ディストリビューションから消えていますね。UbuntuなどでRaspberry Pi用に32bit ARMのバイナリがメンテナンスされてるっぽい。