JVM類加載器-源碼分析

前言

我們?cè)?a href="http://www.itdecent.cn/p/10d1de3d9dde" target="_blank">JVM類加載器-原理一文中了解了JVM類加載器的基本原理?,F(xiàn)在我們一起通過ClassLoader類及其相關(guān)源碼來詳細(xì)分析、理解JVM類加載器的體系,深入理解JVM類加載器的原理與實(shí)現(xiàn)。

ClassLoader的主要責(zé)任就是加載類。ClassLoader通過一個(gè)類的名字,定位并且把這個(gè)class文件加載進(jìn)JVM的內(nèi)存里,生成可以表示這個(gè)類的結(jié)構(gòu)。ClassLoader是JDK為我們提供的一個(gè)基礎(chǔ)的類加載器,它本身是一個(gè)抽象類,我們?cè)趯?shí)現(xiàn)自己特殊需求的類加載器的時(shí)候,只需要根據(jù)我們自己的需要,覆寫findClass方法(通過類的全限定名查找該類的class文件)。

ClassLoader的創(chuàng)建

ClassLoader的構(gòu)造函數(shù)是private的,所以不能直接new一個(gè)ClassLoader實(shí)例。而是在ClassLoader中提供了一些靜態(tài)方法,產(chǎn)生特定ClassLoader。如:

//該方法返回系統(tǒng)類加載器,該類加載器也是典型的用來啟動(dòng)應(yīng)用的類加載器。
//該方法在運(yùn)行時(shí)的啟動(dòng)序列里被首次調(diào)用,在這個(gè)時(shí)間點(diǎn)上,該方法構(gòu)造的類加載器被設(shè)置為調(diào)用線程的上下文類加載器。
@CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }
private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }
private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
//判斷類加載器是否支持并發(fā)
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();//并發(fā)情況下,每個(gè)線程通過同一個(gè)ClassLoader實(shí)例進(jìn)行類的加載時(shí),都會(huì)獲得各自的鎖
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }
  • 類加載的入口是從loadClass方法開始的,而類加載器的雙親委派模型也是在這里實(shí)現(xiàn)的。源碼如下:
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    /**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     * 該方法通過具體的類的名字加載這個(gè)類。默認(rèn)的實(shí)現(xiàn)按以下的順序搜尋這個(gè)類
     * <p><ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     * 調(diào)用findLoadedClass(String)方法檢查該類是否已經(jīng)被加載過
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     * 調(diào)用父類加載器的loadClass(String)方法來加載這個(gè)類,如果父類加載器為null則使用虛擬機(jī)內(nèi)置的類加載器。
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     * 
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     * 官方建議繼承ClassLoader的子類去覆寫findClass方法而非loadClass方法
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     * 除非被覆寫,該方法會(huì)在類加載的整個(gè)過程中持有g(shù)etClassLoadingLock方法返回的鎖
     */
    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;
        }
    }

具體的邏輯分析如下:

  • 在加載類期間,調(diào)用線程會(huì)一直持有一把鎖?;趌oadClass方法的邏輯,我們可以很容易理解為什么loadClass方法的主要邏輯需要在一個(gè)同步塊里。后續(xù)的邏輯里有很多對(duì)ClassLoader共享變量的操作如parent的賦值等。
/**
     * Returns the lock object for class loading operations.
     * For backward compatibility, the default implementation of this method
     * behaves as follows. If this ClassLoader object is registered as
     * parallel capable, the method returns a dedicated object associated
     * with the specified class name. Otherwise, the method returns this
     * ClassLoader object. </p>
     * 為了向前兼容,該方法實(shí)現(xiàn)如下:如果ClassLoader對(duì)象被注冊(cè)為支持并發(fā),那么該方法會(huì)new一個(gè)Object作為鎖返回;否則,同步塊的鎖就是ClassLoader本身;
     * @since  1.7
     */
    protected Object getClassLoadingLock(String className) {
        Object lock = this;
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);//parallelLockMap是一個(gè)ConcurrentHashMap
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }
  • 檢查類是否已經(jīng)加載。
 protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }
//簡(jiǎn)單的檢查加載的類的名字是否為空或是無效
private boolean checkName(String name) {
        if ((name == null) || (name.length() == 0))
            return true;
        if ((name.indexOf('/') != -1)
            || (!VM.allowArraySyntax() && (name.charAt(0) == '[')))
            return false;
        return true;
    }
private native final Class findLoadedClass0(String name);

我們發(fā)現(xiàn)findLoadedClass0是一個(gè)native方法。查了下openjdk源碼如下:

JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_findLoadedClass0(JNIEnv *env, jobject loader,
                                           jstring name)
{
    if (name == NULL) {
        return 0;
    } else {
        return JVM_FindLoadedClass(env, loader, name);
    }
JVM_ENTRY(jclass, JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name))
  JVMWrapper("JVM_FindLoadedClass");
  ResourceMark rm(THREAD);

  Handle h_name (THREAD, JNIHandles::resolve_non_null(name));
  Handle string = java_lang_String::internalize_classname(h_name, CHECK_NULL);

  const char* str   = java_lang_String::as_utf8_string(string());
  // Sanity check, don't expect null
  if (str == NULL) return NULL;

  const int str_len = (int)strlen(str);
  if (str_len > Symbol::max_length()) {
    //類名長(zhǎng)度有限制
    // It's impossible to create this class;  the name cannot fit
    // into the constant pool.
    return NULL;
  }
  //
  TempNewSymbol klass_name = SymbolTable::new_symbol(str, str_len, CHECK_NULL);

  // Security Note:
  //   The Java level wrapper will perform the necessary security check allowing
  //   us to pass the NULL as the initiating class loader.
  Handle h_loader(THREAD, JNIHandles::resolve(loader));
  if (UsePerfData) {
    is_lock_held_by_thread(h_loader,
                           ClassLoader::sync_JVMFindLoadedClassLockFreeCounter(),
                           THREAD);
  }
  //全局符號(hào)字典查找該類的符號(hào)是否存在
  klassOop k = SystemDictionary::find_instance_or_array_klass(klass_name,
                                                              h_loader,
                                                              Handle(),
                                                              CHECK_NULL);

  return (k == NULL) ? NULL :
            (jclass) JNIHandles::make_local(env, Klass::cast(k)->java_mirror());
JVM_END
}

HotSpot虛擬機(jī)在永久代中增加了符號(hào)表。該表為哈希表用于將直接引用與運(yùn)行時(shí)常量池的符號(hào)引用作射。所以該native方法的實(shí)質(zhì)就是在jvm的常量池中查找該類的符號(hào)引用是否存在。

  • 接著的邏輯就是類的雙親加載機(jī)制的實(shí)現(xiàn)。把類加載的任務(wù)交給父類加載器執(zhí)行,直到父類加載器為空,此時(shí)會(huì)返回通過JDK提供的系統(tǒng)啟動(dòng)類加載器加載的類。
private Class findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
    }

    // return null if not found
    private native Class findBootstrapClass(String name);
  • 如果最后發(fā)現(xiàn)該類沒有被加載過,則由當(dāng)前類加載器調(diào)用findClass方法來繼續(xù)加載該類。

findClass

該方法和它的名字一樣,就是根據(jù)類的名字ClassLoader不提供該方法的具體實(shí)現(xiàn),要求我們根據(jù)自己的需要來覆寫該方法。
所以我們可以看一看URLClassLoader對(duì)findClass方法的實(shí)現(xiàn),類加載的工作又被代理給了defineClass方法:

protected Class<?> More ...findClass(final String name)
351         throws ClassNotFoundException
352    {
353        try {
354            return AccessController.doPrivileged(
355                new PrivilegedExceptionAction<Class>() {
356                    public Class More ...run() throws ClassNotFoundException {
357                        String path = name.replace('.', '/').concat(".class");
358                        Resource res = ucp.getResource(path, false);
359                        if (res != null) {
360                            try {
361                                return defineClass(name, res);
362                            } catch (IOException e) {
363                                throw new ClassNotFoundException(name, e);
364                            }
365                        } else {
366                            throw new ClassNotFoundException(name);
367                        }
368                    }
369                }, acc);
370        } catch (java.security.PrivilegedActionException pae) {
371            throw (ClassNotFoundException) pae.getException();
372        }
373    }

defineClass

defineClass方法主要是把字節(jié)數(shù)組轉(zhuǎn)化為類的實(shí)例。同時(shí)definClass方法為final的,故不可以覆寫。
同時(shí)defineClass也是一個(gè)native方法,具體也是由虛擬機(jī)實(shí)現(xiàn),源碼如下:

// common code for JVM_DefineClass() and JVM_DefineClassWithSource()
// and JVM_DefineClassWithSourceCond()
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;

  PerfClassTraceTime vmtimer(ClassLoader::perf_define_appclass_time(),
                             ClassLoader::perf_define_appclass_selftime(),
                             ClassLoader::perf_define_appclasses(),
                             jt->get_thread_stat()->perf_recursion_counts_addr(),
                             jt->get_thread_stat()->perf_timers_addr(),
                             PerfClassTraceTime::DEFINE_CLASS);

  if (UsePerfData) {
    ClassLoader::perf_app_classfile_bytes_read()->inc(len);
  }

  // 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()) {
      // It's impossible to create this class;  the name cannot fit
      // into the constant pool.
      THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name);
    }
    //為類創(chuàng)建符號(hào)
    class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL);
  }

  ResourceMark rm(THREAD);
  ClassFileStream st((u1*) buf, len, (char *)source);
  Handle class_loader (THREAD, JNIHandles::resolve(loader));
  if (UsePerfData) {
    is_lock_held_by_thread(class_loader,
                           ClassLoader::sync_JVMDefineClassLockFreeCounter(),
                           THREAD);
  }
  Handle protection_domain (THREAD, JNIHandles::resolve(pd));
  //從字節(jié)文件中為該類解析創(chuàng)建一個(gè)klassOop對(duì)象,表示Java類
  klassOop k = SystemDictionary::resolve_from_stream(class_name, class_loader,
                                                     protection_domain, &st,
                                                     verify != 0,
                                                     CHECK_NULL);

  if (TraceClassResolution && k != NULL) {
    trace_class_resolution(k);
  }

  return (jclass) JNIHandles::make_local(env, Klass::cast(k)->java_mirror());
}

AppClassLoader/ExtClassLoader/BootstrapClassLoader

系統(tǒng)為我們提供的幾個(gè)類記載器主要是在sun.misc.Launcher類找那個(gè)定義。sun.misc.Launcher$AppClassLoader主要用于加載java.class.path目錄下的類,sun.misc.Launcher$ExtClassLoader主要用于加載<JAVA_HOME>\lib\ext或java.ext.dirs指定路徑下的類庫。這些類的具體繼承關(guān)系如下:


classloaderext.png

啟動(dòng)類加載器是無法被Java程序直接引用,它是在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的,需要它來加載類時(shí),都是通過navtive方法findBootstrapClass來簡(jiǎn)潔使用的。

總結(jié)

類加載器的雙親委派模型的實(shí)現(xiàn)邏輯十分簡(jiǎn)單,都在ClassLoader的loadClass方法中,我們?cè)趯?shí)現(xiàn)自己的類加載器時(shí),最好遵守這種模型,由于在jvm中,一個(gè)類類型實(shí)例的唯一性是由加載他的類加載器與這個(gè)類的類型共同確定的。這對(duì)保證Java程序的穩(wěn)定運(yùn)作十分重要。

參考引用

  • JDK1.7源碼
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容