前言
我們?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)系如下:

啟動(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源碼