blog

JVMバイトコードの深い理解

メソッドの Code プロパティには例外テーブルがあり、各例外テーブルのエントリは例外ハンドラを表し、from ポインタ、to ポインタ、ターゲット・ポインタ、および例外をキャッチした型の 4 つの部...

May 20, 2020 · 6 min. read
シェア

バイトコード

  1. iinc
  2. for
  3. try-catch-finally

メソッドの Code プロパティには例外テーブルがあり、各例外テーブルのエントリは、from ポインタ、to ポインタ、ターゲット・ポインタ、および例外をキャッチした型の 4 つの部分からなる例外ハンドラを表します。これらのポインタの値はバイトコードインデックスで、バイトコードを見つけるために使われます。その意味は、[from, to) バイトコード範囲で、例外型がスローされた場合、実行を継続するためにバイトコードで示されたターゲットポインタにジャンプします。

finally文にreturn文が含まれる場合、戻り値は一時変数に格納され、このfinally文の++i演算はiの値にのみ影響し、格納された一時変数の値には影響しません。

  1. リソース付きトライ

    リソースを解放する際にcloseを使用する際のバグを解決。

    ソース・コードでは、Throwable.addSuppressed() を追加して、抑制された例外をログに記録し、スローされた例外のスタック情報に表示します。

  2. オブジェクト関連のバイトコード命令

    1. < init> メソッドとは、オブジェクトの初期化メソッド、クラスのコンストラクタのメソッド、非定常変数の初期化、オブジェクトの初期化コードがこのメソッドにコンパイルされたものです。そのため、非定常変数で例外をスローするような初期化を行う場合は、コンストラクタまたはコードブロックで処理する必要があります。

    2. ** オブジェクトの作成は、3つの命令、new、dup、および<init>メソッドへのinvokespecialコールを必要とします。**JVMでは、クラスインスタンスの初期化メソッドは<init>であり、呼び出しnew命令は、単にクラスのインスタンス参照を作成し、この参照は、オペランドスタックの先頭に押され、この時点では、初期化メソッドを呼び出していません。invokespecialコール<init>メソッドは、実際のコンストラクタのメソッドを呼び出した後なので、重複命令の途中の役割は何ですか?

      invokespecial はオペランド・スタックの最上位にあるクラス・インスタンス参照を消費します。 invokespecial が呼び出された後、新しく作成されたクラス・インスタンスへの参照をスタックの最上位に保持したい場合、invokespecial が呼び出された後にクラス・インスタンスへの参照のコピーを作成する必要があります。そうしないと、<init>メソッドを呼び出した後、クラス・インスタンスへの参照がスタックから外れてしまい、作成したばかりのオブジェクトへの参照を取り出すことができません。さもなければ、<init>メソッドが呼び出され、クラスインスタンスの参照がスタックから消えた後、先ほど作成したオブジェクトへの参照を見つけることができなくなります。新しいオブジェクトへの参照がスタックの一番上にある状態で、astore 命令を使ってオブジェクトの参照をローカル変数テーブルに格納することができます。

      本質的に、dup ディレクティブが必要な理由は、<init> メソッドには戻り値がないからです。<init> メソッドが新しく生成された参照オブジェクトを戻り値として受け取るのであれば、問題はありません。

    3. < clinit>方法論

      < clinit>はクラスの静的初期化メソッドで、クラスの静的初期化ブロックと静的変数の初期化はこのメソッドにコンパイルされます。

      これは4つのコマンドがトリガーされたときに呼び出され、シナリオは以下の通り:

      new、リフレクション、デシリアライズなど、クラスオブジェクトのインスタンスを作成します;

      静的変数や静的メソッドへのアクセス;

      クラスの静的フィールドにアクセスしたり、静的フィールドに値を代入したりします;

      クラスのサブクラスを初期化します。

バイトコードの前進

  1. メソッド呼び出しディレクティブ

invokestatic: 静的メソッドを呼び出す際に使用します。

invokespecial: super キーワードを使用して、親クラスのプライベート・インスタンス・メソッド、コンストラクタ・メソッド、およびインスタンス・メソッドを呼び出すために使用します。

invokevirtual: 非プライベートなインスタンスメソッドを呼び出すために使用します。

invokeinterface: インターフェースのメソッドを呼び出すために使用します。

invokedynamic: 動的メソッドを呼び出すために使用します。

  1. invokevirtual ディレクティブがあるのに、なぜ invokespecial ディレクティブが必要なのですか?

invokespecialディレクティブはコンパイル時に決定されるメソッドを呼び出します。JDK 1.0.2ではinvokespecialディレクティブはinvokevirtualと区別するためにinvokenonvirtualと名付けられました。例えば、private メソッドはコンパイル時に決定される継承のため、サブクラスでオーバーライドされることはありません。そのため、private メソッドは invokespecial ディレクティブを使用して呼び出されます。

  1. Javaメソッド割り当ての原則

    invokevirtual 単一継承

    invokeinterface仮想メソッド・テーブルvtableに加えて、JVMは複数のインターフェイス実装をサポートするためにitableと呼ばれる構造を提供します。

    vtable、itableの要約:

    vtable, itableメカニズムは、Javaのポリモーフィズムを実装するための基礎です。

    サブクラスは親クラスのvtableを継承します。JavaクラスはすべてObjectクラスを継承し、Objectには継承できるメソッドが5つあるため、空のJavaクラスのvtableのサイズも5に等しくなります。

    finalとstaticによって変更されたメソッドは、継承によってオーバーライドされる方法がないため、vtableには表示されません。同様に、privateによって変更されたメソッドは、vtableには表示されないことが知られています。

    インターフェース・メソッドは invokeinterface ディレクティブを使用して呼び出します。 Java では、複数のインターフェース実装をサポートするために itable を使用しており、 itable は offsettable と method table の 2 つの部分で構成されています。インターフェース・メソッドを呼び出す際には、まず offsettable でメソッド・テーブルのオフセット位置を調べ、次に method table で特定のインターフェース実装を調べます。

  2. インボケスタティック

    メソッド・ハンドルまたはメソッド・ポインタとしても知られるmethodHandleは、java.lang.invokeパッケージ内のクラスで、Javaが他の言語のように関数をパラメータとして渡すことを可能にします。メソッドクラスのリフレクションと似ていますが、Methodクラスよりも柔軟で軽量です。

    実行時にしかわからないインスタンス・メソッドの呼び出し、つまりメソッドの動的割り当ての一種です。

  3. lambda式の原則

Lambda式が宣言されるところで invokedynamic ディレクティブが生成され、コンパイラは対応するブートストラップ・メソッドを生成する。

invokedynamic ディレクティブが最初に実行されると、対応する bootstrap メソッドが呼び出され、 LambdaMetafactory.metafactory メソッドを呼び出して内部クラスを動的に生成します。

ブートストラップ・メソッドは、最終的に Runnable インターフェースを実装する内部クラスを呼び出す、動的に呼び出される CallSite オブジェクトを返します。

ラムダ式の内容は静的メソッドにコンパイルされ、動的に生成された内部クラスから直接呼び出されます。

ラムダ呼び出しの実際の実行は、依然としてinvokeinterfaceディレクティブで行われます。

ラムダ式は、コンパイル時に無名の内部クラスを生成する代わりに、仕様の安定したバイトコード・バイナリ表現を提供するというアプローチを取ります。

  1. ジェネリックとバイトコード
  2. 同期原則、正常・異常を問わずロックを解放、トライファイナリーモニタ(パイプライン)
  3. 反射実施原理、反射インフレメカニズム

Javacコンパイル原理の紹介

javacのコンパイル・プロセスの7つの段階

  1. 解析: .javaソースファイルを読み込み、字句解析と構文解析を行います。
  2. enterシンボルテーブルの生成
  3. processアノテーションの処理
  4. attr: 意味的合法性のチェック、定数折りたたみ
  5. flowデータフロー解析
  6. desugar構文糖の除去
  7. generateバイトコードの生成

第5章 バイトコードから見たKotlin言語

  1. Metadataアノテーション なぜこのようなアノテーションが必要なのでしょうか? Kotlinのコードはクラスファイルにコンパイルする必要がありますが、lateinit、nullable、プロパティの委譲など、Kotlin言語特有の機能は純粋なバイトコードでは表現できないため、これらの情報はMeatdataアノテーションに書かれています。

  2. トップレベルのメソッドは、実際にはXXXKt.ktを生成し、その中にスタティック・メソッドを定義しています。

  3. Objectハングリーマン流シングルトン

  4. 拡張メソッド Kotlinでは、拡張関数のコードが存在するクラスに新しい静的メソッドを作成し、このメソッドの最初の引数を拡張クラスのオブジェクトにします。

    同じメソッドシグネチャを持つメソッドが拡張対象のクラスに既に存在する場合、Kotlinは拡張クラスで定義されたメソッドを優先します。

  5. インターフェイスのデフォルト・メソッド インターフェイスには、以下のメソッドを実装する静的な内部クラスがあります。

  6. デフォルトパラメータ Kotlinのデフォルトパラメータは、呼び出しのどの位置に値が割り当てられていないパラメータがあるかを記録する整数マスクを使用して実装されています。

  7. Kotlinの並行実装の原則は、キーワードを中断し、Kotlinの並行原則は、各ぶら下げ開始点と継続に対応する初期開始点は、ステートマシンの状態に変換されることです、並行切り替えは、ステートマシンは、別の状態に切り替えるだけで、CPSメカニズムの使用は、並行コンテキストを渡すために。

バイトコードの適用

JDK ダイナミックプロキシは、インターセプトされたメソッドを呼び出すためにリフレクションメカニズムを使用しますが、これは効率的ではありません。その原理は、プロキシされるクラスのメソッドにインデックスを追加し、そのインデックス値を通して特定のメソッドに直接アクセスできるようにすることです。

Cglib は、ASM を使用してターゲットプロキシクラスのサブクラスを生成し、サブクラスで親クラスのメソッドを拡張してプロキシの機能を実現します。

Read next

Caché コマンド・ブック ZWRITE コマンド

変数名とその値、または式の値を表示します。 変数名とその値および/または式の値を表示します。 オプション - 表示する変数または式、または表示する変数または式のカンマ区切りリスト。カンマ区切りのリストには、変数や値の任意の組み合わせを含めることができます。 Z...

May 20, 2020 · 16 min read