JVMクラスのロードとクラスローダーの
クラスのロード
クラスのライフサイクルは、図に示すように、7つの段階で構成されています。
まず、
クラスのロード段階1.クラスのフルネームでクラスを格納するクラスファイルを取得します(取得方法)。
2.メソッド領域に格納されているランタイムデータ、つまりinstanceKlassインスタンスに解析します
3.ヒープ領域にこのクラスのクラスオブジェクト、つまりinstanceMirrorKlassインスタンスを生成します
いつロードするか
-積極的に使用する場合
1、new、getstatic、putstatic、invokestatic
2.リフレクション
3.クラスのサブクラスを初期化すると、その親クラスがロードされます
4.スタートアップクラス(main関数が配置されているクラス)
5. jdk1.7動的言語サポートを使用する場合、java.lang.invoke.MethodHandleインスタンスの最終分析結果にREF_getstatic、REF_putstatic、およびREF_invokeStaticメソッドハンドルがあり、このメソッドハンドルに対応するクラスが初期化されていない場合は、最初にその初期化をトリガーする必要があります
-プリロード:ラッパークラス、文字列、スレッド
次のような方法を取得する
1. jar、warなどの圧縮パッケージから読み取ります
2.Webアプレットなどのインターネットから取得します。
3.動的プロキシ、CGLIBなどの動的生成
4.JSPなどの他のファイルによって生成されます
5.データベースから読み取ります
6.暗号化されたファイルから読み取る
検証フェーズ
1.ファイル形式の検証
2.メタデータの検証
3.バイトコードの検証
4.シンボル参照の検証
準備段階
静的変数にメモリを割り当て、初期値を割り当てます。たとえば、int = 0; long = 0L;
オブジェクトの作成時にインスタンス変数が割り当てられると、初期値が割り当てられることは言うまでもありません。
変数がfinalによって変更された場合、constantValue属性はコンパイル時に属性に追加され、割り当ては準備フェーズ(コンパイル済み)で直接完了します。
public static int a = 20;
public static final int b = 30;
public static void main(String[] args) {
//不运行直接编译
}
解析段階
人気のポイントは、その間接参照->直接参照です
間接参照:実行時定数プールへの参照
直接参照:メモリアドレス
引き続きこのテストオブジェクトを使用します。コンパイルして実行した後の定数プール情報を見てみましょう。
public static int a = 20;
public static final int b = 30;
public static void main(String[] args) {
for (;;);//运行中
}
1.コンパイル後、classesディレクトリに切り替えて、javap-verboseまたはjclasslibを使用します。
たとえば、私のものはD:\ spring-boot \ boot \ target \ classes> javap -verbose com.waf.boot.wk.Testです。得られた結果は、定数プール
javap -verboseへの参照です。
D:\spring-boot\boot\target\classes>javap -verbose com.waf.boot.wk.Test
Classfile /D:/spring-boot/boot/target/classes/com/waf/boot/wk/Test.class
Last modified 2021-3-24; size 566 bytes
MD5 checksum d7655afea6f7cf4d8b5ab6005cd334ab
Compiled from "Test.java"
public class com.waf.boot.wk.Test
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #4.#26 // java/lang/Object."<init>":()V
#2 = Fieldref #3.#27 // com/waf/boot/wk/Test.a:I
#3 = Class #28 // com/waf/boot/wk/Test
#4 = Class #29 // java/lang/Object
#5 = Utf8 a
#6 = Utf8 I
#7 = Utf8 b
#8 = Utf8 ConstantValue
#9 = Integer 30
#10 = Utf8 <init>
#11 = Utf8 ()V
#12 = Utf8 Code
#13 = Utf8 LineNumberTable
#14 = Utf8 LocalVariableTable
#15 = Utf8 this
#16 = Utf8 Lcom/waf/boot/wk/Test;
#17 = Utf8 main
#18 = Utf8 ([Ljava/lang/String;)V
#19 = Utf8 args
#20 = Utf8 [Ljava/lang/String;
#21 = Utf8 StackMapTable
#22 = Utf8 MethodParameters
#23 = Utf8 <clinit>
#24 = Utf8 SourceFile
#25 = Utf8 Test.java
#26 = NameAndType #10:#11 // "<init>":()V
#27 = NameAndType #5:#6 // a:I
#28 = Utf8 com/waf/boot/wk/Test
#29 = Utf8 java/lang/Object
{
public static int a;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC
public static final int b;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 30
public com.waf.boot.wk.Test();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/waf/boot/wk/Test;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=0, locals=1, args_size=1
0: goto 0
LineNumberTable:
line 8: 0
LocalVariableTable:
Start Length Slot Name Signature
0 3 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 1
frame_type = 0 /* same */
MethodParameters:
Name Flags
args
static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: bipush 20
2: putstatic #2 // Field a:I
5: return
LineNumberTable:
line 5: 0
}
SourceFile: "Test.java"
主に定数プールを見てください:
#3 =クラス#28 // com /
waf / boot / wk / Testjclasslibは定数プールへの参照を取得します
2.実行後、実際のメモリアドレスはプロセス番号を通じてHSDBに表示されます。
初期化フェーズ
静的コードブロックを実行して、静的変数の割り当てを完了します
静的フィールド、静的コードセグメント、およびclinitメソッドは、バイトコードレベルで生成されます。
メソッド内のステートメントの順序は、コードが記述されている順序に関連しています。
静的のいくつかの例を見ることができます
public class Test_1 {
public static void main(String[] args) {
System.out.println(Test_1_B.str);
}
}
class Test_1_A{
static {
System.out.println("a static");
}
}
class Test_1_B extends Test_1_A{
public static String str = "a str";
static {
System.out.println("b static");
}
}
//output
a static
b static
a str
public class Test_1 {
public static void main(String[] args) {
System.out.println(Test_1_B.str);
}
}
class Test_1_A{
public static String str = "a str";
static {
System.out.println("a static");
}
}
class Test_1_B extends Test_1_A{
static {
System.out.println("b static");
}
}
//output
a static
a str
この静的フィールドはどのようにクラスに保存されますか-HSDBツールを使用して2番目の例のTest_1_AとTest_1_Bを見てください
instanceMirrorKlass(Oop)に格納され、Test_1_Bにはstr属性がありません(Java構文の継承関係のみが存在します)。最下層はどのようにしてそれを見つけましたか(わかりません...)
public class Test_1 {
public static void main(String[] args) {
System.out.println(new Test_1_B().str);
}
}
class Test_1_A{
public String str = "a str";
static {
System.out.println("a static");
}
}
class Test_1_B extends Test_1_A{
static {
System.out.println("b static");
}
}
//output
a static
b static
a str
JVMが静的プロパティを読み取る方法(例2)(どのようにして下部にそれを見つけましたか(理解できませんでした...))
strはクラスTest_1_Aの静的属性であり、サブクラスTest_1_Bのミラークラスに格納されないことがわかります。
ご想像のとおり、サブクラスTest_1_Bを介して親クラスTest_1_Aの静的フィールドにアクセスする方法は2つあります。
1.直接リターンがある場合は、最初にTest_1_Bのミラークラスに移動して取得します。そうでない場合、要求は継承チェーンに沿ってスローされます。明らかに、このアルゴリズムのパフォーマンスは継承チェーンの消滅とともに向上し、アルゴリズムの複雑さはO(n)です。
2.別のデータ構造を使用して、KV形式がストレージに使用され、クエリのパフォーマンスはO(1)です。
ホットスポットは、使用される2番目のメソッドです。別のデータ構造ConstantPoolCacheの助けを借りて、この構造を指す定数プールクラスConstantPoolに属性_cacheがあります。各データは、ConstantPoolCacheEntryクラスに対応しています。
ConstantPoolCacheEntryはどこにありますか?ConstantPoolCacheオブジェクトの背後にある、コードC ++を見てください。
\ openjdk \ hotspot \ src \ share \ vm \ oops \ cpCache.hpp
ConstantPoolCacheEntry * base()const { return(ConstantPoolCacheEntry *)((address)this + in_bytes(base_offset())); }
この式は、ConstantPoolCacheオブジェクトのアドレスとConstantPoolCacheオブジェクトのメモリサイズを足したものを意味します
ConstantPoolCache
定数プールキャッシュは、定数プール用に予約されているランタイムデータ構造です。すべてのフィールドアクセスと呼び出しバイトコードを格納するインタプリタランタイム情報。キャッシュは、クラスがアクティブに使用される前に作成および初期化されます。各キャッシュアイテムは、解析されるといっぱいになります
読み方
\ openjdk \ hotspot \ src \ share \ vm \ interpreter \ bytecodeInterpreter.cpp
CASE(_getstatic): { u2インデックス; ConstantPoolCacheEntry *キャッシュ; index = Bytes :: get_native_u2(pc + 1); // QQQこれを可能な限りインライン化する必要があります。おそらく //すべてのバイトコードケースを分割する必要があるので、c ++コンパイラは //定数propが可能な限りすべてを折りたたむ可能性があります。 キャッシュ= cp-> entry_at(index); if(!cache-> is_resolved((Bytecodes :: Code)opcode)){ CALL_VM(InterpreterRuntime :: resolve_get_put(THREAD、(Bytecodes :: Code)opcode)、 handle_exception); キャッシュ= cp-> entry_at(index); } ……
コードからわかるように、ConstantPoolCacheEntryを直接取得することです
クラスローダー