ホームページ >Java >&#&チュートリアル >Java クラス ファイルに関する知識ポイントは何ですか?

Java クラス ファイルに関する知識ポイントは何ですか?

王林
王林転載
2023-05-05 12:22:061096ブラウズ

Class ファイル形式は、C 言語の構造に似た疑似構造を使用してデータを格納します。この疑似構造には、「符号なし数値」と「テーブル」の 2 つのデータ型のみがあります。

·符号なし数値は基本的なデータ型です。U1、u2、u4、および u8 は、それぞれ 1 バイト、2 バイト、4 バイト、および 8 バイトの符号なし数値を表します。符号なし数値は、数値を記述するために使用できます。インデックス参照、定量値、または UTF-8 でエンコードされた文字列値。

·テーブルとは、複数の符号なし数値や他のテーブルをデータ項目として構成された複合データ型であり、区別しやすくするために、すべてのテーブルの名前が "_info" で終わるのが一般的です。テーブルは、階層的な複合構造でデータを記述するために使用されます。クラス ファイル全体は、基本的にテーブルと見なすことができます。

Java クラス ファイルに関する知識ポイントは何ですか?

各クラス ファイルの最初の 4 ワード。セクションは、マジック ナンバーであり、その唯一の機能は、このファイルが仮想マシンによって受け入れられるクラス ファイルであるかどうかを判断することです。 Class ファイルに限らず、多くのファイル形式標準には、識別にマジック ナンバーを使用する習慣があります。たとえば、GIF や JPEG などの画像形式には、ファイル ヘッダーにマジック ナンバーがあります。

クラス ファイルのマジック ナンバーは非常に「ロマンチック」で、値は 0xCAFEBABE (コーヒー ベイビー?) です。

続くマジック ナンバーの 4 バイトには、クラス ファイルのバージョン番号が格納されます。クラスファイル:5バイト目と6バイト目はマイナーバージョン番号(MinorVersion)、7バイト目と8バイト目はメジャーバージョン番号(Major Version)です。 Java のバージョン番号は 45 から始まります。JDK 1.1 以降にリリースされた各 JDK メジャー バージョンのメイン バージョン番号は 1 ずつ増加します (JDK 1.0 ~ 1.1 はバージョン番号 45.0 ~ 45.3 を使用します)。JDK のより高いバージョンには、以前のバージョンとの下位互換性があります。 . バージョンのクラス ファイルを実行できますが、それ以降のバージョンのクラス ファイルを実行することはできません。

定数プール

メジャー バージョン番号とマイナー バージョン番号の直後は定数プールです。エントランス、コンスタントプール クラスファイル内のリソースウェアハウスと比較できます。クラスファイル構造内で他のプロジェクトに最も関連付けられているデータです。通常、クラスファイル内で最も大きなスペースを占めるデータ項目の 1 つです。 . また、クラスファイルに最初に現れるテーブル型のデータ項目です。

定数プールに格納される定数には、リテラルとシンボリック参照という 2 つの主なタイプがあります。リテラルは、テキスト文字列、final として宣言された定数値など、Java 言語レベルの定数の概念に近いものです。シンボル参照はコンパイル原則の概念に属し、主に次の種類の定数が含まれます。

·モジュールによってエクスポートまたは開かれたパッケージ

·クラスおよびインターフェイスの完全修飾名 (完全修飾名前)

·フィールド名と記述子 (Descriptor)

·メソッド名と記述子

·メソッド ハンドルとメソッド タイプ (メソッド ハンドル、メソッド タイプ、ダイナミック呼び出し)

·動的に計算される呼び出しサイトと動的定数 (動的に計算される呼び出しサイト、動的に計算される定数)

Java コードが Javac でコンパイルされる場合、C と C にはコンパイルがないのとは異なります。 「接続」ステップですが、仮想マシンがクラス ファイルをロードするときに動的に接続されます (詳細については、第 7 章を​​参照)。つまり、メモリ内の各メソッドとフィールドの最終的なレイアウト情報はクラス ファイルに保存されず、これらのフィールドとメソッドのシンボリック参照が実行時に仮想マシンによって変換されない場合、実メモリ エントリ アドレスを取得できません。つまり、仮想マシンから直接使用することはできません。仮想マシンがクラスをロードすると、対応するシンボル参照を定数プールから取得し、クラスの作成時または実行時にそれを解析して特定のメモリ アドレスに変換します。

定数プール内の各定数はテーブルです。当初、定数テーブルには異なる構造を持つ 11 種類のテーブル構造データがありました。その後、動的言語呼び出しをより適切にサポートするために、4 つの追加の動的型が追加されました。言語関連の定数 [1] Java モジュラーシステム (Jigsaw) をサポートするために、CONSTANT_Module_info と CONSTANT_Package_info の 2 つの定数が追加され、JDK13 の時点で定数テーブルには 17 種類の定数が存在します。

Java クラス ファイルに関する知識ポイントは何ですか?

ちなみに、Class ファイル内のメソッドやフィールドなどは、名前を記述するために CONSTANT_Utf8_info 定数を参照する必要があるため、CONSTANT_Utf8_info 定数の最大長はJava のメソッドであり、フィールド名の最大長です。ここでの最大長とは、length の最大値であり、u2 型が表現できる最大値 65535 です。したがって、Java プログラムで 64 KB の英語文字を超える変数名またはメソッド名を定義すると、ルールおよびすべての文字が正当であっても、そのプログラムはコンパイルされません。

クラスファイル /D:/BaiduYunDownload/geekbang-lessons/ Thinking-in-spring/validation/target/classes/org/geekbang/ Thinking/in/spring/validation/TestClass.class

最終更新日 2020 年 6 月 25 日; サイズ 439 バイト

MD5 チェックサム 18760ee8065f9fb68d4dab7bd7450c4c

「TestClass.java」からコンパイル済み

パブリック クラス org.geekbang. Thinking.in.spring .validation.TestClass

マイナー バージョン: 0

メジャー バージョン: 52

フラグ: ACC_PUBLIC、ACC_SUPER

定数プール:

#1 = Methodref #4.#18 // java/lang/Object."":()V

#2 = Fieldref #3.#19 // org/geekbang / Thinking/in/spring/validation/TestClass.m:I

#3 = クラス #20 // org/geekbang/ Thinking/in/spring/validation/TestClass

#4 =クラス #21 // java/lang/Object

#5 = Utf8 m

#6 = Utf8 I

#7 = Utf8

#8 = Utf8 ()V

#9 = Utf8 コード

#10 = Utf8 LineNumberTable

#11 = Utf8 LocalVariableTable

#12 = Utf8 this

#13 = Utf8 Lorg/geekbang/ Thinking/in/spring/validation/TestClass;

#14 = Utf8 inc

#15 = Utf8 () I

#16 = Utf8 SourceFile

#17 = Utf8 TestClass.java

#18 = NameAndType #7:#8 // "":( )V

#19 = NameAndType #5:#6 // m:I

#20 = Utf8 org/geekbang/ Thinking/in/spring/validation/TestClass

#21 = Utf8 java/lang/Object

{

public org.geekbang. Thinking.in.spring.validation.TestClass();

記述子: () V

flags: ACC_PUBLIC

Code:

stack=1、locals=1、args_size=1

0: aload_0

1: invokespecial #1 // メソッド java/lang/Object."":()V

4: return

LineNumberTable:

line 3: 0

LocalVariableTable:

開始 長さ スロット 名前 署名

0 5 0 この Lorg/geekbang/ Thinking/in/spring/validation/TestClass;

public int inc();

記述子: ()I

フラグ: ACC_PUBLIC

コード:

stack=2、locals=1、args_size= 1

0: aload_0

1:ゲットフィールドテーブル:#

##行7:0

localvariabletable:

start length slot name signature

# 0 7 0このlorg/geekbang / Thinking/in/ spring/validation/ TestClass;

}

SourceFile: "TestClass.java"

定数プールが終了した後、次の 2 バイトはアクセスを表しますフラグ (access_flags): このフラグは、このクラスがクラスであるかインターフェイスであるか、パブリック型として定義されているかどうか、抽象型として定義されているかどうかなど、クラスまたはインターフェイス レベルのアクセス情報を識別するために使用されます。はクラス (final として宣言されているかどうか);

クラス インデックス、親クラス インデックス、インターフェイス インデックス セットはすべて、アクセス フラグの後に順番に配置されます。クラス インデックスと親クラス インデックスは、2 つの u2 で表されます。これらはそれぞれ、CONSTANT_Class_info 型のクラス記述子定数を指します。type の定数のインデックス値は、CONSTANT_Utf8_info 型の定数で定義された完全修飾名文字列で見つけることができます。

JDK 8 で Lambda 式とインターフェイスのデフォルト メソッドが登場するまで、InvokeDynamic 命令は Java 言語によって生成された Class ファイルに存在していました。

したがって、JDK 8 の新しい命令はこれです。属性により、コンパイラは

(コンパイル時に -parameters パラメータを追加) してメソッド名をクラス ファイルに書き込むことができ、MethodParameters はメソッド テーブルの属性であり、Code 属性と同じです。フラットであり、実行時にリフレクション API を通じて取得できます。

·ローカル変数を操作スタックにロードします: iload

·オペランド スタックから値をローカル変数テーブルに保存します: istore

·定数をオペランドにロードしますスタック: bipush

iload_、命令 iload_0、iload_1、iload_2、および iload_3

·加算命令: iadd、ladd、fadd、dadd

·減算命令: isub、lsub、fsub、dsub

・乗算命令: imul、lmul、fmul、dmul

・除算命令: idiv、ldiv、fdiv、ddiv

・剰余命令:irem、lrem、frem、drem

・置換命令:ineg、lneg、fneg、dneg

・置換命令:ishl、ishr、iushr、lshl、lshr、lushr

・ビット単位の OR 命令: ior、lor

・ビット単位の AND 命令: iand、land

・ビット単位の XOR 命令: ixor、lxor

・ローカル変数 auto -increment命令: iinc

・比較命令: dcmpg、dcmpl、fcmpg、fcmpl、lcmp

invokespecial命令はJDK 1.0.2セマンティクスで変更され、JDK 7ではinvokedynamic命令が追加され禁止されています。 ret および jsr 命令。

クラスのライフサイクル

読み込み->接続(検証、準備、解析)->初期化->使用->アンインストール。

ロード、検証、準備、初期化、アンロードの 5 つの段階の順序が決定されています。タイプのロード プロセスはこの順序で段階的に開始する必要がありますが、解析段階は必ずしもそうである必要はありません。場合によっては、Java 言語のランタイム バインディング機能 (動的バインディングまたは遅延バインディングとも呼ばれる) をサポートするために、初期化フェーズの後に以下を開始できます。

public static Final int value = 123;

コンパイル時に、Javac は値の ConstantValue 属性を生成します。準備フェーズ中に、仮想マシンは、 ConstantValue の設定。

親委任モデルの動作プロセスは次のとおりです: クラス ローダーがクラス ロード リクエストを受信した場合、最初にクラス自体をロードしようとするのではなく、完了するためにリクエストを親クラス ローダーに委任します。これはクラス ローダーのすべてのレベルに当てはまります。したがって、すべてのロード リクエストは最終的に最上位の起動クラス ローダーに送信される必要があります。親ローダーがロード リクエストを完了できないというフィードバックを返した場合に限ります (そのようなロード リクエストが検索に存在しません)。スコープ) 必要なクラスが見つかると、サブローダーはそれ自体でロードを完了しようとします。

まず、拡張クラス ローダー (Extension Class Loader) がプラットフォーム クラス ローダー (Platform Class Loader) に置き換えられます。これは実際には非常に論理的な変更です。JDK 全体はモジュール性に基づいて構築されているため (元の rt.jar と tools.jar は数十の JMOD ファイルに分割されていました)、Java クラス ライブラリで当然十分です。スケーラブルなニーズには、 \lib\ext ディレクトリを保持する必要はありません。このディレクトリまたは java.ext.dirs システム変数を使用して JDK 関数を拡張する以前のメカニズムには、それ以上の価値はありません。これをロードするために使用されます。拡張クラス ローダー一部のクラス ライブラリもその歴史的使命を完了しました。

メソッド実行バージョンを決定するために静的型に依存するすべてのディスパッチ アクションは、静的ディスパッチと呼ばれます。静的ディスパッチの最も一般的な用途は、メソッドのオーバーロードです。静的ディスパッチはコンパイル フェーズ中に発生するため、静的ディスパッチを決定するアクションは実際には仮想マシンによって実行されません。これが、一部のマテリアルが「ディスパッチ」ではなく「解析」として分類することを選択する理由です。

Java 仮想マシンは、バイトコード命令を呼び出す次の 5 つのメソッド (

·invokestatic) をサポートします。静的メソッドを呼び出すために使用されます。

·invokespecial。インスタンス コンストラクターの () メソッド、プライベート メソッド、および親クラスのメソッドを呼び出すために使用されます。

·仮想を呼び出します。すべての仮想メソッドを呼び出すために使用されます。

·インターフェースを呼び出します。インターフェイス メソッドの呼び出しに使用され、インターフェイスを実装するオブジェクトは実行時に決定されます。

·ダイナミックを呼び出します。呼び出しサイト修飾子によって参照されるメソッドは、まず実行時に動的に解決され、その後メソッドが実行されます。最初の 4 つの呼び出し命令のディスパッチ ロジックは Java 仮想マシン内で固定化されますが、invokedynamic 命令のディスパッチ ロジックはユーザーが設定したブート メソッドによって決定されます。

メソッドが invokestatic および invokespecial 命令によって呼び出せる限り、解析段階で一意の呼び出しバージョンを決定できます。Java 言語でこの条件を満たすメソッドには、静的メソッド、プライベート メソッド、インスタンスなどがあります。 4 つのメソッドと、final によって変更されたメソッド (invokevirtual 命令を使用して呼び出されます) があり、これらの 5 つのメソッド呼び出しは、クラスの呼び出し時にシンボル参照

# をメソッドに直接解決します。ロードされています。これらの方式を総称して「非仮想方式」(Non-Virtual Method)と呼び、それ以外の方式を「仮想方式」(Virtual Method)と呼びます。

解析呼び出しは静的プロセスである必要があり、コンパイル中に完全に決定されます。クラス読み込みの解析フェーズ中に、関連するすべてのシンボル参照が明確な直接参照に変換され、遅延する必要はありません。実行時までです。もう 1 つの主なメソッド呼び出し形式: ディスパッチ (Dispatch) 呼び出しはさらに複雑で、静的または動的である可能性があり、ディスパッチに基づくケースの数に応じて、シングル ディスパッチとマルチ ディスパッチに分けることができます [1]。これら 2 種類のディスパッチ メソッドを組み合わせると、静的シングル ディスパッチ、静的マルチ ディスパッチ、動的シングル ディスパッチ、動的マルチ ディスパッチの 4 つのディスパッチの組み合わせが構成されます。仮想マシンでのメソッド ディスパッチがどのように行われるかを見てみましょう。

コードでは、同じ静的型で実際の型が異なる 2 つの変数を意図的に定義していますが、仮想マシン (正確にはコンパイラ) は、オーバーロード時にパラメータを使用します。

静的型実際のタイプは、

決定の基礎として使用されます。静的型はコンパイル時に既知であるため、コンパイル段階で、Javac コンパイラはパラメータの静的型に基づいてどのオーバーロードされたバージョンが使用されるかを決定します。そのため、呼び出しターゲットとして SayHello (Human) を選択し、シンボリック コンパイラを書き込みます。このメソッドの参照を main() メソッドの 2 つの invokevirtual 命令のパラメータに組み込みます。

静的型に依存してメソッドの実行バージョンを決定するすべてのディスパッチ アクションは、静的ディスパッチと呼ばれます。 静的ディスパッチの最も一般的なアプリケーションは、メソッドのオーバーロードです。静的ディスパッチはコンパイル フェーズ中に発生するため、静的ディスパッチを決定するアクションは実際には仮想マシンによって実行されません。これが、一部のマテリアルが「ディスパッチ」ではなく「解析」として分類することを選択する理由です。

可変長パラメータのオーバーロード優先度が最も低いことがわかります。フィールドはポリモーフィズムに関与することはなく、クラスのメソッドが特定の名前を持つフィールドにアクセスする場合、その名前はクラスが参照できるフィールドを参照します。

重要なポイント

invokevirtual 命令の実行の最初のステップは、実行時にレシーバーの実際のタイプを決定することであるため、invokevirtual 命令は定数プール内のメソッドのシンボリック参照を直接参照に解析し、メソッド レシーバーの実際の型に基づいてメソッド バージョンを選択することで終了します。このプロセスは、メソッド オーバーライドの本質です。 Java 言語。実行時の実際の型に基づいてメソッドの実行バージョンを決定するこのディスパッチ プロセスを、動的ディスパッチと呼びます。ポリモーフィズムの根本は、仮想メソッド呼び出し命令 invokevirtual の実行ロジックにあります。当然のことながら、フィールドではこの命令が使用されないため、私たちが導き出した結論はメソッドに対してのみ有効であり、フィールドに対しては有効ではありません。

Java 言語は、静的なマルチディスパッチ言語および動的なシングルディスパッチ言語です。

プログラム実装の便宜上、同じシグネチャを持つメソッドは、親クラスとサブクラスの仮想メソッド テーブルで同じインデックス番号を持つ必要があります。これにより、型が変更されたときに、変更された仮想メソッド テーブルのみが変更されます。ルックアップを変更する必要がある場合、必要なエントリ アドレスは、さまざまな仮想メソッド テーブルのインデックスによって変換できます。仮想メソッド テーブルは通常、クラスのロードの接続フェーズで初期化され、クラスの変数の初期値が準備された後、仮想マシンはクラスの仮想メソッド テーブルも初期化します。

動的型言語のサポート

Java 仮想マシンのバイトコード命令セットの数: Sun の最初の Java 仮想マシンの登場以来、20 年以上で新しい命令は 1 つだけあり、それは JDK 7 のリリースによるバイトコード命令セットです。コードの最初の新しいメンバーは、invokedynamic 命令です。この新たに追加された命令は、動的型付け言語 (Dynamically Typed Language) のサポートを実装するという JDK 7 のプロジェクト目標を達成するために行われた改善の 1 つであり、JDK 8 でラムダ式をスムーズに実装するための技術的な予備でもあります。

動的型付け言語[1]とは何ですか?動的型付け言語の主な特徴は、型チェックの主要なプロセスがコンパイル時ではなく実行時に実行されることです。この特徴を満たす言語は数多くあります。一般的に使用される言語には、APL、Clojure、Erlang、Groovy などがあります。 、JavaScript、Lisp、Lua、PHP、Prolog、Python、Ruby、Smalltalk、Tclなど。対照的に、C や Java など、コンパイル中に型チェックを実行する言語は、最も一般的に使用される静的型付け言語です。変数には型はありませんが、変数の値だけが型を持ちます

Java 仮想マシン レベルで動的型を直接サポートすることは、Java プラットフォームの開発のために解決しなければならない問題となっています。 JDK 7 の JSR-292 提案の命令と、java.lang.invoke パッケージの出現の技術的背景。

JDK 7 [1] で新しく追加された java.lang.invoke パッケージは、JSR 292 の重要な部分です。このパッケージの主な目的は、シンボル参照のみに依存して決定することです。ターゲット メソッドの呼び出しに加えて、ターゲット メソッドを動的に決定するための「メソッド ハンドル」と呼ばれる新しいメカニズムが提供されます。

·Reflection メカニズムと MethodHandle メカニズムは本質的にメソッド呼び出しをシミュレートしますが、Reflection は Java コード レベルでメソッド呼び出しをシミュレートし、MethodHandle はバイトコード レベルでメソッド呼び出しをシミュレートします。

Tomcat ディレクトリ構造では、3 つのディレクトリ グループ (/common/*、/server/*、および /shared/*) をセットアップできますが、これらはデフォルトで必ずしも開かれているわけではなく、/lib のみが開かれます。 /* ディレクトリが存在する場合があります) Java クラス ライブラリを格納するために使用され、Web アプリケーション自体の "/WEB-INF/*" ディレクトリに加えて、合計 4 つのグループが使用されます。 Java クラス ライブラリを次の 4 つのディレクトリ グループに配置します。各グループには次のような独立した意味があります。

·/common ディレクトリに配置します。クラス ライブラリは、Tomcat およびすべての Web アプリケーションで使用できます。

·/server ディレクトリに配置します。クラス ライブラリは Tomcat で使用できますが、すべての Web アプリケーションには表示されません。

·/shared ディレクトリに配置します。クラス ライブラリはすべての Web アプリケーションで使用できますが、Tomcat 自体には表示されません。

·/WebApp/WEB-INF ディレクトリに配置します。クラス ライブラリは Web アプリケーションでのみ使用でき、Tomcat や他の Web アプリケーションには表示されません。

このディレクトリ構造をサポートし、ディレクトリ内のクラス ライブラリをロードして分離するために、Tomcat は複数のクラス ローダーをカスタマイズしました。これらのクラス ローダーは、従来の親委任モデルに従って実装されています。

Java クラス ファイルに関する知識ポイントは何ですか?

共通クラス ローダー、Catalina クラス ローダー (サーバー クラス ローダーとも呼ばれます)、共有クラス ローダー、および Webapp クラス ローダーは Tomcat 独自のクラス ローダーであり、/common/*、/server/ に Java クラス ライブラリをロードします。それぞれ *、/shared/*、および /WebApp/WEB-INF/*。通常、WebApp クラスローダーと JSP クラスローダーのインスタンスは複数存在し、各 Web アプリケーションは WebApp クラスローダーに対応し、各 JSP ファイルは JasperLoader クラスローダーに対応します。

JasperLoader のロード スコープは、この JSP ファイルによってコンパイルされたクラス ファイルのみです。その存在の目的は破棄されることです。サーバーが JSP ファイルが変更されたことを検出すると、現在の JasperLoader が置き換えられます。インスタンスを作成し、JSP ファイルの HotSwap 機能を実装するための新しい JSP クラスローダを作成します。

以上がJava クラス ファイルに関する知識ポイントは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。