43
Help us understand the problem. What are the problem?

posted at

updated at

Java 19新機能まとめ

Java 19が2022/9/20にリリースされました。

https://mail.openjdk.org/pipermail/jdk-dev/2022-September/006933.html
今回、正式導入された機能がほとんどありませんが、Record PatternsやVirtual Threadは注目すべき機能です。

こちらの配信で解説しています。
image.png

詳細はこちら
JDK 19 Release Notes
Java SE 19 Platform JSR 394
OpenJDK JDK 19 GA Release

APIドキュメントはこちら
Overview (Java SE 19 & JDK 19)

追加されたAPIまとめはこちら
https://docs.oracle.com/en/java/javase/19/docs/api/new-list.html

APIの差分はこちら。
https://cr.openjdk.java.net/~iris/se/19/latestSpec/apidiffs/overview-summary.html

MacやLinuxでのインストールにはSDKMAN!をお勧めします

Oracle OpenJDK以外に無償で商用利用できるディストリビューションとしては、次のようなものがあります。(まだ用意されていないものもあります。9/22時点ではOracle JDK, Azul Zulu, Corretto 19, SapMachineがダウンロード可能です)

アップデートは10月に19.0.1が、1月に19.0.2がリリースされることになります。

JEP

大きめの変更はJEPでまとまっています。
https://openjdk.java.net/projects/jdk/19/

今回は7個のJEPが取り込まれました。すでにプレビューなどで出ていたものが3つあり、新たに取り込まれたものは4です。ただし、正式に取り込まれたのは1つだけです。
また、今回は削除された機能、非推奨になった大きな機能というのはありません。

JEP 405: Record Patterns (Preview)
JEP 422: Linux/RISC-V Port
JEP 424: Foreign Function & Memory API (Preview)
JEP 425: Virtual Threads (Preview)
JEP 426: Vector API (Fourth Incubator)
JEP 427: Pattern Matching for switch (Third Preview)
JEP 428: Structured Concurrency (Incubator)

ツール

今回ツール関係の変更はありませんでした。

言語機能

言語機能の変更としては、Record Patternsがプレビューとして入りました。また、Pattern Matching for switchが3rdプレビューになっています。

JEP 405: Record Patterns (Preview)
JEP 427: Pattern Matching for switch (Third Preview)

JEP 405: Record Patterns (Preview)

レコードの分解ができるパターンマッチです。
たとえばPointというレコードを用意します。

jshell> record Point(int x, int y) {}
|  次を作成しました: レコード Point

jshell> var p = new Point(3, 5)
p ==> Point[x=3, y=5]

そうすると次のようなパターンが書けます。

jshell> p instanceof Point(var xx, var yy)
$3 ==> true

条件式と組み合わせて、パターンマッチが成り立つ時にそのまま変数の値を扱うことができます。

jshell> p instanceof Point(var xx, var yy) ?
   ...>   "x:%d y:%d".formatted(xx, yy) :
   ...>   "no match"
$4 ==> "x:3 y:5"

固定値の指定は出来なさそう。

jshell> p instanceof Point(3, var yy)
|  エラー:
|  型の開始が不正です
|  p instanceof Point(3, var yy)
|                     ^

Pointを持つBoxを定義します。

jshell> record Box(Point topLeft, Point bottomRight){}
|  次を作成しました: レコード Box

jshell> var b = new Box(new Point(3, 4), new Point(7, 9))
b ==> Box[topLeft=Point[x=3, y=4], bottomRight=Point[x=7, y=9]]

そうすると、Box内のPointの値を直接取り出すことができます。

jshell> b instanceof Box(Point(var left, var top), Point(var right, var bottom)) ?
   ...>   "(%d, %d)-(%d, %d)".formatted(left, top, right, bottom) : "no match"
$8 ==> "(3, 4)-(7, 9)"

次のように命令的にレコードの中身を得ていくよりも、やりたいことがはっきりします。

b instanceof Box b ?
  "(%d, %d)-(%d, %d)".formatted(b.topLeft().x(), b.topLeft().y(), 
                                b.bottomRight().x(), b.bottomRight().y()) : 
  "no match"

JEP 427: Pattern Matching for switch (Third Preview)

パターンマッチがswitchで使えるようになります。
3rdプレビューになりました。今回大きな変更がなければ、きっとJava 20で正式機能になりそうです。
Java 19では、使うときに--enable-previewが必要です。

「といってもパターンマッチってそんなに出番ないのでは?」
と思うかもしれませんが、大きな影響が。

switchcase nullが書けるようになります。

jshell> String s = null
s ==> null

jshell> switch (s) {
   ...>     case "test" -> "テスト";
   ...>     case null -> "ぬるぽ";
   ...>     default -> "hello";
   ...> }
$13 ==> "ぬるぽ"

case nullがない場合は従来どおりNullPointerExceptionです。

jshell> switch (s) {
   ...>     case "test" -> "テスト";
   ...>     default -> "hello";
   ...> }
|  例外java.lang.NullPointerException: Cannot invoke "String.hashCode()" because "<local0>" is null
|        at (#12:1)

""nullで同じ処理を行う場合などに併記できるのが便利です。

jshell> switch(s){
   ...>     case null, "" -> "empty";
   ...>     default -> s;
   ...> }
$17 ==> "empty"

defaultnullで同じ処理したいときにアロースタイルで書けないんでは?という疑問に応えて、defaultcaseに書けるようになっています。

jshell> switch (s) {
   ...>     case "one" -> 1;
   ...>     case "two" -> 2;
   ...>     case null, default -> -1;
   ...> }
$21 ==> -1

で、パターンマッチですが、型と変数をswtichに書けるようになりました。これをタイプパターンといいます。

jshell> Object o = "abc"
o ==> "abc"

jshell> switch (o) {
   ...>     case String s -> "--%s--".formatted(s);
   ...>     case Integer i -> "%03d".formatted(i);
   ...>     default -> o.toString();
   ...> }
$35 ==> "--abc--"

タイプパターンのあとにwhenでつなげて条件を書くことでガード節とすることができます。

jshell> o = "helo"
o ==> "helo"

jshell> switch (o) {
   ...>     case String s when s.length() < 5 -> "--%s--".formatted(s);
   ...>     case String s -> s.toUpperCase();
   ...>     case Integer i -> "%05d".formatted(i);
   ...>     default -> o.toString();
   ...> }
$42 ==> "--helo--"

2ndプレビューではwhenではなく&&を使っていました。

switch (o) {
    case String s && s.length() < 5 -> "--%s--".formatted(s);
    case String s -> s.toUpperCase();
    case Integer i -> "%05d".formatted(i);
    default -> o.toString();
}

定数パターンとタイプパターンをひとつのswitchで使うこともできます。定数パターンに使えるのはこれまでどおり整数、文字列、enumです。

jshell> s = "helo"
s ==> "helo"

jshell> switch (s) {
   ...>     case "test" -> "テスト";
   ...>     case String s when s.length() < 5 -> s.toUpperCase();
   ...>     default -> "too long";
   ...> }
$29 ==> "HELO"

case句のタイプパターンで定義した変数のスコープは、case句の中だけです。
ここではswitchの外にあるのと同じく変数sを定義してcase句で上書きしていますが、switchの外に影響はありません。

jshell> s = "helo"
s ==> "helo"

jshell> switch (s) {
   ...>     case String s when s.length() < 5 -> (s="Hello").toUpperCase();
   ...>     default -> "too long";
   ...> }
$30 ==> "HELLO"

jshell> s
s ==> "helo"

Record Patternsと併用が本命ですね。

return switch(n) {
    case IntExpr(int i) -> i;
    case NegExpr(Expr n) -> -eval(n);
    case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
    case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
    default -> throw new IllegalArgumentException(n);
};

ここでSealed Classと組み合わせて次のような定義になっているとします。

sealed interface Expression
  permits IntExpr, NegExpr, AddExpr, MulExpr {}
record IntExpr(int i) implements Expression {}
record NegExpr(Expr n) implements Expression {}
record AddExpr(Expr left, Expr right) implements Expression {}
record MulExpr(Expr left, Expr right) implements Expression {}

そうすると、Expressionが取りうるのが4つのレコードで全てということがわかるのでdefault句が不要になります。また、全てをチェックしていることを保証できるのが、既存のコードと違うところで、パターンマッチのメリットです。

Expression n = getExp();
return switch(n) {
    case IntExpr(int i) -> i;
    case NegExpr(Expr n) -> -eval(n);
    case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
    case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
};

API

JEP 424: Foreign Function & Memory API (Preview)
JEP 425: Virtual Threads (Preview)
JEP 426: Vector API (Fourth Incubator)
JEP 428: Structured Concurrency (Incubator)

小さいもの

BigDecimal.TWO

これが正式機能で唯一多くの人が喜ぶ機能追加かもしれない・・・

jshell> BigDecimal.TWO
$1 ==> 2

Thread.sleep(Duration)

sleepメソッドにDurationが指定できるようになったので、よりわかりやすく書けるようになりました。

jshell> Thread.sleep(Duration.ofSeconds(3))

sleep(3000) じゃ どんだけ待つか、仕様を知らないとわからないし、仕様を知っていても直観的じゃないですよね。

Localeのコンストラクタがdeprecatedに

image.png

代わりにofメソッドを使います。

jshell> Locale.of("ja")
$12 ==> ja

jshell> Locale.of("ja","jp")
$13 ==> ja_JP

HashMap / HashSet / LinkedHashMap / LinkedHashSet newXxx

容量を指定してオブジェクトを生成する newHashMapなどのstaticメソッドがそれぞれ追加されています。

jshell> Map<String, String> m = HashMap.newHashMap(10)
m ==> {}

new HashMap(10)との違いは、<>がなくても「無検査変換」って出ないことと、指定した数ぴったりの容量ではなく、指定した数の要素を格納するのに十分な容量になる、ということかな。

jshell> Map<String, String> m = new HashMap(10)
|  警告:
|  無検査変換
|    期待値: java.util.Map<java.lang.String,java.lang.String>
|    検出値:    java.util.HashMap
|  Map<String, String> m = new HashMap(10);
|                          ^-------------^
m ==> {}

Double.PRECISION / Float.PRECISION

有効数字の精度bit数です。

jshell> Double.PRECISION
$2 ==> 53

jshell> Float.PRECISION
$3 ==> 24

Math.TAU

半径に対する円周比です。つまり2π。

jshell> Math.TAU
$4 ==> 6.283185307179586

jshell> Math.TAU / 2
$5 ==> 3.141592653589793

jshell> Math.PI
$6 ==> 3.141592653589793

Integer.compress, expand / Long.compress, expand

commpressが第一引数に指定した数値について第二引数で指定したbitだけ取り出して右に詰めます。

jshell> Integer.toHexString(Integer.compress(0xcafebabe, 0xf000f0f0))
$15 ==> "cbb"

expandは逆で、第一引数に指定した数値の下位bitを第二引数で指定したbitに広げます。

jshell> Integer.toHexString(Integer.expand(0xcbb, 0xf000f0f0))
$16 ==> "c000b0b0"

JEP 425: Virtual Threads (Preview)

仮想スレッドです。いままでJavaは、初期にプラットフォームスレッドを使うようになって以来、ずっとThreadとしてプラットフォームスレッドを使ってきました。
プラットフォームスレッドというのは、OSが管理するようなスレッドです。OSが管理するスレッドというのは、YouTubeで動画を見てTwitterして時計を表示してファイナルファンタジーやって、と何でも同時に動かせるように設計されています。
もちろんアプリケーションの中でいろいろな働きのスレッドを動かすのですが、多くのスレッドを立ち上げることになるのは、結局同じことをするスレッドです。
その中で、計算のために同じことをするというのはハードウェアの構成を考えないといけなくて、そのための構成がGPUだったりVector APIで扱うSIMD命令だったりするわけですね。
そして、アプリケーションの処理としては結局のところ計算の時間よりも通信待ちの時間のほうがはるかに長いということで、通信待ち時間を有効利用したいというのが並列処理の主な目的になっていました。
そうすると、計算の最中にバランスとりながら処理を切り替えるプラットフォームスレッドというのはオーバークオリティだったわけで、やりたいことに対してメモリも食うし限界も低いし遅いということになってたわけです。
ということで、現状でJavaではReactiveやRxといって通信のときに他の処理に制御を渡せるようなコードを書いているのですが、これが非常に難しくてやってられないということで、Javaから離れてKotlinのように自然にそういう処理が書ける言語に移行する人がでています。

という問題に対処するために、OSではなくJVMが管理するスレッドがVirtual Threadとして導入されます。Virtual ThreadはProject Loomで開発されていました。

Virtual Threadは既存のプラットフォームスレッドと同様に扱うことができるよう気をつけてAPIが設計されています。

ただ、プラットフォームスレッドではnew Thread()としてスレッドを生成できましたが、コンストラクタでVirtual Threadを生成することはできません。

いままでのプラットフォームスレッドはこんな感じで使いました。

jshell> Thread t = new Thread(() -> System.out.println("hello"))
t ==> Thread[#220496,Thread-110304,5,main]

jshell> t.getState()
$33 ==> NEW

jshell> t.start()

jshell> hello

jshell> t.getState()
$35 ==> TERMINATED

Java 19ではThread.Builderが用意されて、ofPlatformofVirtualでプラットフォームスレッドと仮想スレッドを分けれるようになります。
unstartedメソッドを使うと、new Threadと同様にまだ開始していないスレッドを得れます。

jshell> Thread t = Thread.ofPlatform().unstarted(() -> System.out.println("hello"))
t ==> Thread[#220491,Thread-110303,5,main]

jshell> t.start()

hello
jshell> t.getState()
$24 ==> TERMINATED

そしてofVirtualに変更すると仮想スレッドを得れます。仮想スレッドはThreadクラスのオブジェクトとして扱えます。使い方はプラットフォームスレッドと同様です。

jshell> Thread t = Thread.ofVirtual().unstarted(() -> System.out.println("hello"))
t ==> VirtualThread[#220492]/new

jshell> t.start()

jshell> hello

jshell> t.getState()
$27 ==> TERMINATED

startメソッドを使うと、スレッドの実行とThreadオブジェクトの取得が同時に行えます。

jshell> Thread t = Thread.ofVirtual().start(() -> System.out.println("hello"))
t ==> VirtualThread[#220497]/runnable
hello

jshell> t.getState()
$37 ==> TERMINATED

ExecutorServiceで仮想スレッドを使う場合は、Executors.newVirtualThreadPerTaskExecutorを使います。

jshell> ExecutorService ex = Executors.newVirtualThreadPerTaskExecutor()
ex ==> java.util.concurrent.ThreadPerTaskExecutor@28ba21f3
jshell> ex.submit(() -> System.out.println("hello"))
$40 ==> java.util.concurrent.ThreadPerTaskExecutor$ThreadBoundFuture@64a294a6[Not completed]
hello

jshell> ex.isTerminated()
$41 ==> false

jshell> ex.isShutdown()
$42 ==> false

jshell> ex.shutdown()
jshell> ex.isTerminated()
$44 ==> true

jshell> ex.isShutdown()
$45 ==> true

では、少し性能を見てみましょう。
3秒待つメソッドを定義しておきます。

jshell> import java.time.Duration

jshell> void s(){ try{ Thread.sleep(Duration.ofSeconds(3));} catch(Exception ex){}}
|  次を作成しました: メソッド s()

プラットフォームスレッドを100起動してそれぞれで3秒待ちます。

jshell> IntStream.range(0, 100).forEach(i -> Thread.ofPlatform().start(() -> s()))

スレッドが100増えてすぐ元にもどっていることがわかります。
image.png

仮想スレッドを100起動してそれぞれで3秒待ちます。

jshell> IntStream.range(0, 100).forEach(i -> Thread.ofVirtual().start(() -> s()))

そうすると、17スレッドだけ増えて31スレッドになります。

image.png

100増えないことと、終わってもスレッドが残っていることがわかります。

それでは10万スレッドを同時に実行してみましょう。
まずは仮想スレッドから。

jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofVirtual().start(() -> s()))

10:09のところで実行しています。スレッド数は31のまま、CPU使用率は7%くらいです。メモリは120MBくらい使っていますね。

image.png

プラットフォームスレッドで試してみます。

jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofPlatform().start(() -> s()))

まず、しばらく処理が終わりません。
スレッドは2万弱くらいまで使われていますが、その2万弱のスレッドでスレッドが終わったら別のスレッドというふうにやりくりして10万スレッドを処理できるまで、繰り返されます。
また、CPUは80%近くまで使われています。処理はSleepするだけなので、スレッド切り替えにCPUを使っていると推測されます。メモリはそれほど使っていませんね。

image.png

このように、多数のスレッドを処理するときにプラットフォームスレッドでは同時処理数に限界があることと、CPUパワーを余分に使ってしまうので、軽量な仮想スレッドが求められ実装されたわけです。

JEP 428: Structured Concurrency (Incubator)

並列処理では、複数の処理を実行するときに、両方が終われば正常終了とか、どちらか片方が終われば終了だとか、どちらか一方でも例外が発生したら終了だとか、同時に行う処理で連動することがあります。
しかし、これを既存のjoinwaitなどで制御しようとすると、実際にはjoinからwaitへのGo Toを書くようなコードになって、処理が追えなくなります。
そこで導入されるのが構造化並列性といいます。
こんな感じ。詳しくはあとで書きます!

Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Future<String>  user  = scope.fork(() -> findUser());
        Future<Integer> order = scope.fork(() -> fetchOrder());

        scope.join();           // Join both forks
        scope.throwIfFailed();  // ... and propagate errors

        // Here, both forks have succeeded, so compose their results
        return new Response(user.resultNow(), order.resultNow());
    }
}

JEP 426: Vector API (Fourth Incubator)

AVX命令のような、複数のデータに対する計算を同時に行う命令をJavaから利用できるようになります。
Project Panamaのひとつとして開発されていました。
Java 16でインキュベータとして導入されましたが、今回4thインキュベータになりました。

使うためには実行時やコンパイル時に--add-modules jdk.incubator.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()              compare(                div(                    elementSize()           
elementType()           eq(                     equals(                 fma(                    getClass()              
hashCode()              intoArray(              intoByteArray(          intoByteBuffer(         lane(                   
lanewise(               length()                lt(                     max(                    min(                    
mul(                    neg()                   notify()                notifyAll()             pow(                    
rearrange(              reduceLanes(            reduceLanesToLong(      reinterpretAsBytes()    reinterpretShape(       
selectFrom(             shape()                 slice(                  sqrt()                  sub(                    
test(                   toArray()               toDoubleArray()         toIntArray()            toLongArray()           
toShuffle()             toString()              unslice(                viewAsFloatingLanes()   viewAsIntegralLanes()   
wait(                   withLane(      

ところで、こういったメソッド呼び出しの内部でAVX命令などを呼び出すのでは遅くなるんではという気がしますが、実際にはJVM intrinsicsという仕組みでJITコンパイラがこれらのメソッド呼び出しをネイティブ関数呼び出しに置き換えます。

JEP 424: Foreign Function & Memory API (Preview)

3rdインキュベータではなくPreviewになりましたが、Foreign Function APIの前身であるForeign Linker APIはJava 16から、Foreign Memory APIはJava 14からインキュベータで、結構ながくインキュベータやってます。なので もう番号ふるのをやめたのかな

Foreign Memory API

ヒープ外のメモリをアクセスする方法としては、ByteBufferを使う方法やUnsafeを使う方法、JNIを使う方法がありますが、それぞれ一長一短があります。
ByteBufferでdirect bufferを使う場合、intで扱える範囲の2GBまでに制限されたり、メモリの解放がGCに依存したりします。
Unsafeの場合は、性能もいいのですが、名前が示すとおり安全ではなく、解放済みのメモリにアクセスすればJVMがクラッシュします。
JNIを使うとCコードを書く必要があり、性能もよくないです。

ということで、ヒープ外のメモリを直接扱うAPIがJava 14でインキュベータモジュールとして導入され、今回6バージョン目のインキュベータです。
次のようなコードになります。

import java.lang.foreign.*;
import java.nio.ByteOrder;

VarHandle intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   for (int i = 0 ; i < 25 ; i++) {
        intHandle.set(seg, i * 4, i);
   }
}

コンパイル、実行では--enable-previewを付ける必要があります。

MemoryHandlesからwithOffsetとwithStrideが消えましたね。

jshell> import java.lang.foreign.*

jshell> MemoryHandles.
asAddressVarHandle(   asUnsigned(           class                 collectCoordinates(   dropCoordinates(
filterCoordinates(    filterValue(          insertCoordinates(    permuteCoordinates(   varHandle(

Java 15ではMemoryHandlesからVarHandleを得るメソッドが拡充してました。

jshell> MemoryHandles.
asAddressVarHandle(   asUnsigned(           class                 collectCoordinates(   dropCoordinates(
filterCoordinates(    filterValue(          insertCoordinates(    permuteCoordinates(   varHandle(
withOffset(           withStride(

Java 14では次の3つのメソッドしかありませんでした。

jshell> MemoryHandles.
class         varHandle(    withOffset(   withStride(

Java 16ではMemoryAccessというユーティリティが用意されて、VarHandleを使わずにシンプルにかけるようになっています。

import java.lang.foreign.*;

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   for (int i = 0 ; i < 25 ; i++) {
        MemoryAccess.setIntAtOffset(i * 4, i);
   }
}

Foreign Function API

ネイティブライブラリの呼び出しを行う。
外部メモリのアクセスにはForeign Memory Access APIを使う
JNIの代替

Java 16でForeign Linkerとして1stインキュベータになり、今回4バージョン目のインキュベータです。

たとえばこんな感じのC関数があって。

size_t strlen(const char *s);

こんな感じでMethodHandleを取り出して。

CLinker linker = CLinker.systemCLinker();
MethodHandle strlen = linker.downcallHandle(
    linker.lookup("strlen").get(),
    FunctionDescriptor.of(JAVA_LONG, ADDRESS)
);

こんな感じで呼び出すようです。

MemorySegment cString = implicitAllocator().allocateUtf8String("Hello");
long len          = strlen.invoke(cString);  // 5

JDK

JDKの変更、つまりリリース方針などの変更としては、今回RISC-Vへの対応が入りました。

JEP 422: Linux/RISC-V Port

対応したのはSIMD命令付64bitのRV64GVコンフィグレーションです。
まだVector APIにはちゃんと対応してないぽい。
[vectorIntrinsics] Vector API for RISC-V

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
43
Help us understand the problem. What are the problem?