類加載、鏈接和初始化 C++源碼解析

類加載器種類

  • BootstrapClassLoader:C++ 編寫,負責加載 java 核心類庫
  • Launcher.ExtClassLoader:Launcher 中的內部類,parent == null
  • Launcher.AppClassLoader:Launcher 中的內部類,parent == ExtClassLoader
  • 用戶自定義 ClassLoader:繼承自 ClassLoader,parent == AppClassLoader


    image.png

類加載機制

java 中默認的類加載機制是雙親委派模式。

ClassLoader 中關鍵的方法說明:

loadClass // 類加載入口,包含下面這些步驟

=> findLoadedClass => findLoadedClass0 // 先從緩存中查詢一下,看看目標類是否已加載過

=> findBootstrapClassOrNull => findBootstrapClass // 用Bootstrap類加載器進行加載

=> findClass // 讀取字節碼文件,然后加載字節碼文件

    => defineClass // 加載字節碼文件

        => preDefineClass // 加載前的檢查

        => defineClassSourceLocation // 定義類加載的路徑

        => defineClass1/defineClass2 // 調用native方法加載類

        => postDefineClass // 

=> resolveClass => resolveClass0

ClassLoader 部分源碼:

package java.lang;

import java.io.InputStream;
...

public abstract class ClassLoader {

    private final ClassLoader parent;

    // -- Class --
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);  // 緩存機制
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    // 雙親委派機制
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

    // findClass由子類去實現
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }

    // defineClass 加載類
    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        postDefineClass(c, protectionDomain);
        return c;
    }

    private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        // Note:  Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
        // relies on the fact that spoofing is impossible if a class has a name
        // of the form "java.*"
        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }

    private String defineClassSourceLocation(ProtectionDomain pd)
    {
        CodeSource cs = pd.getCodeSource();
        String source = null;
        if (cs != null && cs.getLocation() != null) {
            source = cs.getLocation().toString();
        }
        return source;
    }

    private void postDefineClass(Class<?> c, ProtectionDomain pd)
    {
        if (pd.getCodeSource() != null) {
            Certificate certs[] = pd.getCodeSource().getCertificates();
            if (certs != null)
                setSigners(c, certs);
        }
    }

    private native Class<?> defineClass0(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd);

    private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

    private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
                                         int off, int len, ProtectionDomain pd,
                                         String source);

    protected final void resolveClass(Class<?> c) {
        resolveClass0(c);
    }

    private native void resolveClass0(Class<?> c);

    private Class<?> findBootstrapClassOrNull(String name) {
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
    }

    // return null if not found
    private native Class<?> findBootstrapClass(String name);

    protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }

findLoadedClass0

JVM已經加載的類中查找目標類由本地方法findLoadedClass0實現,通過啟動類加載器加載類由本地方法findLoadedClass0實現,將已經加載完成的class解析并完成鏈接由本地方法findLoadedClass0實現,將讀取class文件的字節數組轉換成Class由defineClass0,defineClass1,defineClass2等方法完成,這四個本地方法的底層邏輯是什么樣的?神秘的啟動類加載器是如何加載rt.jar的?符號引用的解析是如何完成的?類的鏈接和初始化是怎樣實現的?

Symbol

Symbol類的定義位于oops/symbol.hpp中,Symbol表示一個規范化的字符串形式的描述符,如方法Object m(int i, double d, Thread t) {...}對應的方法描述符就是(IDLjava/lang/Thread;)Ljava/lang/Object,參考《Hotspot class文件和字節碼解析》。所有的Symbol實例通過保存在全局的SymbolTable中,SymbolTable即符號表,基于此實現符號引用計數功能。當有一個新的指針指向該Symbol實例,則引用計數加1,當該指針銷毀時需要將引用計數減1,當一個Symbol的引用計數為0,垃圾回收器就會從SymbolTable中刪除該Symbol并回收內存。

其類繼承關系如下圖:

image.png

SymbolBase定義了三個Symbol的基礎屬性:

  • _refcount:支持原子操作的short變量,表示該Symbol的引用計數
  • _length:UTF-8字符串的長度
  • _identity_hash:hash唯一標識碼

Symbol增加一個字節數組的屬性_body,用于存儲描述符對應的字符串,定義了可以操作該屬性的byte_at_put(int index, int value)方法,打印具體字符串內容的as_C_string(),as_utf8()方法,以及引用計數相關的refcount(),increment_refcount(),decrement_refcount()方法。

SymbolTable和StringTable

SymbolTable和StringTable類的定義位于classfile/symbolTable.hpp中,對應于C/C++編譯過程的符號表,用于保存管理所有的Symbol實例,StringTable就是Java特有的字符串常量池。其類繼承關系如下:

image.png

即SymbolTable和StringTable實際是一個支持自動擴容的HashMap。

SymbolTable定義了如下五個靜態屬性:

  • _the_table:SymbolTable指針,即全局實際保存Symbol實例的地方
  • _needs_rehashing:bool變量,是否需要重新hash
  • _symbols_removed:int變量,已經被移除的Symbol的數量
  • _symbols_counted:int變量,當前的Symbol的屬性
  • _arena:Arena類指針,表示從未被加載過的描述符

SymbolTable定義的方法都是靜態方法,主要有兩大類:

  • Symbol創建,釋放和查找相關的,如allocate_symbol,new_symbols,new_symbol,release,lookup,probe等方法

  • HashTable自身創建復制相關的,如create_table,copy_buckets,copy_table,rehash_table等。
    StringTable定義了三個靜態屬性,

  • _the_table:StringTable指針,全局實際保存字符串的地方

  • _needs_rehashing:bool變量,是否需要重新hash

  • _parallel_claimed_idx:volatile int變量,并發標記時使用
    StringTable也重寫了HashTable自身創建復制相關的方法,除此之外增加了用于往常量池添加字符串的intern方法,查找字符串的lookup方法。

以根據描述符字符串查找對應Symbol實例的lookup方法為例說明其邏輯:

Symbol* SymbolTable::lookup(const char* name, int len, TRAPS) {
  //計算hash值
  unsigned int hashValue = hash_symbol(name, len);
  //計算hash槽的位置
  int index = the_table()->hash_to_index(hashValue);
  //去指定位置的hash槽中查找是否存在目標Symbol
  Symbol* s = the_table()->lookup(index, name, len, hashValue);
  //如果存在則返回
  if (s != NULL) return s;
  //如果不存在則需要獲取鎖SymbolTable_lock,并創建一個新的
  MutexLocker ml(SymbolTable_lock, THREAD);
  //會調用allocate_symbol在內存中創建一個新的Symbol,然后以此構建一個新的HashtableEntry實例,將其添加到HashTable中
  return the_table()->basic_add(index, (u1*)name, len, hashValue, true, CHECK_NULL);
}

即符號表中的符號都只是原始字符串形式的描述符,并未涉及符號解析以及解析結果的保存。

ConstantPool

ConstantPool類的定義位于oops/constantPool.hpp文件中,用于表示class文件中的常量池,每個Klass都有對應的ConstantPool,兩者是一一對應的。常量池的數據大部分是在class文件解析的時候寫入的,可以安全訪問;但是CONSTANT_Class_info類型的常量數據是在這個類被解析時修改,這時只能通過解析狀態判斷這條數據是否修改完成。其類繼承關系如下:

image.png

常量池的每項數據都通過類CPSlot表示,其定義跟ConstantPool類位于同一個文件中,只有一個屬性,解析結果Klass或者Symbol的地址,如果未解析則地址是0,可以將該地址轉換成Klass或者Symbol類的指針,定義了對應的轉換方法(get_symbol和get_klass方法)和判斷該數據項是否已經解析的方法(is_resolved和is_unresolved方法)。

ConstantPool定義如下關鍵屬性:

  • _tags:單字節數組指針,描述常量池所有數據的類型的tag數組,每個tag用一個單字節表示

  • _cache:ConstantPoolCache類指針,保存解釋器運行時用到的動態調用相關信息的緩存

  • _pool_holder:InstanceKlass指針,當前常量池所屬的Klass實例

  • _operands:兩字節的數組指針,為大小可變的常量池數據項使用,通常為空

  • _resolved_references:jobject類型,實際是_jobject指針的別名,_jobject等同于C++層面的Java Object對象,表示已經解析的對象數組

  • _reference_map:兩字節的數組指針,表示已經解析的對象的索引到原始的常量池的索引的映射關系
    ConstantPool定義了讀取和解析常量池數據的諸多方法,大致有以下幾類:

  • 屬性讀寫的方法,如tags,set_tags,flags,set_flags,pool_holder,set_pool_holder,cache,set_cache等方法

  • 讀取基地址指定偏移位置的值的方法,如obj_at_addr_raw,long_at_addr,double_at_addr等方法

  • 向常量池指定位置寫入數據的方法,解析class文件時調用該類方法,如klass_at_put,unresolved_klass_at_put,method_handle_index_at_put,invoke_dynamic_at_put,int_at_put,field_at_put,name_and_type_at_put等方法

  • 從常量池指定位置讀取數據的方法,如klass_at,klass_name_at,resolved_klass_at,long_at,symbol_at,name_and_type_at,method_handle_name_ref_at等方法

  • 解析常量池符號引用的方法,如resolve_constant_at,resolve_bootstrap_specifier_at,resolve_constant_at_impl,resolve_string_constants_impl,resolve_bootstrap_specifier_at_impl等方法

  • 從class文件讀取常量池的字節流時校驗常量池是否符合規范的方法,如verify_on

至此答案已經明確,符號引用的解析,解析結果的緩存都由ConstantPool完成,以klass_at_impl(constantPoolHandle this_oop, int which, TRAPS)方法的實現為例說明,如下:

Klass* ConstantPool::klass_at_impl(constantPoolHandle this_oop, int which, TRAPS) {
  //獲取指定位置的數據項,判斷其是否已解析
  CPSlot entry = this_oop->slot_at(which);
  if (entry.is_resolved()) {
    //如果已解析但是不是Klass類型則拋出異常
    assert(entry.get_klass()->is_klass(), "must be");
    //返回解析結果
    return entry.get_klass();
  }
 
  //判斷調用方線程是否是Java線程,不能是本地的C++線程
  assert(THREAD->is_Java_thread(), "must be a Java thread");
  bool do_resolve = false;
  bool in_error = false;
 
  //用于創建目標類Class即InstanceMirrorKlass實例的Handle
  Handle mirror_handle;
 
  Symbol* name = NULL;
  Handle       loader;
  //獲取常量池的鎖
  {  MonitorLockerEx ml(this_oop->lock());
    //進一步校驗,如果未解析
    if (this_oop->tag_at(which).is_unresolved_klass()) {
       //如果是因為解析異常導致未解析,則將in_error置為true
      if (this_oop->tag_at(which).is_unresolved_klass_in_error()) {
        in_error = true;
      } else {
         //準備開始解析
        do_resolve = true;
         //獲取完整類名
        name   = this_oop->unresolved_klass_at(which);
        //根據當前常量池所屬的Klass實例獲取加載該Klass實例的類加載器
        loader = Handle(THREAD, this_oop->pool_holder()->class_loader());
      }
    }
  } // unlocking constantPool
 
 
  //解析失敗拋出異常
  if (in_error) {
    Symbol* error = SystemDictionary::find_resolution_error(this_oop, which);
    guarantee(error != (Symbol*)NULL, "tag mismatch with resolution error table");
    ResourceMark rm;
    // exception text will be the class name
    const char* className = this_oop->unresolved_klass_at(which)->as_C_string();
    THROW_MSG_0(error, className);
  }
 
  if (do_resolve) {
    // 執行resolve_or_fail時常量池必須處于非鎖定狀態
    oop protection_domain = this_oop->pool_holder()->protection_domain();
    Handle h_prot (THREAD, protection_domain);
    //通過SystemDictionary調用類加載器Oop解析目標類
    Klass* k_oop = SystemDictionary::resolve_or_fail(name, loader, h_prot, true, THREAD);
    KlassHandle k;
    if (!HAS_PENDING_EXCEPTION) {
 
      //如果解析成功
      k = KlassHandle(THREAD, k_oop);
      //初始化mirror_handle
      mirror_handle = Handle(THREAD, k_oop->java_mirror());
      //校驗訪問權限
      verify_constant_pool_resolve(this_oop, k, THREAD);
    }
 
    //解析失敗,必須記錄具體的失敗原因,這樣后面的解析都會以同樣的原因失敗
    if (HAS_PENDING_EXCEPTION) {
      ResourceMark rm;
      Symbol* error = PENDING_EXCEPTION->klass()->name();
 
      bool throw_orig_error = false;
      {
        MonitorLockerEx ml(this_oop->lock());
 
        //校驗是否因為并發導致解析失敗,即有一個線程解析成功
        if (this_oop->tag_at(which).is_klass()) {
          CLEAR_PENDING_EXCEPTION;
          entry = this_oop->resolved_klass_at(which);
          //返回解析成功的結果
          return entry.get_klass();
        }
 
        if (!PENDING_EXCEPTION->
              is_a(SystemDictionary::LinkageError_klass())) {
          //如果不是鏈接類錯誤則直接拋出異常不用記錄,如StackOverflow
        }
        else if (!this_oop->tag_at(which).is_unresolved_klass_in_error()) {
          //記錄解析失敗異常
          SystemDictionary::add_resolution_error(this_oop, which, error);
          this_oop->tag_at_put(which, JVM_CONSTANT_UnresolvedClassInError);
        } else {
          //如果是其他線程已經記錄了異常原因則獲取具體原因然后拋出異常
          error = SystemDictionary::find_resolution_error(this_oop, which);
          assert(error != NULL, "checking");
          throw_orig_error = true;
        }
      } // unlocked
      
      //拋出異常
      if (throw_orig_error) {
        CLEAR_PENDING_EXCEPTION;
        ResourceMark rm;
        const char* className = this_oop->unresolved_klass_at(which)->as_C_string();
        THROW_MSG_0(error, className);
      }
 
      return 0;
    }
    //如果需要根據類加載并且當前類不是數組類型的
    if (TraceClassResolution && !k()->oop_is_array()) {
      ResourceMark rm;
      int line_number = -1;
      const char * source_file = NULL;
      if (JavaThread::current()->has_last_Java_frame()) {
        // try to identify the method which called this function.
        vframeStream vfst(JavaThread::current());
        if (!vfst.at_end()) {
          //獲取觸發當前解析的代碼行號和源代碼文件名
          line_number = vfst.method()->line_number_from_bci(vfst.bci());
          Symbol* s = vfst.method()->method_holder()->source_file_name();
          if (s != NULL) {
            source_file = s->as_C_string();
          }
        }
      }
      //如果待加載類不是當前常量池所屬的類
      if (k() != this_oop->pool_holder()) {
        if (source_file != NULL) {
          tty->print("RESOLVE %s %s %s:%d\n",
                     this_oop->pool_holder()->external_name(),
                     InstanceKlass::cast(k())->external_name(), source_file, line_number);
        } else {
          tty->print("RESOLVE %s %s\n",
                     this_oop->pool_holder()->external_name(),
                     InstanceKlass::cast(k())->external_name());
        }
      }
      return k();
    } else {
      MonitorLockerEx ml(this_oop->lock());
      //獲取鎖進一步校驗常量池該項數據是否未解析
      do_resolve = this_oop->tag_at(which).is_unresolved_klass();
      if (do_resolve) {
        //如果未解析則將接解析結果存入常量池
        this_oop->klass_at_put(which, k());
      }
    }
  }
 
  entry = this_oop->resolved_klass_at(which);
  assert(entry.is_resolved() && entry.get_klass()->is_klass(), "must be resolved at this point");
  return entry.get_klass();
}

SystemDictionary

SystemDictionary類的定義在classfile/systemDictionary.hpp中,是一個系統字典類,用于保存所有已經加載完成的類,通過一個支持自動擴容的HashMap保存,key是表示類名Symbol指針和對應的類加載器oop指針,value是對應的Klass指針,當一個新的類加載完成后就會在SystemDictionary中添加一個新的鍵值對,其類繼承關系如下:


image.png

SystemDictionary定義的屬性和方法基本都是靜態的,其關鍵屬性如下:

  • _sdgeneration:int變量,保存已加載類的HashMap的容量
  • _dictionary:Dictionary類指針,實際保存已加載類的HashMap
  • _placeholders:PlaceholderTable類指針,當類加載的過程中臨時存儲鍵值對的地方,底層數據結構同Dictionary類
  • _shared_dictionary:Dictionary類指針,共享架構下用于保存已加載類的HashMap
  • _number_of_modifications: int變量,發生修改的次數,類加載或者刪除都會增加該計數器
  • _system_loader_lock_obj:oop指針,系統類加載器的對象鎖
  • _loader_constraints:LoaderConstraintTable類指針,保存類加載器加載約束的HashTable
  • _resolution_errors:ResolutionErrorTable類指針,保存類解析錯誤的HashTable
  • _invoke_method_table:SymbolPropertyTable類指針,保存MethodHandle調用的解析結果
  • _java_system_loader:oop指針,系統類加載器的引用

其定義 的方法主要有以下幾類:

  • 屬性操作的相關方法,如check_constraints,add_placeholder,add_klass,dictionary等
  • 根據類加載器和類名加載類的方法,如resolve_or_fail,resolve_or_null,resolve_super_or_fail
  • 根據class文件流,類加載器和類名加載類的方法,如parse_stream,resolve_from_stream
  • 根據類名和類加載器從已經加載的類中查找目標類,如find,find_instance_or_array_klass
  • 根據符號引用解析MethodHandle調用的方法,如find_method_handle_invoker,find_method_handle_type

綜上可知SystemDictionary相當于類加載的一個統一入口,同時提供查找已加載的類和加載新的類的服務,實現邏輯比較復雜,有興趣的可以參考源碼。

ClassLoader

ClassLoader類的定義在classfile/classLoader.hpp中,ClassLoader就是傳說中的用于加載Java核心類文件如rt.jar的啟動類加載器的實現,其類繼承關系如下:

image.png

ClassLoader定義的屬性大都是用于統計類加載性能的計數器,如_perf_class_parse_time,PerfCounter類指針,用于統計類解析的累計耗時,除此之外有以下幾個靜態屬性:

  • _first_entry:ClassPathEntry類指針,ClassPathEntry用于表示單個classpath路徑,所有的ClassPathEntry實例以鏈表的形式關聯起來,_first_entry表示鏈表的第一個實例
  • _last_entry:ClassPathEntry類指針,表示鏈表的最后一個實例
  • _num_entries:int變量,表示ClassPathEntry鏈表中ClassPathEntry實例的個數
  • _package_hash_table:PackageHashtable類指針,用于保存已經加載過的包名

ClassLoader定義的方法不多,大多是統計類加載性能相關的,除此之外有以下幾個方法比較重要:

  • 加載zip文件讀取寫入等操作的動態鏈接庫,load_zip_library方法
  • ClassPathEntry相關的,如setup_search_path,contains_entry,add_to_list,num_classpath_entries,classpath_entry,create_class_path_entry,update_class_path_entry_list等
  • 初始化的方法,initialize
  • 根據類名加載指定類文件的方法,load_classfile

其中initialize的調用鏈如下圖:

image.png

其實現邏輯比較簡單,關鍵代碼如下:

void ClassLoader::initialize() {
  assert(_package_hash_table == NULL, "should have been initialized by now.");
  EXCEPTION_MARK;
 
  if (UsePerfData) {
    //如果開始性能檢測則初始化各計數器
    NEWPERFTICKCOUNTER(_perf_accumulated_time, SUN_CLS, "time");
    NEWPERFTICKCOUNTER(_perf_class_init_time, SUN_CLS, "classInitTime");
  }
 
  //加載讀寫zip文件的動態鏈接庫
  load_zip_library();
  //設置加載核心jar包的搜索路徑,從系統參數Arguments中獲取
  setup_bootstrap_search_path();
  //如果是惰性啟動加載,即啟動時不加載rt.jar等文件
  if (LazyBootClassLoader) {
    //設置meta_index_path,設置完成后會觸發對meta_index_path下文件的解析
    setup_bootstrap_meta_index();
  }
}

load_classfile的調用鏈如下:


image.png

查看SystemDictionary的load_instance_class方法代碼可知當類加載器oop為空的時候會調用load_classfile方法,即當其他的Java類加載器無法加載特定類的時候將類加載請求委托給啟動類加載器加載,其關鍵代碼如下:

instanceKlassHandle SystemDictionary::load_instance_class(Symbol* class_name, Handle class_loader, TRAPS) {
  instanceKlassHandle nh = instanceKlassHandle(); // null Handle
  //如果class_loader為空,即Java類加載器無法加載該類了
  if (class_loader.is_null()) {
    instanceKlassHandle k;
    //目標類未加載
    if (k.is_null()) {
      //執行目標類的加載
      k = ClassLoader::load_classfile(class_name, CHECK_(nh));
    }
 
    //如果已經加載則查找加載的類
    if (!k.is_null()) {
      k = find_or_define_instance_class(class_name, class_loader, k, CHECK_(nh));
    }
    return k;
  } else {
    ResourceMark rm(THREAD);
 
    assert(THREAD->is_Java_thread(), "must be a JavaThread");
    JavaThread* jt = (JavaThread*) THREAD;
    
    //構建JNI調用的參數,即類名
    Handle s = java_lang_String::create_from_symbol(class_name, CHECK_(nh));
    Handle string = java_lang_String::externalize_classname(s, CHECK_(nh));
    //調用的結果
    JavaValue result(T_OBJECT);
    //結果的處理器
    KlassHandle spec_klass (THREAD, SystemDictionary::ClassLoader_klass());
    //調用Java的類加載器加載特定類
    if (MustCallLoadClassInternal && has_loadClassInternal()) {
      JavaCalls::call_special(&result,
                              class_loader,
                              spec_klass,
                              vmSymbols::loadClassInternal_name(),
                              vmSymbols::string_class_signature(),
                              string,
                              CHECK_(nh));
    } else {
      JavaCalls::call_virtual(&result,
                              class_loader,
                              spec_klass,
                              vmSymbols::loadClass_name(),
                              vmSymbols::string_class_signature(),
                              string,
                              CHECK_(nh));
    }
    //從加載結果中獲取目標類oop
    assert(result.get_type() == T_OBJECT, "just checking");
    oop obj = (oop) result.get_jobject();
 
    //檢查訪問權限
    if ((obj != NULL) && !(java_lang_Class::is_primitive(obj))) {
      instanceKlassHandle k =
                instanceKlassHandle(THREAD, java_lang_Class::as_Klass(obj));
      //檢查類名是否一致
      if (class_name == k->name()) {
        return k;
      }
    }
    //類加載失敗,返回空對象
    return nh;
  }
}

load_classfile方法的關鍵代碼如下:

instanceKlassHandle ClassLoader::load_classfile(Symbol* h_name, TRAPS) {
  ResourceMark rm(THREAD);
  //獲取類名
  const char* class_name = h_name->as_C_string();
  EventMark m("loading class %s", class_name);
  ThreadProfilerMark tpm(ThreadProfilerMark::classLoaderRegion);
 
  stringStream st;
  st.print_raw(h_name->as_utf8());
  st.print_raw(".class");
  //獲取文件名
  const char* file_name = st.as_string();
  ClassLoaderExt::Context context(class_name, file_name, THREAD);
 
  //ClassFileStream表示Class文件的字節流
  ClassFileStream* stream = NULL;
  int classpath_index = 0;
  ClassPathEntry* e = NULL;
  instanceKlassHandle h;
  {
    //從第一個ClassPathEntry開始遍歷所有的ClassPathEntry
    e = _first_entry;
    while (e != NULL) {
      stream = e->open_stream(file_name, CHECK_NULL);
      //如果檢查返回false則返回null,check方法默認返回true
      if (!context.check(stream, classpath_index)) {
        return h; // NULL
      }
      //如果找到目標文件則跳出循環
      if (stream != NULL) {
        break;
      }
      e = e->next();
      ++classpath_index;
    }
  }
  //如果找到了目標class文件
  if (stream != NULL) {
    //構建一個ClassFileParser實例
    ClassFileParser parser(stream);
    //構建一個ClassLoaderData實例
    ClassLoaderData* loader_data = ClassLoaderData::the_null_class_loader_data();
    Handle protection_domain;
    TempNewSymbol parsed_name = NULL;
    //解析并加載class文件,注意此時并未開始鏈接
    instanceKlassHandle result = parser.parseClassFile(h_name,
                                                       loader_data,
                                                       protection_domain,
                                                       parsed_name,
                                                       context.should_verify(classpath_index),
                                                       THREAD);
    //如果解析異常
    if (HAS_PENDING_EXCEPTION) {
      ResourceMark rm;
      if (DumpSharedSpaces) {
        //打印異常
        tty->print_cr("Preload Error: Failed to load %s", class_name);
      }
      return h;
    }
    //調用ClassLoader的add_package方法,把當前類的包名加入到_package_hash_table中
    h = context.record_result(classpath_index, e, result, THREAD);
  } else {
    //沒有找到目標文件
    if (DumpSharedSpaces) {
      tty->print_cr("Preload Warning: Cannot find %s", class_name);
    }
  }
 
  return h;
}
···
 再看另一個問題,啟動類加載器是什么時候開始加載rt.jar等核心jar包的,是一次性把jar包中所有類都加載進去么?答案是需要的時候,當啟動類執行main方法或者JVM自身啟動的過程中需要用到rt.jar包中的某個類如java.lang.Thread等就會觸發rt.jar的加載,加載特定需要的類。測試用例如下:
···
package jvmTest;
 
import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
 
 
public class MainTest3 {
 
    public static void main(String[] args) {
 
        while (true) {
            try {
                System.out.println(getProcessID());
                Thread.sleep(600 * 1000);
            } catch (Exception e) {
 
            }
        }
    }
 
    public static final int getProcessID() {
        RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
        System.out.println(runtimeMXBean.getName());
        return Integer.valueOf(runtimeMXBean.getName().split("@")[0])
                .intValue();
    }
 
}

執行main方法,啟動HSDB,然后查找DriverManager類,該類是JDBC的驅動管理類,位于rt.jar中,結果如下:


image.png

ClassLoader JNI接口實現

ClassLoader JNI接口的實現源碼在jdk/src/share/native/java/lang/ClassLoader.c中

findLoadedClass0方法

該方法實際調用JVM_FindLoadedClass方法,關鍵代碼如下:

//JVM_ENTRY是宏定義,用于處理JNI調用的預處理,如獲取當前線程的JavaThread指針
JVM_ENTRY(jclass, JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name))
  JVMWrapper("JVM_FindLoadedClass");
  //THREAD表示當前線程
  ResourceMark rm(THREAD);
 
  Handle h_name (THREAD, JNIHandles::resolve_non_null(name));
  //獲取類名對應的Handler
  Handle string = java_lang_String::internalize_classname(h_name, CHECK_NULL);
 
  //檢查是否為空
  const char* str   = java_lang_String::as_utf8_string(string());
  if (str == NULL) return NULL;
 
  //判斷類名是否超長
  const int str_len = (int)strlen(str);
  if (str_len > Symbol::max_length()) {
    return NULL;
  }
  //創建一個臨時的Symbol
  TempNewSymbol klass_name = SymbolTable::new_symbol(str, str_len, CHECK_NULL);
 
  //獲取類加載器對應的Handler
  Handle h_loader(THREAD, JNIHandles::resolve(loader));
  //查找目標類是否存在
  Klass* k = SystemDictionary::find_instance_or_array_klass(klass_name,
                                                              h_loader,
                                                              Handle(),
                                                          CHECK_NULL);
  //將Klass轉換成Java中的Class                                                         
  return (k == NULL) ? NULL :
            (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END

findBootstrapClass方法

該方法時委托啟動類加載器加載特定類,該方法在檢查類名合法后調用JVM_FindClassFromBootLoader完成加載,關鍵代碼如下:

JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,
                                              const char* name))
  JVMWrapper2("JVM_FindClassFromBootLoader %s", name);
 
  //檢查類名是否合法
  if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
    return NULL;
  }
 
  //調用SystemDictionary解析目標類,如果未找到返回null
  TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
  Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
  if (k == NULL) {
    return NULL;
  }
  //將Klass轉換成java中Class
  return (jclass) JNIHandles::make_local(env, k->java_mirror());
JVM_END

resolveClass0

該方法的本意是解析類,該方法實際并不會調用,只是兼容JDK1.1而保留了下來。該方法會調用JVM_ResolveClass完成解析,如下,OpenJDK只是提供了一個空實現。

image.png

defineClass0、defineClass1、defineClass2

defineClass0實際調用defineClass1的實現,defineClass1和defineClass2的區別就在于保存字節數據的數組是位于堆內存的普通數組還是位于元空間堆外內存的java.nio.ByteBuffer,兩者的處理邏輯基本一致,就是將對應數組的數據拷貝到C++的字節數組中,然后調用JVM_DefineClassWithSource方法,最終調用jvm_define_class_common方法,該方法的關鍵代碼如下:

static jclass jvm_define_class_common(JNIEnv *env, const char *name,
                                      jobject loader, const jbyte *buf,
                                      jsize len, jobject pd, const char *source,
                                      jboolean verify, TRAPS) {
  if (source == NULL)  source = "__JVM_DefineClass__";
 
  assert(THREAD->is_Java_thread(), "must be a JavaThread");
  JavaThread* jt = (JavaThread*) THREAD;
 
  // Since exceptions can be thrown, class initialization can take place
  // if name is NULL no check for class name in .class stream has to be made.
  TempNewSymbol class_name = NULL;
  if (name != NULL) {
    const int str_len = (int)strlen(name);
    //檢查類名的長度是否合法
    if (str_len > Symbol::max_length()) {
      THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name);
    }
    //創建一個新的Symbol實例
    class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL);
  }
 
  ResourceMark rm(THREAD);
  //根據字節數組和文件名構建ClassFileStream實例
  ClassFileStream st((u1*) buf, len, (char *)source);
  //構建Java類加載器實例對應的Hanlder
  Handle class_loader (THREAD, JNIHandles::resolve(loader));
  //構建ProtectionDomain對應的Hander
  Handle protection_domain (THREAD, JNIHandles::resolve(pd));
  //完成字節數組的解析并創建一個Klass實例
  Klass* k = SystemDictionary::resolve_from_stream(class_name, class_loader,
                                                     protection_domain, &st,
                                                     verify != 0,
                                                     CHECK_NULL);
  //將Klass實例轉換成Java 的Class
  return (jclass) JNIHandles::make_local(env, k->java_mirror());
}

類鏈接

鏈接包含驗證,準備和解析,其中符號引用的解析有ConstantPool完成,驗證則是由Verifier類完成,該類的定義在classfile/verifier.hpp中,該類比較簡單,核心方法就一個靜態方法verify,其調用關系如下:

image.png

從調用關系可知,類鏈接的入口就是InstanceKlass::link_class_impl方法了,其關鍵代碼如下:

bool InstanceKlass::link_class_impl(
    instanceKlassHandle this_oop, bool throw_verifyerror, TRAPS) {
  //如果類狀態異常
  if (this_oop->is_in_error_state()) {
    ResourceMark rm(THREAD);
    THROW_MSG_(vmSymbols::java_lang_NoClassDefFoundError(),
               this_oop->external_name(), false);
  }
  //如果類已鏈接則返回
  if (this_oop->is_linked()) {
    return true;
  }
 
 
  assert(THREAD->is_Java_thread(), "non-JavaThread in link_class_impl");
  JavaThread* jt = (JavaThread*)THREAD;
 
  instanceKlassHandle super(THREAD, this_oop->super());
  if (super.not_null()) {
    //如果父類是一個接口則拋出異常
    if (super->is_interface()) {  
      ResourceMark rm(THREAD);
      Exceptions::fthrow(
        THREAD_AND_LOCATION,
        vmSymbols::java_lang_IncompatibleClassChangeError(),
        "class %s has interface %s as super class",
        this_oop->external_name(),
        super->external_name()
      );
      return false;
    }
    //完成父類的鏈接
    link_class_impl(super, throw_verifyerror, CHECK_false);
  }
 
  //完成當前類實現的所有接口的鏈接
  Array<Klass*>* interfaces = this_oop->local_interfaces();
  int num_interfaces = interfaces->length();
  for (int index = 0; index < num_interfaces; index++) {
    HandleMark hm(THREAD);
    instanceKlassHandle ih(THREAD, interfaces->at(index));
    link_class_impl(ih, throw_verifyerror, CHECK_false);
  }
 
  //某些情況下鏈接父類的時候會把子類鏈接了,此時做檢查是否已鏈接
  if (this_oop->is_linked()) {
    return true;
  }
 
  
  {
    //初始化對象鎖
    oop init_lock = this_oop->init_lock();
    ObjectLocker ol(init_lock, THREAD, init_lock != NULL);
    // rewritten will have been set if loader constraint error found
    // on an earlier link attempt
    // don't verify or rewrite if already rewritten
 
    if (!this_oop->is_linked()) {
      if (!this_oop->is_rewritten()) {
        {
          //完成字節碼驗證
          bool verify_ok = verify_code(this_oop, throw_verifyerror, THREAD);
          if (!verify_ok) {
            return false;
          }
        }
 
        //在校驗是否驗證完成
        if (this_oop->is_linked()) {
          return true;
        }
 
        // also sets rewritten
        this_oop->rewrite_class(CHECK_false);
      } else if (this_oop()->is_shared()) {
        ResourceMark rm(THREAD);
        char* message_buffer; // res-allocated by check_verification_dependencies
        Handle loader = this_oop()->class_loader();
        Handle pd     = this_oop()->protection_domain();
        //依賴約束檢查
        bool verified = SystemDictionaryShared::check_verification_dependencies(this_oop(),
                        loader, pd, &message_buffer, THREAD);
        if (!verified) {
          THROW_MSG_(vmSymbols::java_lang_VerifyError(), message_buffer, false);
        }
      }
 
      //完成方法鏈接,即方法的入參和返回值的類型的鏈接
      this_oop->link_methods(CHECK_false);
      //初始化vtable和itable
      ClassLoaderData * loader_data = this_oop->class_loader_data();
      if (!(this_oop()->is_shared() &&
            loader_data->is_the_null_class_loader_data())) {
        ResourceMark rm(THREAD);
        this_oop->vtable()->initialize_vtable(true, CHECK_false);
        this_oop->itable()->initialize_itable(true, CHECK_false);
      }
      //設置類的狀態為鏈接完成
      this_oop->set_init_state(linked);
      if (JvmtiExport::should_post_class_prepare()) {
        Thread *thread = THREAD;
        assert(thread->is_Java_thread(), "thread->is_Java_thread()");
        //發布JVMTI事件
        JvmtiExport::post_class_prepare((JavaThread *) thread, this_oop());
      }
    }
  }
  return true;
}

類初始化

查看link_class_impl方法的調用鏈可知類初始化的入口是InstanceKlass::initialize_impl方法,如下圖:


image.png

該方法的關鍵代碼如下:

void InstanceKlass::initialize_impl(instanceKlassHandle this_oop, TRAPS) {
  //完成此類的鏈接,如果已鏈接則會立即返回
  this_oop->link_class(CHECK);
 
  DTRACE_CLASSINIT_PROBE(required, InstanceKlass::cast(this_oop()), -1);
 
  bool wait = false;
 
  // Step 1
  {
    //獲取對象鎖
    oop init_lock = this_oop->init_lock();
    ObjectLocker ol(init_lock, THREAD, init_lock != NULL);
 
    Thread *self = THREAD; // it's passed the current thread
 
    // Step 2
    //如果正在初始化則等待初始化完成
    while(this_oop->is_being_initialized() && !this_oop->is_reentrant_initialization(self)) {
        wait = true;
      ol.waitUninterruptibly(CHECK);
    }
 
    //等待超時返回
    if (this_oop->is_being_initialized() && this_oop->is_reentrant_initialization(self)) {
      DTRACE_CLASSINIT_PROBE_WAIT(recursive, InstanceKlass::cast(this_oop()), -1,wait);
      return;
    }
 
    //初始化完成返回
    if (this_oop->is_initialized()) {
      DTRACE_CLASSINIT_PROBE_WAIT(concurrent, InstanceKlass::cast(this_oop()), -1,wait);
      return;
    }
 
    //狀態異常,拋出異常
    if (this_oop->is_in_error_state()) {
      DTRACE_CLASSINIT_PROBE_WAIT(erroneous, InstanceKlass::cast(this_oop()), -1,wait);
      ResourceMark rm(THREAD);
      const char* desc = "Could not initialize class ";
      const char* className = this_oop->external_name();
      size_t msglen = strlen(desc) + strlen(className) + 1;
      char* message = NEW_RESOURCE_ARRAY(char, msglen);
      if (NULL == message) {
        // Out of memory: can't create detailed error message
        THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);
      } else {
        jio_snprintf(message, msglen, "%s%s", desc, className);
        THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);
      }
    }
 
    // 設置狀態,初始化進行中
    this_oop->set_init_state(being_initialized);
    this_oop->set_init_thread(self);
  }
 
  // Step 7
  //如果不是一個接口而是一個類則需要初始化它的父類
  if (!this_oop->is_interface()) {
     //獲取父類
    Klass* super_klass = this_oop->super();
    //初始化父類
    if (super_klass != NULL && super_klass->should_be_initialized()) {
      super_klass->initialize(THREAD);
    }
    //實現的接口存在默認方法則初始化接口
    if (!HAS_PENDING_EXCEPTION && this_oop->has_default_methods()) {
      this_oop->initialize_super_interfaces(this_oop, THREAD);
    }
 
    //初始化異常,拋出異常
    if (HAS_PENDING_EXCEPTION) {
      Handle e(THREAD, PENDING_EXCEPTION);
      CLEAR_PENDING_EXCEPTION;
      {
        EXCEPTION_MARK;
        this_oop->set_initialization_state_and_notify(initialization_error, THREAD);
        CLEAR_PENDING_EXCEPTION;
      }
      DTRACE_CLASSINIT_PROBE_WAIT(super__failed, InstanceKlass::cast(this_oop()), -1,wait);
      THROW_OOP(e());
    }
  }
 
  // Step 8
  {
    assert(THREAD->is_Java_thread(), "non-JavaThread in initialize_impl");
    JavaThread* jt = (JavaThread*)THREAD;
    DTRACE_CLASSINIT_PROBE_WAIT(clinit, InstanceKlass::cast(this_oop()), -1,wait);
    //執行靜態方法
    this_oop->call_class_initializer(THREAD);
  }
 
  // Step 9
  if (!HAS_PENDING_EXCEPTION) {
    //設置狀態初始化完成
    this_oop->set_initialization_state_and_notify(fully_initialized, CHECK);
    { ResourceMark rm(THREAD);
      debug_only(this_oop->vtable()->verify(tty, true);)
    }
  }
  else {
    //初始化失敗,拋出異常
    Handle e(THREAD, PENDING_EXCEPTION);
    CLEAR_PENDING_EXCEPTION;
    JvmtiExport::clear_detected_exception((JavaThread*)THREAD);
    {
      EXCEPTION_MARK;
      this_oop->set_initialization_state_and_notify(initialization_error, THREAD);
      CLEAR_PENDING_EXCEPTION;   // ignore any exception thrown, class initialization error is thrown below
      JvmtiExport::clear_detected_exception((JavaThread*)THREAD);
    }
    DTRACE_CLASSINIT_PROBE_WAIT(error, InstanceKlass::cast(this_oop()), -1,wait);
    if (e->is_a(SystemDictionary::Error_klass())) {
      THROW_OOP(e());
    } else {
      JavaCallArguments args(e);
      THROW_ARG(vmSymbols::java_lang_ExceptionInInitializerError(),
                vmSymbols::throwable_void_signature(),
                &args);
    }
  }
  DTRACE_CLASSINIT_PROBE_WAIT(end, InstanceKlass::cast(this_oop()), -1,wait);
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容