78
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Java 24新機能まとめ

Last updated at Posted at 2025-03-21

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/

Samplesに新機能のサンプルがあります。
image.png

イベント

新機能解説で東京と福岡で登壇します。

東京では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対応だろうか。
Untitled.png

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以降では最大ですね。
Untitled-1.png

言語機能

言語機能の変更としては、新しいものはなく正式化されたものもないですが、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]

ただ、そうするとListjava.util.Listjava.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!

publicclassstaticなどのキーワードが消え、[]という謎の記号も消えました。
プログラムを勉強するときに、まずやりたいことは処理を書くことです。
クラスは書いた処理をうまく構成するための仕組みなので、処理が書けないうちに勉強してもあまり意味がありません。
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というクラスが導入されました。このクラスには、printprintlnreadlnの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での正式化はなさそうです。

並列処理では、複数の処理を実行するときに、両方が終われば正常終了とか、どちらか片方が終われば終了だとか、どちらか一方でも例外が発生したら終了だとか、同時に行う処理で連動することがあります。
しかし、これを既存のjoinwaitなどで制御しようとすると、実際には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());
    }
}

SubtaskSupplierを継承しているので、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のバイナリがメンテナンスされてるっぽい。

78
65
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
78
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?