Slide 1

Slide 1 text

Java8〜16における バイトコード生成の変化 2021/May/23, Kengo TODA

Slide 2

Slide 2 text

whoami ● 15歳からプログラム書いてる35歳 ● Java, TypeScript, Gradle, Sphinx, development workflowなどが好き ● Javaバイトコードの静的解析ツールSpotBugsの開発メンバー 2

Slide 3

Slide 3 text

このセッションの狙い ● 静的解析ツール開発者の目線から、バイトコード生成の変化についてJEPにあるも のから細かいものまで紹介 ● 明日使えないムダ知識 ● OpenJDKのissueやcommitを掘るときに参考できるかもしれない 3

Slide 4

Slide 4 text

前提とする知識 ● javac, バイトコード, JEPなどの単語 ● Java 8〜16であった変化のざっくりした理解 4

Slide 5

Slide 5 text

Java 9 警戒していたほど 大変ではなかった jigsaw対応 ● JEP 220: Modular Run-Time Images ● JEP 238: Multi-Release JAR Files ● JEP 213: Milling Project Coin 5

Slide 6

Slide 6 text

2017年のJJUG CCC Springで…… 6 「Jigsaw対応こわい、やりたくない」と LTで言いまし た。 IBMとRed Hatに勝手に期待していた私。

Slide 7

Slide 7 text

jigsawはこわくなかった! 蓋を開けてみればJarの形式が変わるわけでもCLASSPATHという概念がなくなるわけ でもなく。静的にファイルを読む静的解析ツールにとっては破壊的変更とはなりませんで した。 JEP 220: Modular Run-Time Imagesだけは例外ですが、rt.jarがなくなっただけなの で解析ロジックには変更無く、初期化処理がちょっと変わっただけ。 7

Slide 8

Slide 8 text

MRJAR……対応する? Multi-Release JAR Filesのこと。異なるJavaバージョン向けの.classファイルを、ひとつ の.jarにまとめられる仕様。例えば「アノテーションが修飾する対象にMODULEを加えた いけど、MODULEの無いJava8もサポートしたい」のようなケースで利用できる。 便利ではあるがjavadocとsourcesは未対応なので、濫用は混乱のもと。 またバグの再現を難しくする可能性もあるので要注意。 SpotBugsでは未対応。解析のコンテキストがひとつ増える=実行時引数を増やす対応 が必要か。 8

Slide 9

Slide 9 text

$closeResourceメソッドの爆誕 Milling Project Coinでtry-with-resources構文が改善された。 表面的な改善点は桜庭さんの記事を参照。 バイトコード的には不要なnullチェックを排除するとともに、処理をメソッドに切り出して重 複を抑える工夫がされた。これにより「AutoCloseableインスタンスがCloseされたかどうか」 の確認に複数メソッドをまたぐ必要性が生じた。 9

Slide 10

Slide 10 text

これが$closeResourceメソッドだ! 10

Slide 11

Slide 11 text

Java 11 JLSに沿うように実装を 修正したら、様々なツールが 壊れた回 11 ● A change around try-with-resources ● JEP 181: Nest-Based Access Control ● JEP 336: Deprecate the Pack200 Tools and API

Slide 12

Slide 12 text

Dead code in $closeResource()? Java 9で導入された手法にもデッドコードがあるということでより効率的な方法をJava 11 b07で導入したところ、JaCoCoとSpotBugsで誤作動が発生。JaCoCoは修正済みだが SpotBugsは4.2.2時点で一部のみ対応。 SpotBugsが誤検知した内容は「nullじゃないとわかっているローカル変数のnullチェック をしている」。Eclipseのコンパイラ(ecj)では問題なし。 バイトコードを見てみると、確かにインスタンスのメソッドを利用したあとで、そのインスタン スがnullかどうか確認している。javacのバグでは? 12

Slide 13

Slide 13 text

SpotBugsにはOpenJDKのコミッタがいませんが、Issueで議論していた人の中にいた Ismael Juma氏がcompiler-devメーリスで代わりに聞いてくれました。 spotBugs and JDK-8194978: Javac produces dead code for try-with-resource その結果、驚愕の事実が判明。 OpenJDKコミッタじゃなくてもなんとかなるよ 13

Slide 14

Slide 14 text

「nullチェックしろとJLSに書いてあるよ」v7から。 14

Slide 15

Slide 15 text

try-with-resources構文のバイトコード変化まとめ 15 ● Java 7でtry-with-resources構文が導入される。 ● Java 9で、デッドコードを減らしコードの重複を省く目的で、 $closeResource() メソッ ドが自動的に生成されるようになる。メソッドをまたいだ解析が必要になる。 ● Java 11 b7で、デッドコードを減らす目的で $closeResource() メソッドが削除され る。無意味なnullチェックコードが含まれているが、JLSに必要と記載されているので 問題ない。

Slide 16

Slide 16 text

そのほかのJava 11の変更 ● JEP 181: Nest-Based Access Controlについてはじゅくちょー氏のブログに詳しい ので割愛。SpotBugsでの修正はこちら。 ● JEP 336: Deprecate the Pack200 Tools and APIはバイトコードの変化ではない が、JaCoCoに影響を及ぼしたということでここに載せておきます。 16

Slide 17

Slide 17 text

Java 14 OpenJDKとコミュニティが協調した 模範的な事例 ● JEP 305: Pattern Matching for instanceof (Preview) ● JEP 359: Records (Preview) 17

Slide 18

Slide 18 text

Pattern Matching:ことの発端 Number number = 1f; if (number instanceof Float f) { System.out.println( "number is a float: " + f ); } 一見問題なさそうなコードだが、 SpotBugsではロー カル変数を自分自身と比較している という警告が出 る。 JaCoCoではカバレッジの異常として報告される。 Eclipseのコンパイラ(ecj)では問題なし。 18

Slide 19

Slide 19 text

どのようなバイトコードか 7: aload_2 8: instanceof #8 // class java/lang/Float 11: ifeq ... 14: aload_2 15: checkcast #8 // class java/lang/Float 18: dup 19: astore_1 20: aload_2 21: checkcast #8 // class java/lang/Float 24: if_acmpne ... jdk-14.0.2+12同梱のjavacで確認。 確かにaload_2で取り出した同じローカル変数を if_acmpneで比較している。 19

Slide 20

Slide 20 text

Evgeny Mandrikov氏がProject Amberのメーリスで指摘 20

Slide 21

Slide 21 text

Rémi Forax氏がjavacのバグとして登録するよう返信 21

Slide 22

Slide 22 text

Evgeny Mandrikov氏がバグとして登録 22

Slide 23

Slide 23 text

Jan Lahoda氏が修正、Java 15のGAに入る 23

Slide 24

Slide 24 text

Java15で変化した内容 11: ifeq ... 14: aload_2 15: checkcast #8 // class java/lang/Float - 18: dup - 19: astore_1 - 20: aload_2 - 21: checkcast #8 // class java/lang/Float - 24: if_acmpne ... + 18: astore_1 余計な比較が無くなり、非常にシンプルなバイト コードに。🎉 素早いリリースサイクルとオープンな開発体制がプ ラスに働いた模範的な事例ではないでしょうか? 24

Slide 25

Slide 25 text

Recordのequals()がベストプラクティスに沿ってない? Java14もうひとつの誤検知は、Recordクラスのequals()実装がベストプラクティスに沿って ないというもの。 equals()といえばIDEによる自動生成もこなれている領域。 javacが妙なコードを生成するとは思えないのだが……? 25

Slide 26

Slide 26 text

なんじゃこりゃ 26

Slide 27

Slide 27 text

困ったときのOracleさんも、目的については説明せず 27

Slide 28

Slide 28 text

なぜ動的生成なのか、調査中 愚直な実装より利点が多いのでinvoke dynamicを利用しているはず。.classファイルサイ ズ削減とか、実行パフォーマンスとか? ● Switch pattern matching の実装に invokedynamic が良いという話はあった 情報お待ちしておりますm(_ _)m 28

Slide 29

Slide 29 text

Microbenchmark in my local (https://git.io/JODNQ) IntelliJ IDEAの生成したhashCode()実装と、 record標準のinvokedynamicを利用した hashCode()実装との単純な性能比較。横軸は Recordに含まれるフィールドの数と種類。縦軸は秒 間実行回数で、高い方が速い。 invokedynamicを利用したhashCode()実装の方が 若干遅い。primitiveだと特に差が顕著。 今は遅いとはいえ、ObjectMethodsクラスが生成す るロジックはJavaの成長とともに成長するので、強 い理由がなければinvokedynamicを利用した hashCode()実装で良いのでは。 29

Slide 30

Slide 30 text

注意:配列をRecordに使わないこと 配列使うとmutableになるから使うべきではない、というのに加えて。生成される hashCode() と equals(Object), toString() が配列を考慮しない実装になっているため、 JavaのRecordでは配列を使わないほうが良いようです。 詳細はブログ記事参照。 30

Slide 31

Slide 31 text

Java 16 解析ツールにとって 肩身が狭い世の中 ● JEP 396: Strongly Encapsulate JDK Internals by Default 31

Slide 32

Slide 32 text

JDK内部の実装を積極的にカプセル化していく予定 JEP 396の影響で、Google Errorproneが依存しているcom.sun.tools.javacパッケージ などが標準でアクセスできなくなった。このためErrorproneのビルド時に--illegal-access オプションを追加する対応を行った模様。 続くJEP 403でもJDK内部実装は積極的にカプセル化され、将来的に は--illegal-accessオプションも削除予定。API標準化が進むことが期待されます。 32

Slide 33

Slide 33 text

まとめ Java 9, 11, 14, 16にわたって 様々な変更を見てきた ● OpenJDK is Open ● 14に問題があったら15で直せば いい ● デッドコード削除やIndy活用など 33

Slide 34

Slide 34 text

おわりに:オープンソースしようぜ! ● 今回の事例のように、あなたの報告したIssueが開発の手助けになるかも ● (OpenJDKに限らず)コミュニティの開発者は新しい人に協力的な事が多い ● オープンソースガイドラインなども参考に 34

Slide 35

Slide 35 text

ご清聴ありがとうございました 35

Slide 36

Slide 36 text

付録:Opcodeは増えたか? 増減なし ● Java8のJVMSとJava16のJVMSでは

が増えてない 36

Slide 37

Slide 37 text

付録:Attributesは増えたか? 増えた ● JVMS 4.7.25. The Module Attribute ● JVMS 4.7.26. The ModulePackages Attribute ● JVMS 4.7.27. The ModuleMainClass Attribute ● JVMS 4.7.28. The NestHost Attribute ● JVMS 4.7.29. The NestMembers Attribute ● JVMS 4.7.30. The Record Attribute 37