2019/09/18

JEPでは語れないJava SE 13

このエントリーをはてなブックマークに追加

アメリカ西海岸時間の9月17日にJava SE 13がリリースされました。

ちょうどサンフランシスコではOracle Code Oneが開催されていて、櫻庭も参加しています。OC1に参加しているのでなかなか書く時間がとれないのですが、恒例となっているのでJEPで提案されている以外のJava SEの変更をまとめておきます。

Java SE 13も変更はかなり少ないです。提案されたJEPは5個。ライブラリに関するJEPはなく、言語使用に関するJEPが2つです。

1つがswitchが文から式になるということ(JEP 354)。Java SE 12でもPreview機能で入っていましたが、再検討されて新たにPreview機能として入っています。値を返す時のyieldが一番の違いです。

もう1つが、テキストブロック(JEP 355) こちらはJava SE 12で入らなかったJEP 326 Raw String Literalの再検討版です。もちろん、テキストブロックもpreview機能なので、今後変更される可能性があります。

 

さて、本題のAPIの方です。こちらもそれほど変更は大きくありません。

今回もABC順にならんでいます。同じように、セキュリティ系のAPIはちゃんと理解していないので、省略してます。

 

廃止になったAPI

Java SE 13では2つのメソッドが廃止になっています。

いずれも、Java SE 9で@DeprecatedのforRemovalがtrueになったメソッドです。

  • java.lang.Runtime.traceInstructions(boolean)
  • java.lang.Runtime.traceMethodCalls(boolean)

Java SE 12以前のJavadocを見ていただければ分かりますけど、この2つのメソッドの説明は"Not implemented, does nothing." なぜ、今まで残していたのか不思議なくらいですねw

 

廃止予定のAPI

廃止予定APIの追加は多いです。

パッケージ

  • javax.security.cert

クラス

  • javax.security.cert.Certificate
  • javax.security.cert.X509Certificate

例外

  • javax.security.cert.CertificateEncodingException
  • javax.security.cert.CertificateException
  • javax.security.cert.CertificateExpiredException
  • javax.security.cert.CertificateNotYetValidException
  • javax.security.cert.CertificateParsingException

メソッド

  • java.lang.String.formatted
  • java.lang.String.stripIndent
  • java.lang.String.translateEscapes
  • javax.net.ssl.HandshakeCompletedEvent.getPeerCertificateChain
  • javax.net.ssl.SSLSession.getPeerCertificateChain

javax.security.certパッケージは、java.security.certパッケージを使うようにということです。javax.security.certパッケージはJava SE 9で@Deprecatedになったのですが、Java SE 13でforRemoval=trueになったということで、廃止することが決まったということです。

Stringクラスの3つのメソッドはJava SE 13で追加されたメソッドです。Java SE 13で追加されたのに、forRemoval=trueというのは、どういうこと?という感じですね。

これらのメソッドの機能については後述しますが、いずれもテキストブロックに関連しているメソッドなのです。つまり、テキストブロックがpreviewから正式な仕様に変更される時に、これらのメソッドも変更されるかもということを示しているわけです。

javax.net.sslパッケージの2つのメソッドもjavax.security.certパッケージに関連したメソッドです。HandshakeCompletedEvent.getPeerCertificateChainメソッドは返り値がX509Certificateクラスの配列なのです。今後はgetPeerCertificateChainメソッドではなく、同じクラスのgetPeerCertificatesメソッドを使うようにします。

SSLSession.getPeerCertificateChainメソッドも同じで、代わりにgetPeerCertificatesメソッドを使うようにします。

 

追加されたAPI

java.langパッケージ

JEPにはなっていないのですが、Java SE 13ではUnicode 12.0をサポートしています。その関連で2つのクラスに定数が増えました。

Character.UnicodeBlockクラス

UnicodeBlockクラスはその名の通り、Unicodeのブロックを表す定数を定義しているクラスです。Unicode 12.1で導入されたブロックが追加になりました。

  • EGYPTIAN_HIEROGLYPH_FORMAT_CONTROLS
  • ELYMAIC
  • NANDINAGARI
  • NYIAKENG_PUACHUE_HMONG
  • OTTOMAN_SIYAQ_NUMBERS
  • SMALL_KANA_EXTENSION
  • SYMBOLS_AND_PICTOGRAPHS_EXTENDED_A
  • TAMIL_SUPPLEMENT
  • WANCHO

Character.UnicodeScript列挙型

enumのUnicodeScriptにもUnicode 12.0で導入されたスクリプトが追加になりました。

  • ELYMAIC
  • NANDINAGARI
  • NYIAKENG_PUACHUE_HMONG
  • WANCHO

 

追記

Oracleの佐藤さんに、Java 13が対応しているUnicodeのバージョンが12.0ではなく、12.1と教えていただきました。本文は修正してあります。

なお、対応しているUnicodeのバージョンはjava.lang.CharaccterクラスのJavadocに記述してあるそうです。Java 13の場合は以下のように表記されています。

Character information is based on the Unicode Standard, version 12.1.

(Java 13 CharacterクラスのJavadocより引用)

 

Stringクラス

前述したように、Stringクラスにはテキストブロック関連のメソッドが3つ追加されました。しかも@DeprecatedのforRemoval=trueなので、コンパイル時に警告が出ます。

  • String formatted(java.lang.Object... args)
  • String stripIndent()
  • String translateEscapes()

J2SE 5でstaticメソッドのformatメソッドが導入されましたが、formattedメソッドはそれのインスタンスメソッド版です。

jshell>  import java.time.*

jshell> "%s%n".formatted(LocalDate.now())
|  警告:
|  java.lang.Stringのformatted(java.lang.Object...)は推奨されておらず、削除用にマークされ ています
|  "%s%n".formatted(LocalDate.now())
|  ^--------------^
$1 ==> "2019-09-17\r\n"

jshell>

formatメソッドもformattedメソッドも内部ではjava.util.Formatterクラスのformatメソッドをコールしています。

stripIndentメソッドは複数行からなる文字列の先頭文字がホワイトスペースだった場合、前詰めした文字列に変換するためのメソッドです。

jshell> var text = "  abc\n" +
   ...> "   def\n" +
   ...> "    ghi"
text ==> "  abc\n   def\n    ghi"

jshell> System.out.println(text)
  abc
   def
    ghi

jshell> System.out.println(text.stripIndent())
abc
 def
  ghi
|  警告:
|  java.lang.StringのstripIndent()は推奨されておらず、削除用にマークされています
|  System.out.println(text.stripIndent())
|                     ^--------------^

jshell>

なお、テキストブロックを使用すると、勝手に前詰めされます。コンパイル時にこのメソッドを使用しているかどうかまでは調べていないので、ぜひ調べてみてください。

translateEscapesメソッドはエスケープシーケンスを変換するメソッドです。

たとえば、"\n"という2文字で表されているエスケープシーケンスをU+000Aに置き換えます。

とはいうものの、このメソッドの使い道がイマイチよく分からないんですよね。文字列を出力する時には、置き換えられていたので、意識することはなかったのですが... どういう時に使うんだろう?

なお、このメソッドではUnicodeのエスケープは置き換えを行わないそうです。

 

java.nioパッケージ

Buffer系のクラスにメソッドがいろいろと追加されました。

Bufferクラス

Bufferクラスにはsliceメソッドがオーバーロードされました。

  • Buffer slice(int index, int length)

今まで使われていたsliceメソッドは引数なしでcurrent positionとlimitの間を切り出すメソッドでした。

オーバーロードされたのは明示的にインデックスと長さを指定して切り出すために使用されます。

実際には内部で、引数なしのsliceと同じような処理行っているので、使い勝手をよくするためのメソッドということですね。

jshell> var buffer = ByteBuffer.allocate(5)
buffer ==> java.nio.HeapByteBuffer[pos=0 lim=5 cap=5]

jshell> buffer.position(2)
$3 ==> java.nio.HeapByteBuffer[pos=2 lim=5 cap=5]

jshell> var buffer2 = buffer.slice()
buffer2 ==> java.nio.HeapByteBuffer[pos=0 lim=3 cap=3]

jshell> var buffer3 = buffer.slice(2, 3)
buffer3 ==> java.nio.HeapByteBuffer[pos=0 lim=3 cap=3]

jshell>

同じ切り出しを引数なしのsliceメソッドと、引数ありのsliceメソッドでやってみました。

なお、Bufferクラスはアブストラクトクラスなので、ByteBufferクラスを使用しています。

もちろん、他のCharBufferクラスなどでも引数ありsliceメソッドがオーバーロードされています。

ByteBuffer/CharBuffer/DoubleBuffer/FloatBuffer/IntBuffer/LongBuffer/ShortBufferクラス

それぞれのクラスにgetメソッドが2つ、putメソッドが2つオーバーロードされています。

Bufferクラスに追加されたsliceメソッドは返り値の方がBufferクラスなのですが、ByteBufferクラスなどでオーバーロードされたメソッドは返り値の型がそれぞれのクラスなので、Bufferクラスでは定義できないのでした。

ただ、使い道はどのクラスでも同じなので、ここではByteBufferクラスで説明します。

  • ByteBuffer get(int index, byte[] dst)
  • ByteBuffer get(int index, byte[] dst, int offset, int length)
  • ByteBuffer put(int index, byte[] src)
  • ByteBuffer put(int index, byte[] src, int offset, int length)

今までのgetメソッドはpositionの位置から読み込みを行うか、インデックスを指定して1バイト読み込むかのどちらかしかありませんでした。新しくオーバーロードされたgetメソッドはインデックスを使用して第2引数のバイト配列に読み込みを行います。

jshell> var b = new byte[]{0, 1, 2, 3, 4, 5}
b ==> byte[6] { 0, 1, 2, 3, 4, 5 }

jshell> var buffer = ByteBuffer.wrap(b)
buffer ==> java.nio.HeapByteBuffer[pos=0 lim=6 cap=6]

jshell> var bytes = new byte[2]
bytes ==> byte[2] { 0, 0 }

jshell> buffer.get(2, bytes)
$13 ==> java.nio.HeapByteBuffer[pos=0 lim=6 cap=6]

jshell> bytes
bytes ==> byte[2] { 2, 3 }

jshell>

ここでは、インデックス2から2バイト読み込んでます。

putメソッドも同じで、今まではpositionから書き込みを行うか、インデックスを使用して1文字の書き込みを行うだけだったのが、インデックスを使用してバイト配列で書き込みが行えるようになりました。

MappedByteBufferクラス

MappedByteBufferクラスにはforceメソッドがオーバーロードされました。

  • MappedByteBuffer force(int index, int length)

今までのforceメソッドは引数なしで、メモリにマップされた内容を強制的にファイルに書き出すというメソッドでした。オーバーロードされたforceメソッドはインデックスと長さを指定して強制的にファイルに書き出しを行います。

 

java.nio.fileパッケージ

FileSystemsクラス

FileSytemクラスでは、3種類のnewFileSystemメソッドがオーバーロードされています。

  • FileSystem newFileSystem(Path path)
  • FileSystem newFileSystem(Path path, Map env)
  • FileSystem newFileSystem(Path path, Map env, ClassLoader loader)

newFileSystemメソッドはFileSystemクラスのファクトリメソッドなのですが、今までは場所を指定するのにURIを使用していました。Pathインタフェースも使えたのですが、クラスローダ―を指定する必要がありました。

Java 13で追加された3種類はどれもPathインタフェースで場所を指定します。

ZIPファイルやJARファイルをファイルシステムとみなして扱うような時に使用するメソッドなのですが、パスで指定できるようになったので、少しだけ扱いやすくなりました。

jshell> var path = Paths.get("C:\\Program Files\\Java\\jdk-13\\lib\\src.zip")
path ==> C:\Program Files\Java\jdk-13\lib\src.zip

jshell> var fileSystem = FileSystems.newFileSystem(path)
fileSystem ==> C:\Program Files\Java\jdk-13\lib\src.zip

jshell> Files.list(fileSystem.getPath(".")).forEach(System.out::println)
./jdk.zipfs
./jdk.xml.dom
./jdk.unsupported.desktop
./jdk.unsupported
./jdk.security.jgss
./jdk.security.auth
./jdk.sctp
./jdk.scripting.nashorn.shell
./jdk.scripting.nashorn
./jdk.rmic
./jdk.pack
./jdk.net
    <<以下、省略>>

jshell>

 

java.time.chronoパッケージ

JapaneseEraクラス

日本の元号を表すクラスですが、令和が定数として追加されました。メジャーバージョンとしてはJava 13からということです。

  • REIWA

javax.annotationパッケージ

ProcessingEnvironmentインタフェース

アノテーションの処理を行う時に使用するインタフェースがProcessingEnvironmentインタフェースです。

  • boolean isPreviewEnabled()

プレビュー機能を使用する時にはコンパイル/実行時に--enable-previewオプションを使用します。isPreviewEnabledメソッドは--enable-previewが指定されていればtrue、いなければfalseを返します。

 

javax.lang.modelパッケージ

SourceVersion列挙型

毎度のお約束ですが、定数にJava 13が追加されました。

  • RELEASE_13

 

javax.lang.model.elementパッケージ

ExecutableElementインタフェース

ExecutableElementインタフェースは、クラスやインタフェースのメソッド、コンストラクタなど実行できる要素を表すためのインタフェースです。

  • TypeMirror asType()

ExecutableElementインタフェースで表している実行要素がメソッドなのか、コンストラクタなのか、初期化子なのかを判別するために使用します。

 

javax.toolsパッケージ

StandardJavaFileManagerインタフェース

javax.toolsパッケージはコンパイラ系のクラスが定義されているのですが、そこで使用するファイルマネージャがStandardJavaFileManagerインタフェースです。

  • Iterable<? extends JavaFileObject> getJavaFileObjectsFromPaths​(Collection<? extends Path> paths)

これまで使われていたgetJavaFileObjectsFromPathメソッドは引数の型がIterableインタフェースだったのですが、Collectionインタフェースを引数にとるメソッドがオーバーロードされました。

逆に引数の型がIterableインタフェースの方は@Deprecatedになっています。

 

javax.xml.parsersパッケージ

DocumentBuilderFactoryクラス

まさか、この期におよんでDOMパーサーに機能が追加されるとは思いもよりませんでしたw

  • DocumentBuilderFactory newDefaultNSInstance()
  • DocumentBuilderFactory newNSInstance()
  • DocumentBuilderFactory newNSInstance​(String factoryClassName, ClassLoader classLoader)

いずれも名前空間を認識したDocumentBuilderFactoryオブジェクトを生成するファクトリクラスです。

 

他にもセキュリティ系のAPIに追加がありますが、よく分からないので省略します。

ということで、Java 13のAPIの変更をまとめてみましたが、やっぱり変更は少ないですね。

 

2019/06/15

JJUG CCC 2019 Spring

このエントリーをはてなブックマークに追加

もう1月も前ですが、5月18日にJJUG CCC 2019 Springが開催されました。Oracle Codeの次の日ですね。

今回はProject LoomのFiberについてプレゼンしました。資料はこちら。

Fiberはいわゆる軽量スレッドです。

プレゼンではなぜ軽量スレッドが必要なのかという部分について、かなり時間をかけて説明しました。

OSに強く結びついたThreadを使った場合、コンテキストスイッチにコストがかかります。時間もかかるし、メモリも多く使用します。

これは、スレッドごとに用意されるJVM Stackをすべて退避させたり、もどしたりする必要があるからです。JVM Stackは、そのスレッドでコールされているメソッドコールのスタックで、メソッドで使われるローカル変数やオペランドスタックも含みます。

また、OSがスレッドのスケジューリングを管理しているため、コンテキストスイッチがいつ行われているかもJava側からは管理することができません。

そのために求められているのが、JVMで管理する軽量スレッド、つまりFiberというわけです。

これに対し、Fiberを使えば、ワーカースレッド上で動作するため、スケジューリングはJVMで管理でき、状態の退避用のメモリも少なくてすみます。

Fiberでは、スケジューリングには既存のFork/Join Frameworkが使用されます。コンテキストスイッチには処理の中断、再開のための仕組みが必要ですが、これはContinuationで行います。Continuationはいわゆる継続を実現させるための仕組みですが、JavaのContinuationは限定継続になります。

プレゼンの中ではContinuationをかなり強引にwait-notifyAllと同じようなものと説明してしまったため、誤解させてしまったのではないかと反省しています。もちろん、Continuationもwait-notifyAllも処理の中断・再開をするという機能はありますが、一般的なContinuationでできることは処理の中断・再開だけではありません。ちょっと説明不足でした。

しかし、今のところProject LoomではContinationを積極的に活用するようなシナリオはないように見えます。現状は、I/O待ちのためにパフォーマンスが落ちていることに対して、Fiberを使ってI/O待ちをなるべく解消することがメインの目的のようです。

これは、処理の中断・再開を行うFiberのparkメソッド、unparkメソッドがデフォルトのアクセス制御であることからも分かります。park/unparkを使用しているのは、ReentrantLockクラスなどのロック系のクラスや、java.nio.channelパッケージのソケット通信などのクラスなどです。

プレゼンの中では前日に行ったOracle Codeのデモについても説明しました。JDKに含まれているHTTPサーバーであるcom.sun.net.httpserver.HttpSErverクラスを使用して、Fiberを指定しているだけです。これだけで、既存のExecutorServiceインタフェースを使用した場合よりもスレッド数やスループットが向上しました。

とはいうものの、現状Fiberはそこまで速くありません。

コンテキストスイッチが起きないようにうまくタスク分けして、I/O待ちも多重化するなどして待たなければいけないスレッドを最小化するなどのチューニングを行えば、Fiberより高いパフォーマンスを得ることができます。

ただ、それをするには設計やチューニングなどの高度な知識や経験が求められます。Fiberを使うことで誰でも簡単にパフォーマンスを向上させられるということが、Fiberの意義の1つなのではないかと感じています。

また、Fiberのさらなるパフォーマンス向上のためにJVM Stackを操作することも考えられているようなので、今後に期待したいですね。

 

さて、以下は会場やsli.doでの質問とその回答です。

Q Fiberの中断前と再開後でワークスレッドが変わることがあるのか?

A. あります。

キターーーーーーー 「この分野は素人なのですが」な質問!!!

なんとか答えられてよかったですw

さて、現状はFiberはシリアライザブルではないのですが、シリアライザブルにする計画があります。このため、ワークスレッドが変化することもありますし、処理途中のFiberを他のCPUに移動させてそこで再開というシナリオも考えられます。

質問した伊藤さんも心配されていますが、ロガーのようにスタックトレースを保持させるようなものは、Fiberにするとやばいかもしれません。

Q. ある時点の処理で中断していたものを複数回再実行することはできるか?

A. 一般的な継続だとこれができるのですが、今のところJavaのFiberでは計画されていないようです。

Fiberは継続を行うための状態管理に既存のJVM Stackをそのまま使っています。そのため、再実行を行うには、JVM Stackを操作するバイトコードが必要になるはずですが、そこまでやるとかなり大がかりになってしまうためかもしれません。

Q. ワークスレッドは自動的に用意されるのか?

A. されます。

デフォルトではForkJoinPoolを利用してワークスレッドの割り当てを行っています。もちろん、他のスレッドプールに置き換えることもできます。

Q. OSによらないThreadがFiberだとしたら、Green Threadのようなもの?

A. まさにGreen Threadです。

Javaの初期のころ、Solaris向けのJavaはネイティブのスレッドではなく、JVMが管理するスレッドを使用していたのですが、それがGreen Threadです。

Q. Kotlinのcoroutineと何が違うのか?

A. Kotlinのことをよく知らないのですが、同じような機能のようですね。

Q. Continuationのscopeがよく分からない。コンテキストスイッチを行いたい複数のFiberからなるグループのようなもの?

A. 継続処理を行いたい範囲をしめすものです。

どこからでも自由に中断・再開を行うのは難しいので、継続ができる範囲を決めているという感じです。Fiberも内部でスコープを持っていて、Fiberのタスク処理の中だけで継続を行っています。

Q. ThreadとFiberの使い分け指針が知りたい

A. I/Oの待機やロックの取得を含む非同期処理であればFiberを使うのがよいと思います。

計算処理だけであるならパラレルストリームやFork/Join Frameworkを使いましょう。それ以外だったらThreadになると思いますが、今でもExecutorServiceにタスクを渡すのが主で、Threadを直接使うことはまずないはずです。

Q. 既存のThreadをFiberに置き換えるイメージがつかめません。ExecutorServiceでThreadを使うようなことはFiberでもできるのでしょうか。

A. Fiber.scheduleメソッドがExecutorService.submitメソッドのような感じです。

Fiber.scheduleメソッドはstaticメソッドなので、ExecutorServiceのようにExecutorsクラスでExecutorServiceインスタンスを生成して、それからsubmitするまでを行っているような感じです。

Q. Loom入りのJDKを含んだDocker Imageは公開されているか?

A. 私が調べたときにはなかったです。

Docker上でLoomのJDKをビルドしようとするとなぜか落ちてしまうので、私はローカルな環境でビルドしてからそれをコピーするDockerfileを作ってDockerのイメージを作ってました。

Q. Loomのリリース予定は未定だとしても、現時点での目標とかはあるのでしょうか?

A. 明確なリリース予定時期がFiberを作っている人たちの間ではあるのかもしれませんが、私には分からないです。

少なくとも、Windowsで動作できるようにならないとリリースはできないので、そこが最低限の目標となるのではないでしょうか。

2019/05/22

Oracle Code Tokyo 2019

このエントリーをはてなブックマークに追加

5月17日にシェラトン都ホテルで開催されたOracle Code Tokyoのキーノートで、@bitter_foxさんと一緒にデモをしてきました。

キーノートは2部構成で前半がOracleのGeorges Saabさん、後半がウルシステムズの漆原さんがモデレータのパネル。
私たちは前半のGeorgesのキーノートの中でデモを行いました。

 

当初、Oracle側からはJava SE 12か13の新機能についてデモをしてくれということだったのですが... switch文がswitch式になって書き方が変わったよとデモしてもつまらないですよね。

そこで、12もしくは13ではなく、今後登場する機能のデモでもいいかと聞いてみたら、OKが出たのです。

そこで、@bitter_foxさんと検討して、Project PanamaのVector APIとProject LoomのFiberのデモを行うことにしました。

 

デモのシナリオとしては以下の通り。デモの題材は画像処理のソフトフォーカスエフェクトです。

  1. デスクトップアプリで、Vector APIと従来のループを使った画像処理を比較
  2. デスクトップアプリはJavaFXで記述しているが、REST APIとしても画像処理できるようにする
  3. 画像処理を行うインスタンスを複数起動して、それらをコールするフロントのWebサーバーをFiberを使用して実装する
  4. 従来のThreadを使ったサーバーと、Fiberを使ったサーバーに対し負荷テストを行い、スループットなどの違いをリアルタイムで見せる

見てすぐわかるというところに、かなりこだわってみました。

上の写真がデスクトップアプリのスクリーンショットです。上部に24枚の写真のサムネイルが並んでおり、順々に画像処理していきます。だいたい、3倍から4倍ぐらいパフォーマンスが向上しました。

負荷テストの結果はGrafanaを使って表示させています。ちょっと条件を作りこんでいる部分はあるのですが、2倍程度のスループット向上が実現できました。

まぁ、お分かりだとは思いますけど、Vector APIの部分を櫻庭が作り、Fiberの部分を@bitter_foxさんが作っています。サーバーサイドがまったく分からない櫻庭の実力の低さが露呈してしまいましたw Grafanaを使ったモニタリングの仕組みなども、全部@bitter_foxさんにおまかせです 😅

それでも、見ていただいた方には結構好評だったようで、ほんとよかったです!

デモのコードについては、近日公開予定です。公開したら、解説のエントリーを書きます。

 

ところで、このデモで使った写真ですが、今までのJavaOneなどで撮った写真を使ってます。すべての人物が分かるのであれば、それはJavaの歴史にかなり精通しているといえると思いますよ。歴史的瞬間もあったりします。

ところで、土下座している人は何を謝っているんですかねぇw