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

 

2019/04/04

Java SE 12: Not Mentioned in JEP

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

Java SE 12 was released in 19th March!

So, I'll show updates in Java SE 12. But, I don't explain about the new features mentioned in JEPs.

In general, the updates in Java SE 12 are fewer than other Java SE releases. In the point of syntax, switch statement changes to switch expression (JEP 325). It's big change! But, the API updates are not big.

 

Removed APIs

There are 5 removed methods in Java SE 12.

All are the methods that forRemoval of @Deprecated set true in Java SE 10.

  • java.io.FileInputStream.finalize()
  • java.io.FileOutputStream.finalize()
  • java.util.zip.Deflater.finalize()
  • java.util.zip.Inflater.finalize()
  • java.util.zip.ZipFile.finalize()

Because these methods are only finalize methods, so you feel no difficulty for coding. If you have difficulty, the design may be wrong.

 

In addition, 4 methods were removed.

All of them are override getCause methods, and these classes use getCause method of super class after removing.

  • java.lang.FileNotFoundException.getCause()
  • java.lang.ExceptionInInitializerError.getCause()
  • java.lang.reflext.UndeclaredThrowableException.getCause()
  • java.security.PrivilegedActionException.getCause()

 

APIs proposed for Removal

There are no APIs that are added for removal in Java SE 12.

 

Added APIs

java.lang.constant package

A package was added to java.base module!

But, most of developer seem never to use the package. Because, the pacakge was introduced for "Condy." Cody is for dynamic constant loading as an equivalent to Indy for dynamic method invocation.

APIs included in new package are based on JEP 334: JVM Constants API.

In addition, some interfaces/class and methods are introduced in java.lang.invoke package.

Added interfaces/class are shown below:

  • TypeDescriptor interface
  • TypeDescriptor.OfField interface
  • TypeDescriptor.OfMethod interface
  • VarHandle.VarHandleDesc class

There are 3 methods added:

  • Optional<MethodHandleDesc> MethodHandle.describeConstable()
  • String MethodType.describeString()
  • Optional<MethodTypeDesc> describeConstable()

Moreover, there are 4 methods added in java.lang.Class class:

  • Class<?> arrayType()
  • Class<?> componentType()
  • Optional<ClassDesc> describeConstable()
  • String descriptorString()

 

Besides these, Enum class implements Constable interface, and Enum.EnumDesc inner class is added. Integer/Long/Float/Double/String classes are updated to implement ConstantDesc insterface. These classes are added describeConstant method and resolveConstantDesc method.

However, most of developers seems not to use these new condy APIs for usual software development.

 

java.io package

InputStream class

InputStream class has skip method, and a related method is added:

  • void skipNBytes(long n)

skip method javadoc said "The skip method may, for a variety of reasons, end up skipping over some smaller number of bytes." For this reason, skip methods return actual number of skipping over.

Comparatively, skipNBytes method skip over and discards exactly n bytes. Because of exactly skipping over, type of skipNBytes return value is void.

 

java.lang package

I listed condy related updates of java.lang package, so I'll show other updates.

 

Character.UnicodeBloch/UnicodeScript class

Java SE 12 starts to support Unicode 11.0, but this update is not described in JEP. Java SE 11 supports Unicode 10.0.

For this update, some Unicode blocks and scripts are added. For example, CHESS_SYMBOLS that means chess pawns is added.

 

String class

As previously described, String class added two condy related methods. In addition, a method is added:

  • String indent(int n)

indent method inserts specified number of white spaces into head of line, and inserts new line (\n) into end of string.

jshell> var text = "Hello\nWorld!"
text ==> "Hello\nWorld!"

jshell> System.out.println(text)
Hello
World!

jshell> text = text.indent(4)
text ==> "    Hello\n    World!\n"

jshell> System.out.println(text)
    Hello
    World!


jshell>

If argument is minus value, indent method removes specified number of white spaces. In this case, indent method only remove white spaces.

jshell> text = text.indent(-2)
text ==> "  Hello\n  World!\n"

jshell> text = text.indent(-3)
text ==> "Hello\nWorld!\n"

jshell>

Originally, Java SE 12 planed to introduce JEP 326 Raw String Literals, but gave it up.

JEP 326 includes some String class methods definition. However, these methods were also abandoned.

Only indent method was added to String class. I don't know why.

 

java.net package

SecureCacheResponse class

A method to get SSL/TLS session is added to SevureCacheResponse class.

  • Optional<SSLSession> getSSLSession()

 

ServerSocket class

A constructe is added to ServerSocket class!

  • protected ServerSocket(SocketImpl impl)

However, this constructer is proteced method, so we don't use it in normal software development.

 

java.nio.file package

Files class

A method for finding mismatch points of two fiels is introduced:

  • static long mismatch(Path path, Path path2)

 

mismatch method finds the first difference between path and path2, and returns the position of the first difference of two.

If two files are same, mismatch method returns -1.

C:\demo>cat hello1.txt
Hello, World!

C:\demo>cat hello2.txt
Hello, Java!

C:\demo>cat hello3.txt
Hello, World!

C:\demo>jshell
|  Welcome to JShell -- Version 12
|  For an introduction type: /help intro

jshell> import java.nio.file.*

jshell> var hello1 = Paths.get("hello1.txt")
hello1 ==> hello1.txt

jshell> var hello2 = Paths.get("hello2.txt")
hello2 ==> hello2.txt

jshell> var hello3 = Paths.get("hello3.txt")
hello3 ==> hello3.txt

jshell> Files.mismatch(hello1, hello2)
$5 ==> 7

jshell> Files.mismatch(hello1, hello3)
$6 ==> -1

jshel>

 

java.text package

CompactNumberFormat class

We occasionally want to show numbers using K or M like 10K (10,000). But, Java didn't have such number formatter API before.

Then, Java SE 12 introduced CompactNumberFormat class.

CompaceNumberFormat is a subclass of NumberFormat class, so usage is same. There is a constructor, but it is convenient for us to use factory methods of NumberFormat class.

jshell> import java.text.*

jshell> var formatter = NumberFormat.getCompactNumberInstance()
formatter ==> java.text.CompactNumberFormat@73cf7357

jshell> formatter.format(10000)
$3 ==> "10K"

jshell> formatter.format(12345)
$4 ==> "12K"

jshell> formatter.format(15678)
$5 ==> "16K"

jshell>

The method to get CompactNumberFormat object is NumberFormat.getCompactNumberInstance method. If you use it without argument, you get CompactNumberFormat object of default locale.

In above example, the default locale is English, so the result of formatting 10,000 was 10K.

It's to be noted that default rounding is RoundingMode.HALF_UP. So, CompactNumberInstance method returns 16K when formatting 15,678.

If you'd like to use RoundingMode.FLOOR, you should use setRoundingMode method.

When formatting the number more than 1,000,000, CompactNumberFormat object uses M.

jshell> formatter.format(12_345_678)
$3 ==> "12M"

jshell>

It's also to be noted that the return type of getCompactNumberInstance method is not CompactNumberFormat class but NumberFormat class.

We, Japanese count number by 104not 103. Number unit of 104 is "万" and 108 is "億".

If you'd like to use "万" or "億", you chage locale.

jshell> var formatter = NumberFormat.getCompactNumberInstance(new Locale("jp"), NumberFormat.Style.SHORT)
formatter ==> java.text.CompactNumberFormat@952071d5

jshell> formatter.format(100_000)
$5 ==> "10万"

jshell> formatter.format(123_456)
$6 ==> "12万"

jshell>

Second argument type of getCompactNumberInstance method is Number.Style enum. It is introduced in Java SE 12.

There are two constants defined in Number.Style: LONG and SHORT. No argument getCompactNumberInstance method uses SHORT by default.

When locale is jp (Japanese), 100,000 is formatted to "10万" and 123,456 is formatted to "12万".

 

In case of LONG;

jshell> var formatter = NumberFormat.getCompactNumberInstance(new Locale("en"), NumberFormat.Style.LONG)
formatter ==> java.text.CompactNumberFormat@952071d5

jshell> formatter.format(10000)
$7 ==> "10 thousand"

jshell> formatter.format(12345)
$8 ==> "12 thousand"

jshell>

"10 thousand" is compact, isn't it 🤔.

 

NumberFormat class

As previously explained, two static factory methods are introduced:

  • static NumberFormat getCompactNumberInstance()
  • static NumberFormat getCompactNumberInstance(Locale locale, NumberFormat.Style formatStyle)

java.text.spi.NumberFormatProvider were also added getCompactNumberInstance method.

 

NumberFormat.Field class

Two constants are added:

  • static final NumberFormat.Field PREFIX
  • static final NumberFormat.Field SUFFIX

 

NumberFormat.Style enum

Style enum is also explained previously. It is for constructing CompactNumberFormat object.

There are two constants: LONG and SHORT.

 

java.util.concurrent package

CompletionStage class

5 similar methods to exceptionally method are introduced.

  • default CompletionStage<T> exceptionallyAsync(Function<Throwable, ? extends T> fn)
  • default CompletionStage<T> exceptionallyAsync(Function<Throwable, ? extends T> fn, Executor executor)
  • default CompletionStage<T> exceptionallyCompose (Function<Throwable, ? extends CompletionStage<T>> fn)
  • default CompletionStage<T> exceptionallyComposeAsync(Function<Throwable, ? extends CompletionStage<T>> fn)
  • default CompletionStage<T> exceptionallyComposeAsync(Function<Throwable, ? extends CompletionStage<T>> fn, Executor executor)

exceptionally method is called when an exception is thrown in the previsou stage. If no exception in the previous stage, exceptionally method are skipped, and then next stage are executed.

exceptionallyAsync methods are called asynchronously as can be seen by method name.

jshell> import java.util.concurrent.*;
  
jshell> CompletableFuture.runAsync(() -> {
   ...>     System.out.println(Thread.currentThread());
   ...>     throw new RuntimeException();
   ...> }).
   ...> exceptionally(ex -> {
   ...>     System.out.println(Thread.currentThread());
   ...>     return null;
   ...> }).get();
Thread[ForkJoinPool.commonPool-worker-3,5,main]
Thread[main,5,main]
$2 ==> null

jshell> CompletableFuture.runAsync(() -> {
   ...>     System.out.println(Thread.currentThread());
   ...>     throw new RuntimeException();
   ...> }).
   ...> exceptionallyAsync(ex -> {
   ...>     System.out.println(Thread.currentThread());
   ...>     return null;
   ...> }).get();
Thread[ForkJoinPool.commonPool-worker-3,5,main]
Thread[ForkJoinPool.commonPool-worker-3,5,main]
$3 ==> null

jshell>

Executing by JShell, you can see exceptionally method is executed in main thread, but exceptionallyAsync method is executed in worker thread managed by ForkJoinPool.

 

The Argument of exceptionally method is a lambda expression that gets Throwable object as argument and return something value. On the other hand, the return value tyep of exceptionallyCompose method is CompletionStage class.

thenCompose method returns also CompletionStage object, so exceptionallyCompose is similar to thenCompose. As you may know, exceptionallyComposeAsync method is asynchronous version of exceptionallyCompose.

 

java.util.stream package

Collectors class

A method that execute two terminal operations and gathers these return values is introduced:

  • static <T,R1,R2,R> Collector<T,?,R> teeing(Collector<? super T,?,R1> downstream1, Collector<? super T,?,R2> downstream2, BiFunction<? super R1,? super R2,R> merger)

First and second argument is objects implicated Collect interface (usually we use static methods of Collectors class.) Third argument is a lambda expression for combining first and second Collect objects return values.

The following shows string concatination and couting number of characters, and then combining these two operations return values.

jshell> var text = List.of("a", "b", "c", "d", "e")
text ==> [a, b, c, d, e]

jshell> text.stream().
   ...> collect(Collectors.teeing(Collectors.joining(), 
   ...> Collectors.counting(), 
   ...> (x, y) -> x + " : " + y))
$1 ==> "abcde : 5"

jshell>

It seems very useful!

 

javax.lang.model package

SouceVersion class

Every version up, SouceVersion add a constant:

  • static final SourceVersion RELEASE_12

On the other hand, SourceVersion class is now implementing Constable interface.

 

Others

javax.naming.ldap.spi package is introduced for LDAP service provider. The package includes two class: LdapDnsProvider and LdapDnsProviderResult.

javax.net.ssl.HttpsURLConnection class is added getSSLSession method.

 

Swing is also added a method.

getChooserShortcutPanelFiles method is introduced in javax.swing.filechooser.FileSystemView class. But, usually we don't use the method. It is for internal use.

 

That's all.