JVM源碼分析-類載入場景實例分析

来源:https://www.cnblogs.com/mushan/archive/2020/02/05/12266553.html
-Advertisement-
Play Games

A類調用B類的靜態方法,除了載入B類,但是B類的一個未被調用的方法間接使用到的C類卻也被載入了,這個有意思的場景來自一個提問: "方法中使用的類型為何在未調用時嘗試載入?" 。 場景如下: 添加JVM varbose參數進行執行,輸出是: main方法執行 ,而 方法裡面只有列印語句,所以理論上應該 ...


A類調用B類的靜態方法,除了載入B類,但是B類的一個未被調用的方法間接使用到的C類卻也被載入了,這個有意思的場景來自一個提問:方法中使用的類型為何在未調用時嘗試載入?

場景如下:

public class Main {
    static {
        System.out.println("Main static block");
    }

    public static void main(String[] args) {
        Helper.staticMethod();
    }
}

public class Helper {
    static {
        System.out.println("Helper static block");
    }

    public static void staticMethod() {
        System.out.println("Helper#staticMethod");
    }

    public void test(XXXManager ab, XXXSubInterface xxxSubInterface) {
        ab.setXXX(xxxSubInterface);
    }
}

public interface XXX {}

public interface XXXSubInterface extends XXX {}

public interface XXXManager {
    void setXXX(XXX xxx);
}

添加JVM -varbose參數進行執行,輸出是:

[Loaded Main from file:/Users/mazhibin/project/java/loadclasstest/target/classes/]
Main static block
[Loaded Helper from file:/Users/mazhibin/project/java/loadclasstest/target/classes/]
[Loaded XXX from file:/Users/mazhibin/project/java/loadclasstest/target/classes/]
Helper static block
Helper#staticMethod

main方法執行Helper.staticMethod(),而staticMethod方法裡面只有列印語句,所以理論上應該只要載入Helper就夠了,為了什麼會載入到XXX類,好,即使接受可以載入類的情況,為什麼是XXX,而不是直接使用到的XXXManager或者XXXSubInterface。你提的問題大概是這個場景。

在說探索過程之前先說下最終結論:在驗證Helper類時,校驗到setXXX方法,會驗證XXXSubInterface類型是否可以賦值到XXX類型,這個時候就會去載入XXX類,然後因為XXX是一個介面,代碼中認為介面和Object類是一樣的,什麼類型都可以賦值給介面類型,所以就直接校驗成功,就沒有去載入XXXSubInterface類了。

然後在介紹一下類載入的過程。首先要清楚一點,“類載入”和“載入”是兩個概念,“載入”是“類載入”(Class Loading)的一個步驟。類載入包含載入、鏈接、初始化這三個步驟,其中鏈接又分為驗證、準備、解析這三個子步驟。載入是根據特定名稱查找類或介面類型的二進位表示(Binary Representation),並由此二進位表示創建類或介面的過程。鏈接是為了讓類或介面可以被 Java 虛擬機執行,而將類或介面併入虛擬機運行時狀態的過程。類或介面的初始化是指執行類或介面的初始化方法<clinit>

file

類載入複雜就複雜在這些步驟執行的時機,並且其中的子步驟還不一定按順序執行,載入、驗證、準備、初始化和卸載這5個階段的順序是固定的,需要按這個順序開始(允許交叉),而解析則不一定,有可能在初始化之後才進行。

那什麼時候會開始載入步驟?Java虛擬機規範沒有強制要求,但是對於初始化階段,則明確規定了5種情況需要對類進行初始化,分別是:

  1. 在執行下列需要引用類或介面的Java虛擬機指令時:new,getstatic,putstatic或invokestatic。這些指令通過欄位或方法引用來直接或間接地引用其它類。執行上面所述的new指令,在類或介面沒有被初始化過時就初始化它。執行上面的getstatic,putstatic或invokestatic指令時,那些解析好的欄位或方法中的類或介面如果還沒有被初始化那就初始化它。
  2. 在初次調用java.lang.invoke.MethodHandle實例時,它的執行結果為通過Java虛擬機解析出類型是2(REF_getStatic)、4(REF_putStatic)或者6(REF_invokeStatic)的方法句柄(§5.4.3.5)。
  3. 在調用JDK核心類庫中的反射方法時,例如,Class類或java.lang.reflect包。
  4. 在對於類的某個子類的初始化時。
  5. 在它被選定為Java虛擬機啟動時的初始類(§5.2)時。

結合上面說的,載入、驗證、準備、初始化和卸載這5個階段的順序是固定的,需要按這個順序開始(允許交叉),我們確定了初始化的時機,那麼在初始化時或者之前,就要開始載入了。同時還有一點,也就是這個問題涉及到的場景,一個類在驗證這個步驟時,會驗證A類的位元組碼,其中可能會涉及到所以來的其他類,根據驗證的具體需求,可能需要載入其他類。而這個問題具體的校驗過程就是一個方法調用,涉及到類型轉換賦值(傳入子介面類型,需要轉為父介面類型),這種情況下需要載入類型來判斷是否可以進行賦值,按理是需要載入賦值左右兩邊的類型的,但是因為左邊類型是介面,被認為都可以賦值,所以沒有載入右邊類型。

總結來說可能的載入時機為以下幾點(不一定全面,是我目前已知的):

  1. JVM啟動時會預載入一些核心類,比如Object、String等
  2. 這個類被第一次真正使用到時,比如主類因為要調用其main方法,自然需要被載入
  3. 在A類校驗階段,可能需要載入其代碼中使用到的B類
  4. 在A類進行某個符號引用的解析時,需要載入對應的B類。比如正在執行A類的一個方法,執行到B.func(),那就需要解析B和func符號引用為直接引用,那自然需要載入B類到方法區中

接下來說下是如何得到上述結論的,首先類載入的流程是Java虛擬機規範中有寫的,可以看看。而具體為什麼只載入了XXX類,則要調試JVM源碼才能知道了。最近因為有看JVM源碼,所以編譯了並可以進行GDB調試,然後添加條件斷點:break SystemDictionary::load_instance_class if strncmp(class_name._body, "XXX", 3) == 0,表示在載入XXX類的時候停下來,接著分析調用堆棧:

// 載入Main類
[Loaded Main from file:/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/mzb/]

// 執行Main的初始化方法
Main static block

// 因為要執行Helper.staticMethod()語句,觸發載入Helper流程
[Loaded Helper from file:/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/mzb/]

// 接著斷點停在了載入XXX介面的函數調用上
Breakpoint 1, SystemDictionary::load_instance_class (class_name=0x7ffff01a5338, class_loader=..., __the_thread__=
    0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:1345
1345    instanceKlassHandle nh = instanceKlassHandle(); // null Handle

// 查看函數調用棧,分析為什麼會需要載入XXX類(要從下往上看)
(gdb) bt
#0  SystemDictionary::load_instance_class (class_name=0x7ffff01a5338, class_loader=...,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:1345
#1  0x00007ffff7578062 in SystemDictionary::resolve_instance_class_or_null (name=0x7ffff01a5338,
    class_loader=..., protection_domain=..., __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:755
#2  0x00007ffff7576a17 in SystemDictionary::resolve_or_null (class_name=0x7ffff01a5338, class_loader=...,
    protection_domain=..., __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:203
#3  0x00007ffff75765ad in SystemDictionary::resolve_or_fail (class_name=0x7ffff01a5338, class_loader=...,
    protection_domain=..., throw_error=true, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:145
// 上面就開始了載入流程了

// 下文分析了這裡是在校驗XXXSubInterface類型是否可以賦值到XXX
// 下文分析了為什麼需要載入XXX介面,而不需要載入XXXSubInterface介面
#4  0x00007ffff75df854 in VerificationType::is_reference_assignable_from (this=0x7ffff7fe5770, from=..., context=
    0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verificationType.cpp:62
#5  0x00007ffff753bc37 in VerificationType::is_assignable_from (this=0x7ffff7fe5770, from=...,
    context=0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verificationType.hpp:289
#6  0x00007ffff75eba80 in StackMapFrame::pop_stack (this=0x7ffff7fe5e20, type=..., __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/stackMapFrame.hpp:181
#7  0x00007ffff75ea155 in ClassVerifier::verify_invoke_instructions (this=0x7ffff7fe60e0, bcs=0x7ffff7fe5dc0,
    code_length=18, current_frame=0x7ffff7fe5e20, this_uninit=0x7ffff7fe5f1f, return_type=..., cp=...,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:2064

// 下文分析了這裡是因為驗證Helper.test(LXXXManager;)V這個方法導致的載入XXX介面
#8  0x00007ffff75e64ea in ClassVerifier::verify_method (this=0x7ffff7fe60e0, m=...,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:1237
#9  0x00007ffff75e0e75 in ClassVerifier::verify_class (this=0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:312
#10 0x00007ffff75e04b1 in Verifier::verify (klass=..., mode=Verifier::ThrowException, should_verify_class=true,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:127
#11 0x00007ffff71f5d8c in instanceKlass::verify_code (this_oop=..., throw_verifyerror=true,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:214
// 上面的方法是驗證的過程,也就是校驗位元組碼是否正確,是否合法

#12 0x00007ffff71f6425 in instanceKlass::link_class_impl (this_oop=..., throw_verifyerror=true,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:321
#13 0x00007ffff71f5e73 in instanceKlass::link_class (this=0xfb01ab80, __the_thread__=0x7ffff0028000)

// 在類或介面被初始化之前,它必須被鏈接過,也就是經過驗證、準備階段,且有可能已經被解析完成了。所以上面是鏈接的流程
    at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:230
#14 0x00007ffff71f691f in instanceKlass::initialize_impl (this_oop=..., __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:397
#15 0x00007ffff71f5cca in instanceKlass::initialize (this=0xfb01ab80, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/oops/instanceKlass.cpp:199
#16 0x00007ffff7383903 in LinkResolver::resolve_static_call (result=..., resolved_klass=...,
    method_name=0x7ffff01a4908, method_signature=0x7ffff0051f28, current_klass=..., check_access=true,
    initialize_class=true, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:629

// Main類在運行位元組碼時,執行到invokestatic指令,對應的語句是Helper.staticMethod()
// JVM規範中說明,在執行new,getstatic,putstatic或invokestatic這些指令時,需要確保目標類已經進行初始化流程
// 而初始化流程需要確保目標類已經被載入、驗證、準備,所以上面會走到Helper的載入、驗證、準備的流程
// 這個堆棧跟蹤到的是驗證的流程
#17 0x00007ffff738599f in LinkResolver::resolve_invokestatic (result=..., pool=..., index=65537,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1077
#18 0x00007ffff738575c in LinkResolver::resolve_invoke (result=..., recv=..., pool=..., index=65537,
    byte=Bytecodes::_invokestatic, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1050
#19 0x00007ffff7239c58 in InterpreterRuntime::resolve_invoke (thread=0x7ffff0028000,
    bytecode=Bytecodes::_invokestatic)
    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:686

// 我們到第16號棧幀中,可以看出的確是正要執行Helper.staticMethod()方法
(gdb) f 16
#16 0x00007ffff7383903 in LinkResolver::resolve_static_call (result=..., resolved_klass=...,
    method_name=0x7ffff01a4908, method_signature=0x7ffff0051f28, current_klass=..., check_access=true,
    initialize_class=true, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:629
629       resolved_klass->initialize(CHECK);
(gdb) p Klass::cast(current_klass.obj())->external_name()
$1 = 0x7fffcc002548 "Main"
(gdb) p *method_name._body@method_name._length
$5 = "staticMethod"

// 我們到第8號棧幀中,可以看出是因為驗證Helper.test(LXXXManager;)V這個方法導致的載入XXX介面
(gdb) f 8
#8  0x00007ffff75e64ea in ClassVerifier::verify_method (this=0x7ffff7fe60e0, m=...,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verifier.cpp:1237
1237                &this_uninit, return_type, cp, CHECK_VERIFY(this));
(gdb) p m->name_and_sig_as_C_string()
$6 = 0x7fffcc002568 "Helper.test(LXXXManager;)V"

// 我們到第4號棧幀中,可以看出是在校驗XXXSubInterface類型是否可以賦值到XXX
(gdb) f 4
#4  0x00007ffff75df854 in VerificationType::is_reference_assignable_from (this=0x7ffff7fe5770, from=...,
    context=0x7ffff7fe60e0, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/verificationType.cpp:62
62          Handle(THREAD, klass->protection_domain()), true, CHECK_false);
(gdb) p *from.name()[email protected]()._length
$10 = "XXXSubInterface"
(gdb) p *name()._body@name()._length
$11 = "XXX"

上面分析出了載入是因為驗證的流程,具體觸發載入的驗證代碼如下,是驗證賦值操作是否可以成功的:

// hotspot/src/share/vm/classfile/verificationType.cpp
bool VerificationType::is_reference_assignable_from(
    const VerificationType& from, ClassVerifier* context, TRAPS) const {
  instanceKlassHandle klass = context->current_class();
  if (from.is_null()) {
    // null is assignable to any reference
    return true;
  } else if (is_null()) {
    return false;
  } else if (name() == from.name()) {
    return true;
  } else if (is_object()) {
    // 如果賦值語句左邊類型是對象,判斷是否是Object,如果是那都可以賦值成功,返回true
    // We need check the class hierarchy to check assignability
    if (name() == vmSymbols::java_lang_Object()) {
      // any object or array is assignable to java.lang.Object
      return true;
    }

    // 否則需要把左邊類型載入進來 <=========================== 載入行為發生在這裡
    klassOop obj = SystemDictionary::resolve_or_fail(
        name(), Handle(THREAD, klass->class_loader()),
        Handle(THREAD, klass->protection_domain()), true, CHECK_false);
    KlassHandle this_class(THREAD, obj);

    // 如果左邊類型是介面
    if (this_class->is_interface()) {
      // 這裡註釋說明瞭,認為介面和Object一樣,都可以賦值成功所以返回true
      // We treat interfaces as java.lang.Object, including
      // java.lang.Cloneable and java.io.Serializable
      return true;
    } else if (from.is_object()) {
      // 否則要把賦值賦予右邊的類型也載入進來
      klassOop from_class = SystemDictionary::resolve_or_fail(
          from.name(), Handle(THREAD, klass->class_loader()),
          Handle(THREAD, klass->protection_domain()), true, CHECK_false);
      return instanceKlass::cast(from_class)->is_subclass_of(this_class());
    }
  } else if (is_array() && from.is_array()) {
    VerificationType comp_this = get_component(context, CHECK_false);
    VerificationType comp_from = from.get_component(context, CHECK_false);
    if (!comp_this.is_bogus() && !comp_from.is_bogus()) {
      return comp_this.is_assignable_from(comp_from, context, CHECK_false);
    }
  }
  return false;
}

這樣就分析完了,嘗試把XXX和XXXSubInterface改成class,可以發現兩個都會被載入,符合上面這個代碼的邏輯。

接著順便分析一下Helper類載入的堆棧:

// 載入Main類
[Loaded Main from file:/root/jvm/openjdk/build/linux-amd64-debug/hotspot/outputdir/linux_amd64_compiler2/jvmg/mzb/]

// 執行Main初始化方法
Main static block

// 斷點停在載入Helper類的邏輯上
Breakpoint 2, SystemDictionary::load_instance_class (class_name=0x7ffff01a60d8, class_loader=..., __the_thread__=
    0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:1345
1345      instanceKlassHandle nh = instanceKlassHandle(); // null Handle
(gdb) bt
#0  SystemDictionary::load_instance_class (class_name=0x7ffff01a60d8, class_loader=...,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:1345
#1  0x00007ffff7578062 in SystemDictionary::resolve_instance_class_or_null (name=0x7ffff01a60d8,
    class_loader=..., protection_domain=..., __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:755
#2  0x00007ffff7576a17 in SystemDictionary::resolve_or_null (class_name=0x7ffff01a60d8, class_loader=...,
    protection_domain=..., __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:203
#3  0x00007ffff75765ad in SystemDictionary::resolve_or_fail (class_name=0x7ffff01a60d8, class_loader=...,
    protection_domain=..., throw_error=true, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/classfile/systemDictionary.cpp:145
#4  0x00007ffff70c1915 in constantPoolOopDesc::klass_at_impl (this_oop=..., which=18,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/constantPoolOop.cpp:102
#5  0x00007ffff6fa1f69 in constantPoolOopDesc::klass_at (this=0xfb019e90, which=18,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/constantPoolOop.hpp:366
#6  0x00007ffff70c2c84 in constantPoolOopDesc::klass_ref_at (this=0xfb019e90, which=65537,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/oops/constantPoolOop.cpp:382
#7  0x00007ffff73817c0 in LinkResolver::resolve_klass (result=..., pool=..., index=65537,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:161
#8  0x00007ffff7385871 in LinkResolver::resolve_pool (resolved_klass=..., method_name=@0x7ffff7fe6638: 0x0,
    method_signature=@0x7ffff7fe6630: 0x0, current_klass=..., pool=..., index=65537,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1062

// Main類在運行位元組碼時,執行到invokestatic指令,對應的語句是Helper.staticMethod()
// JVM規範中說明,指令anewarray、checkcast、getfield、getstatic、instanceof、nvokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarray、new、putfield和putstatic將符號引用指向運行時常量池,執行上述任何一條指令都需要對它的符號引用的進行解析。
// 所以這裡需要將Main中的運行時常量池中的Helper和staticMethod進行符號解析
// 符號解析是把符號引用替換為真實引用,自然需要載入Helper類,才能進行替換,所以上面就觸發了Helper的載入流程
#9  0x00007ffff738595b in LinkResolver::resolve_invokestatic (result=..., pool=..., index=65537,
    __the_thread__=0x7ffff0028000) at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1076
#10 0x00007ffff738575c in LinkResolver::resolve_invoke (result=..., recv=..., pool=..., index=65537,
    byte=Bytecodes::_invokestatic, __the_thread__=0x7ffff0028000)
    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/linkResolver.cpp:1050
#11 0x00007ffff7239c58 in InterpreterRuntime::resolve_invoke (thread=0x7ffff0028000,
---Type <return> to continue, or q <return> to quit---
    bytecode=Bytecodes::_invokestatic)
    at /root/jvm/openjdk/hotspot/src/share/vm/interpreter/interpreterRuntime.cpp:686
// hotspot/src/share/vm/interpreter/linkResolver.cpp
void LinkResolver::resolve_invokestatic(CallInfo& result, constantPoolHandle pool, int index, TRAPS) {
  KlassHandle  resolved_klass;
  Symbol* method_name = NULL;
  Symbol* method_signature = NULL;
  KlassHandle  current_klass;
  // 解析常量池中的符號引用,會觸發載入被調用類的流程 <==================
  resolve_pool(resolved_klass, method_name,  method_signature, current_klass, pool, index, CHECK);
  // 解析從方法簽名解析出方法oop,會觸發類的初始化流程 <==================
  resolve_static_call(result, resolved_klass, method_name, method_signature, current_klass, true, true, CHECK);
}

總結

總結來說可能的載入時機為以下幾點(不一定全面,是我目前已知的):

  1. JVM啟動時會預載入一些核心類,比如Object、String等
  2. 這個類被第一次真正使用到時,比如主類因為要調用其main方法,自然需要被載入
  3. 在A類校驗階段,可能需要載入其代碼中使用到的B類
  4. 在A類進行某個符號引用的解析時,需要載入對應的B類。比如正在執行A類的一個方法,執行到B.func(),那就需要解析B和func符號引用為直接引用,那自然需要載入B類到方法區中

對應A類調用B類的情況,JVM規範中說明,指令anewarray、checkcast、getfield、getstatic、instanceof、nvokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarray、new、putfield和putstatic將符號引用指向運行時常量池,執行上述任何一條指令都需要對它的符號引用的進行解析,所以需要解析A類中對B類的符號引用,而解析是把符號引用替換為真實引用,所以需要把B類載入到方法區中,這就觸發了載入流程。

而B類的鏈接流程,則是因為JVM規範中說明,在執行new,getstatic,putstatic或invokestatic這些指令時,需要確保目標類已經進行初始化流程,而初始化流程需要確保目標類已經被載入、驗證、準備,而載入之前執行過了,所以需要進入驗證和準備的流程。而鏈接中的解析過程不會執行,B類的解析會在執行B類中相關代碼時再進行。

上面說的兩個過程都是在執行位元組碼時觸發的,比如invokestaic。而B類在驗證的過程中,可能又會需要載入其代碼中使用到的C類。

參考資料:When is a Java Class loaded? - Stack Overflow

本文獨立博客地址:JVM源碼分析-類載入場景實例分析 | 木杉的博客


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 問題1:Android error “Could not get BatchedBridge, make sure your bundle is packaged properly” on start of app react-native run-android react-native star ...
  • 運用在React 中 setState的對象、數組的操作時是不能用類似array.push()等方法,因為push沒有返回值,setState後會出現state變成Number,為了方便他人和自己查看,因此寫個數組和對象的操作方法小筆記。 1、修改object中某項 this.setState({ ...
  • 1.最後生成的效果是這樣的: 2.第一個對話框如下(包含了樣圖),用於輸入維度個數【最小為4,最大不限】: 3.第二個對話框如下,根據維度個數生成了信息錄入表【每個維度3個信息,每3個一次重覆,不清楚意義清查看第一步】 4.第三個對話框如下,該錄入名稱最終展示在圖形中央上部,以及命名為圖片的文件名 ...
  • 1. 切勿混用帶符號類型和無符號類型 如果表達式里既有帶符號類型又有無符號類型,帶符號類型會自動轉換為無符號類型。 2. 列表初始化 列表初始化當用於內置類型時,如果存在精度丟失編譯器將報錯。 3. 變數聲明和定義的關係 4. C++操作符代替名 5. const限定符 6. 類型別名 7. dec ...
  • 親愛的兒子: 當你打開這封信的時候,我已經離開波士頓回到加州了,你也已經結束自己最後一個暑假,去往自己非常喜歡的公司。 雖然你我都是程式員,但是你卻很少向我咨詢過技術相關的問題,咱們上一次一起寫代碼,也是你高考結束那個暑假了。不過前幾天你問了我一個問題,你說,如果讓你寫一封信,跟當初剛剛成為程式員的 ...
  • That syntax is called an indexed part-select. The first term is the bit offset and the second term is the width. It allows you to specify a variable f ...
  • 官方文檔 猛戳這裡 在settings中配置以下代碼 #LOGGING_DIR 日誌文件存放目錄 LOGGING_DIR = "logs" # 日誌存放路徑 if not os.path.exists(LOGGING_DIR): os.mkdir(LOGGING_DIR) import loggin ...
  • 伴隨著移動互聯網的飛速發展,越來越多用戶被互聯網連接在一起,用戶所積累下來的數據越來越多,市場對數據方面人才的需求也越來越大,由此也帶火瞭如數據分析、數據挖掘、演算法等職業,而作為其中入門門檻相對較低、工資高於大多傳統行業崗位的數據分析一職,則成為了許多想轉行進入數據領域的同學的首要選擇。 那麼在現在 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...