KEMBAR78
Head toward Java 16 (Night Seminar Edition) | PDF
HeadtowardJava16(NightSeminarEdition)
KUBOTAYuji
SBISecuritySolutions
JJUGNightSeminar(2021/Mar/16)
KUBOTAYuji(@sugarlife)
ManagerofAWSinfrastructureteamatSBISecuritySolutions
We'rehiring!
https://www.sbisecsol.com/
OpenJDKAuthor/IcedTeacommitter
JavaOne2014/2016/2017,WEB+DB「DivetoJava」(2018-19)
https://www.slideshare.net/YujiKubota/
今⽇話すこと
Java16の新機能と変更点
主にAPI以外のVM機能
話さないこと
⾮互換性
APIの細かい変更点
http://cr.openjdk.java.net/~iris/se/16/latestSpec/apidiffs/overview-summary.html
References
bugs.openjdk.java.net(通称JBS,b.o.j.n)
JEPsforJava16
CSRsforJava16
https://github.com/openjdk/jdk
Glossary(1/2)
JEP(JDKEnhancement-Proposal)
JEP1で定義されているJava新機能拡張の提案
CSR(Compatibility&SpecificationReviews)
⾮互換性を伴う変更のレビュー(⾮互換性を伴う変更は出す必要がある)
Releasenote
各製品ごとの変更点。OpenJDKやOracleJDKで書かれている内容がすべての製品にあてはま
るわけではないのことに注意
OpenJDK:JBS
OracleJDK:https://www.oracle.com/java/technologies/javase/jdk-relnotes-index.html
Glossary(2/2)
Incubator(JEP11)
JavaAPIの試験⽤モジュール( jdk.incubator )。標準化に向けてフィードバックを得て変更
しやすいように特別なモジュールにしている
有効にするには --add-modules jdk.incubator.xxx の指定が必要
Preview(JEP12)
Java⾔語やJVMの試験機能。有効にするには --enable-preview の指定が必要。コンパイ
ル時には --resource か -source の指定も必要
Standard
上記のテストフェーズを通じて標準機能に昇格した機能。通例は2回(例:Preview->Second
Preview->Standard)で昇格しているが、Foreign-MemoryAccessAPIのように3回⽬を迎え
るのもある
Java16
17JavaEnhancement-Proposals
Projectpage:https://openjdk.java.net/projects/jdk/16/
JBS
172Compatibility&SpecificationReviews
JBS
Ecosystemchanges
347:EnableC++14LanguageFeatures
357:MigratefromMercurialtoGit
369:MigratetoGitHub
386:AlpineLinuxPort@kisさん担当、以下@kisと記載
388:Windows/AArch64Port@kis
347:EnableC++14LanguageFeatures
⽬的:C++14機能を利⽤して効率よくJDKを実装できるようにする
背景:JDKやJVMはC++で実装されているが機能はC++98/03に⻑らく限定されていた。JDK
11から新しいバージョンでの「ビルド」はサポートされ始めたが、⾔語機能の利⽤⾃体は許
可されていなかった
ちなみにHotSpot(JVM実装)とJDK実装ではこのルールが異なり、HotSpotの実装ではビルド
ルールでC++exceptionsは利⽤できないようにされている
Java利⽤者にとっては特に影響なし
357:MigratefromMercurialtoGit
⽬的:OpenJDKをGitで管理する
背景:⻑らくOpenJDKはMercurial(hg)で管理されていたが開発者との親和性などの観点から
Gitに移⾏を図った(使いづらい、開発者を呼び込みたい、など)
バージョン管理やコミットメッセージのリフォーマット、各種チェッカー(jcheck)やレビュー
ツール(webrev)などの移植が⾏われた
既存Mercurialリポジトリ(hg.openjdk.java.net)は積極的に廃棄されず残る予定
369:MigratetoGitHub
⽬的:OpenJDKをGitHubで管理する
背景:357と⼀緒。こちらは構成管理をどのプラットフォームで⾏うか
リポジトリはhttps://github.com/openjdk/
今までjava.netにあったレビューツールやチェッカーなどもGitHubエコシステムに移植
Bylaws(グループやプロジェクト、開発者のロール、ガバメントボードなどの原則)や
Census(グループやプロジェクト、開発者、およびその関連性)は変わらない
課題管理やWikiなどのインフラも変わらずjava.net上のまま(いつでもGitHubから移⾏でき
るように=影響を受けないように保つという⽬的もある)
Language/APIchanges
338:VectorAPI(Incubator)@kis
380:Unix-DomainSocketChannels
389:ForeignLinkerAPI(Incubator)
390:WarningsforValue-BasedClasses
393:Foreign-MemoryAccessAPI(ThirdIncubator)@kis
394:PatternMatchingforinstanceof@kis
395:Records@kis
396:StronglyEncapsulateJDKInternalsbyDefault
397:SealedClasses(SecondPreview)@kis
380:Unix-DomainSocketChannels(1/4)
⽬的:単⼀マシン上でのプロセス間通信に使⽤されるUnixDomainSocket( AF_UNIX )を標準
APIでサポート
背景:プロセス間通信はTCP/IP(ループバック接続)よりも効率的でセキュアな通信が⾏え、近
年Windowsでサポートされ始めた。これにより主要プラットフォームでサポートされたので
標準APIに⼊った。同⼀システム上のコンテナ間通信でも、共有ボリュームを利⽤することで
Unix-DomainSocket利⽤が実現できる
効率的:Unix-DomainSocketはファイルシステムのパス名( C:¥hoge , /hoge )を指定して通
信する。つまり、ループバックTCP/IPソケットではない=TCP/IPスタックを通らないので
LatencyやCPU使⽤率が改善できる
セキュア:➀ローカルアクセスは必要だがリモートネットワークアクセスが不要なのでリモー
トアクセスを遮断できる➁プラットフォーム(Unix,Windows)のファイルシステムのアクセス
制御を適⽤できる
380:Unix-DomainSocketChannels(2/4)
制約:➀UnixではサポートされているがWindowsでサポートされていない機能はこのJEPで対
応することはゴールとして設定されていない。今後JDKごとの固有オプションとして対応さ
れる可能性がある。例えばPeercredentialsはUnix固有だが、ソケットオプション
SO_PEERCRED がすでに対応されている。➁レガシーAPI( java.net.Socket ,
java.net.ServerSocket )のサポートは⾏われていない。これはTCP/IP
( java.net.InetAddress )を前提に実装されているため
380:Unix-DomainSocketChannels(3/4)
import java.net.UnixDomainSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import static java.net.StandardProtocolFamily.UNIX;
public class Server {
private static final Path SOCKETE_FILEPATH = Path.of("./", "testsocket");
public static void main(String... args) throws Exception {
var address = UnixDomainSocketAddress.of(SOCKETE_FILEPATH);
try (var serverChannel = ServerSocketChannel.open(UNIX)) { // INETかUNIXかを指定
serverChannel.bind(address);
try (var clientChannel = serverChannel.accept()) {
ByteBuffer buf = ByteBuffer.allocate(64);
clientChannel.read(buf);
buf.flip();
System.out.printf("Read %d bytesn", buf.remaining());
}
} finally {
Files.deleteIfExists(address.getPath()); // ソケットと独⽴してファイルが存在するので確実に消す
}
}
}
380:Unix-DomainSocketChannels(4/4)
import java.net.UnixDomainSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.file.Path;
public class Client {
private static final Path SOCKETE_FILEPATH = Path.of("./", "testsocket");
public static void main(String... args) throws Exception {
var address = UnixDomainSocketAddress.of(SOCKETE_FILEPATH);
try (var clientChannel = SocketChannel.open(address)) {
ByteBuffer buf = ByteBuffer.wrap((args.length != 0 ? args[0] : "Hello world").getBytes());
clientChannel.write(buf);
}
}
}
ref:https://inside.java/2021/02/03/jep380-unix-domain-sockets-channels/
389:ForeignLinkerAPI(Incubator)(1/2)
⽬的:ProjectPanama。ネイティブ実装をもっと楽に呼び出したい
背景:JNIめんどい
.Nativeを呼び出すJava実装を書く private static native int getpid()
.headerファイルを作る javac -h . XXX.java
.JNIAPIを使ってC実装を書く JNIEXPORT jint JNICALL Java_XXX_getpid(JNIEnv
*env, jclass clas){...}
.コンパイル cc -shared -o libmain.dylib -I $JAVA_HOME/include -I XXX.c
.Java実装に動的ライブラリを読み込むコードを書く System.loadLibrary("XXX");
389:ForeignLinkerAPI(Incubator)(2/2)
import java.lang.invoke.*;
import jdk.incubator.foreign.*;
class PanamaMain {
public static void main(String[] args) throws Throwable {
// System Linker取得、ネイティブシンボルのルックアップ
var linker = CLinker.getInstance();
var lookup = LibraryLookup.ofDefault();
// C宣⾔のメソッドハンドル作成、関数記述⼦算出
var getpid = linker.downcallHandle(
lookup.lookup("getpid").get(),
MethodType.methodType(int.class),
FunctionDescriptor.of(CLinker.C_INT));
// 実⾏
System.out.println((int)getpid.invokeExact());
}
}
$ java -Dforeign.restricted=permit --add-modules jdk.incubator.foreign PanamaMain.java
ref.https://inside.java/2020/10/06/jextract/
より楽に:Cheaderファイルから上記相当の処理を⾏うJavaクラスをjextractで作成できる(サンプル集)
390:WarningsforValue-BasedClasses(1/4)
⽬的:Value-BasedClassesの利⽤⽅法を制限する
背景:
ProjectValhallaはprimitiveclassを活⽤してインライン表現が可能なようにプログラミングモデルを強化する
JDK8からある"Value-BasedClasses(以降、値クラス)"もこの対象にしたい
i.不変(可変オブジェクトへの参照は可)
ii.インスタンス状態からのみ計算される equals , hashCode , toString 実装を持つ
iii.インスタンスIDに依存した操作を使⽤しない( synchronized , == , hashCode )
iv. equals() のみに基づいて同等とみなす( == を利⽤した参照に基づく等価判定を⾏わない)
v.公開されたコンストラクタを持たない(⼀意性に寄与しないファクトリメソッドで作成される)
vi.同等な時は⾃由に置き換え可。インスタンスxとyが⽰す「値」が同じ時は⼊れ替えても変化なし
対象にするには以下のリスクが考えられる
i. != の結果に依存しているコードが破壊される
ii.コンストラクタ経由で Integer のようなpritimivewrapperclassのインスタンスを作っている場合は
LinkageErrors が発⽣する
iii.これらのクラスのインスタンスを同期処理( synchronized )させようとした場合例外が発⽣する
というわけで警告を出して利⽤⽅法を制約しておきたい←本旨
390:WarningsforValue-BasedClasses(2/4)
i.については、値クラスはFactoryメソッドで⼀意性の保証はしておらず、仕様を無視した前
提のコードは⾃動的に検出も難しいのでスコープ外とする
ii.については、Java9で既に⾮推奨化されておりコンストラクタが呼ばれていればデフォル
トでコンパイル時( javac )に警告が出される
iii.については、値クラスを対象に synchronized ⽂を利⽤していた場合はコンパイル時に警
告が出るようになった
また、実⾏時にも値クラスのインスタンス上での monitorenter が検出された場合、 -
XX:DiagnoseSyncOnValueBasedClasses に 1 を設定していた場合はfatalエラーとして扱
い、 2 を設定した場合は警告がコンソールおよびJFRのイベント経由でロギングされるよう
になった(注: UnlockDiagnosticVMOptions の設定も必要)
390:WarningsforValue-BasedClasses(3/4)
$ ./jdk-16.jdk/bin/javac ValueBased.java
ValueBased.java:5: 警告:[synchronization] 値ベース・クラスのインスタンスで同期しようとしました
synchronized (valueBased)) {
^
警告1個
$ ./jdk-16.jdk/bin/java -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=1 ValueBased
#
# A fatal error has been detected by the Java Runtime Environment:
#
# Internal Error (synchronizer.cpp:416), pid=71724, tid=11267
# fatal error: Synchronizing on object 0x00000007bfe78198 of klass java.lang.Long at ValueBased.main(ValueBased.java:5)
:(snip)
$ ./jdk-16.jdk/bin/java -XX:+UnlockDiagnosticVMOptions -XX:DiagnoseSyncOnValueBasedClasses=2 ValueBased
[0.101s][info][valuebasedclasses] Synchronizing on object 0x00000007bfe78198 of klass java.lang.Long
[0.101s][info][valuebasedclasses] at ValueBased.main(ValueBased.java:5)
[0.101s][info][valuebasedclasses] - locked <0x00000007bfe78198> (a java.lang.Long)
:(snip)
390:WarningsforValue-BasedClasses(4/4)
実現するために、対象クラスに @jdk.internal.ValueBased アノテーション追加
java.lang.{Integer,Long,Short,...} (primitivewrapperclass)
java.lang.Runtime.Version
java.util.{Optional,OptionalInt,OptionalLong,OptionalDouble}
java.time.{Instant,LocalDate,LocalTime,...} ,
java.time.chrono.MinguoDate,HijrahDate,...
java.lang.ProcessHandle (+実装クラス)
java.util のcollectionファクトリー実装クラス
java.util.List.{of,copyOf}
java.util.Map.{of,copyOf,ofEntries,entry}
396:StronglyEncapsulateJDKInternalsbyDefault
⽬的: sun.misc.Unsafe を含む重要なAPIを除くJDK内部実装へのアクセスを原則不可
背景:JDK9で実施されたモジュール化に伴い、外部利⽤を意図していない内部実装へのアク
セスを不可にしてメンテナンスコストを下げたい。しかし、代替不可能かつ依存しているラ
イブラリが多い sun.misc.Unsafe のような内部実装もあり、⼀律して全てを不可にするこ
とは難しいという状態が続いている
JEP261から導⼊された --illegal-access のデフォルト値が permit (最初のリフレクティ
ブアクセス時に警告がでるが原則許可)から deny ( --add-opens オプションなどを⽤いて明
⽰的に許可してない限りアクセス不可)へ変更。 jdeps コマンドを使って依存モジュールを確
認して適切にアクセス設定を⾏いましょう
JVMchanges
376:ZGC:ConcurrentThread-StackProcessing
387:ElasticMetaspace
376:ZGC:ConcurrentThread-StackProcessing(1/3)
⽬的:スレッドスタック処理をsafepointから並⾏フェーズに移⾏することで、ZGCの
STW(StopTheWorld)を更に減らす
背景:次ページ
利点:ZGCを使っている場合、何もせず⾼速化する可能性がある
376:ZGC:ConcurrentThread-StackProcessing(2/3)
背景(1):GCがなぜSTWを必要としているか
GCは各ヒープオブジェクトへのポインタに関する情報が必要
これはJITコンパイラによって作成されたオブジェクトマップ(OopMap)に含まれる
JITによってコンパイルされた各メソッドは、特定の場所にOopMapを記録し、スタックとレジスタのどの場所が参
照されてるかを記録する
GCは、スタックをスキャンするときにこれらのOopMapを照会して、参照するポインタがどこにあるかを把握する
GC時に各スレッドがポインタを触っていると予期せぬ領域に対して処理が⾛る可能性があるので、そのような
(unsafeな)スレッドはサスペンドする必要がある
各スレッドにsafepointに関する状態と遷移時の処理を持たせてlockすることで、GCなどのVMThread処理
(vmoperation)を⾏う際に各スレッドをblockする(そしてVMThreadがsafepointにて処理を⾏う)
結果的に各Javaスレッドが⽌まってる状態なので"vmoperation≒ safepoint≒ STW"となっている
376:ZGC:ConcurrentThread-StackProcessing(3/3)
背景(2):ZGCにおけるSTW
元々ZGCはマーキング処理などヒープサイズやメタスペースサイズに応じて増⼤化する
GC処理を、safepointでの操作から追い出して並⾏フェーズ(JavaスレッドとGCスレッ
ドを並⾏に処理)に移⾏させることでヒープサイズに関わらず1ミリ秒のSTWを達成して
きた
ルート処理などの⼀部がまだsafepointで⾏われている。ルートオブジェクト数はヒープ
サイズ依存ではないが、主にスレッド数などに依存するので⼤規模アプリでは問題にな
りえる
stackwatermarkbarrierを利⽤してGCがすべてのスレッドスタックと他のスレッドルー
トを処理をしている際にJavaスレッドがフレームに戻らないように整合性を確保するこ
とで並列化を実現
将来的に他GCへの転⽤が望める
387:ElasticMetaspace
⽬的:Metaspaceのフラグメンテーションやメモリフットプリントなどを改善
背景:Metaspaceはヒープ外メモリを多く消費するケースがある
メタデータは通常、クラスがロードされたときに確保され、その⽣存期間はクラスローダーに依存する
Metaspaceのチャンクはアロケーション操作を効率的に⾏うため粒度が粗い
クラスローダが回収されるとMetaspaceのチャンクは後で再利⽤するためにフリーリストに配置される
結果、頻繁にクラスローディングとアンローディングを⾏うアプリケーションは、⼤量の使わないスペースを
Metaspaceのフリーリスト内に蓄積してしまう→ここをどうにかしたい
⽅法:メモリアロケータをKernelなどでも利⽤されているbuddyallocationに置き換え
より⼩さなチャンクでメタスペースメモリを割り当てられるようになり、クラスローダのオーバーヘッドを減
らせる
同様にフラグメンテーションが減らせ、未使⽤のメタスペースメモリをより迅速にOSに戻せるようになる(※
元々断⽚化されてなかったらOSに返す)
利点:何もせずにJavaアプリケーションのメモリ使⽤量が改善
ref.https://cr.openjdk.java.net/~stuefe/JEP-Improve-Metaspace-Allocator/review-guide/review-guide-1.0.html
Toolchanges
392:PackagingTool
392:PackagingTool
⽬的:JDK14で⼊った jpackage (JEP343)が標準機能化
背景:各プラットフォーム間で共通のインストーラー⽣成ツールがなかったので作られた。こ
れからは jlink でカスタムランタイムイメージを作り、 jpackage でインストーラーを作
ってユーザに提供する流れが⼀般的に(なるかもしれない)
JDK14からの変更点は --bind-services オプションが --jlink-options に変わっただけ
Basicusage
$ jpackage --name sample --input <directory_includes_jar> 
--main-jar sample.jar --main-class Sample
$ ls sample-*
sample-1.0.dmg
Withcustomruntimeimage:
# Show ependent modules
$ jdeps -summary Sample.class
Sample.class -> java.base
Sample.class -> java.desktop
# Create runtime image that consists only of dependent modules
$ jlink --add-modules java.base,java.desktop --output image
# Create installer with custom runtime image
$ jpackage --name sample --input <directory_includes_jar> 
--main-jar sample.jar --main-class Sample --runtime-image image
HeadtowardJava16(NightSeminarEdition)
KUBOTAYuji
SBISecuritySolutions
JJUGNightSeminar(2021/Mar/16)

Head toward Java 16 (Night Seminar Edition)