Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Java8〜16におけるバイトコード生成の変化 / Changes of Bytecode Generation from Java 8 to 16

Java8〜16におけるバイトコード生成の変化 / Changes of Bytecode Generation from Java 8 to 16

JJUG CCC 2021 Springで発表したものです。Java8〜16においてOpenJDKが生成するバイトコードにどのような変化があったのか、静的解析ツール開発者の視点から紹介します。
https://fortee.jp/jjug-ccc-2021-spring/proposal/288ac3a0-9683-4ed5-89e9-fbe1a61ca5c8

Kengo TODA

May 23, 2021
Tweet

More Decks by Kengo TODA

Other Decks in Technology

Transcript

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  19. どのようなバイトコードか
    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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  24. 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

    View full-size slide

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

    View full-size slide

  26. なんじゃこりゃ
    26

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  37. 付録: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

    View full-size slide