前言
我們?cè)?a href="http://www.itdecent.cn/p/10d1de3d9dde" target="_blank">JVM類(lèi)加載器-原理一文中了解了JVM類(lèi)加載器的基本原理?,F(xiàn)在我們一起通過(guò)ClassLoader類(lèi)及其相關(guān)源碼來(lái)詳細(xì)分析、理解JVM類(lèi)加載器的體系,深入理解JVM類(lèi)加載器的原理與實(shí)現(xiàn)。
ClassLoader的主要責(zé)任就是加載類(lèi)。ClassLoader通過(guò)一個(gè)類(lèi)的名字,定位并且把這個(gè)class文件加載進(jìn)JVM的內(nèi)存里,生成可以表示這個(gè)類(lèi)的結(jié)構(gòu)。ClassLoader是JDK為我們提供的一個(gè)基礎(chǔ)的類(lèi)加載器,它本身是一個(gè)抽象類(lèi),我們?cè)趯?shí)現(xiàn)自己特殊需求的類(lèi)加載器的時(shí)候,只需要根據(jù)我們自己的需要,覆寫(xiě)findClass方法(通過(guò)類(lèi)的全限定名查找該類(lèi)的class文件)。
ClassLoader的創(chuàng)建
ClassLoader的構(gòu)造函數(shù)是private的,所以不能直接new一個(gè)ClassLoader實(shí)例。而是在ClassLoader中提供了一些靜態(tài)方法,產(chǎn)生特定ClassLoader。如:
//該方法返回系統(tǒng)類(lèi)加載器,該類(lèi)加載器也是典型的用來(lái)啟動(dòng)應(yīng)用的類(lèi)加載器。
//該方法在運(yùn)行時(shí)的啟動(dòng)序列里被首次調(diào)用,在這個(gè)時(shí)間點(diǎn)上,該方法構(gòu)造的類(lèi)加載器被設(shè)置為調(diào)用線(xiàn)程的上下文類(lèi)加載器。
@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;
//判斷類(lèi)加載器是否支持并發(fā)
if (ParallelLoaders.isRegistered(this.getClass())) {
parallelLockMap = new ConcurrentHashMap<>();//并發(fā)情況下,每個(gè)線(xiàn)程通過(guò)同一個(gè)ClassLoader實(shí)例進(jìn)行類(lèi)的加載時(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;
}
}
- 類(lèi)加載的入口是從loadClass方法開(kāi)始的,而類(lèi)加載器的雙親委派模型也是在這里實(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:
* 該方法通過(guò)具體的類(lèi)的名字加載這個(gè)類(lèi)。默認(rèn)的實(shí)現(xiàn)按以下的順序搜尋這個(gè)類(lèi)
* <p><ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
* 調(diào)用findLoadedClass(String)方法檢查該類(lèi)是否已經(jīng)被加載過(guò)
* <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)用父類(lèi)加載器的loadClass(String)方法來(lái)加載這個(gè)類(lèi),如果父類(lèi)加載器為null則使用虛擬機(jī)內(nèi)置的類(lè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的子類(lèi)去覆寫(xiě)findClass方法而非loadClass方法
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
* 除非被覆寫(xiě),該方法會(huì)在類(lèi)加載的整個(gè)過(guò)程中持有g(shù)etClassLoadingLock方法返回的鎖
*/
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//類(lèi)加載期間持有一把鎖
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;
}
}
具體的邏輯分析如下:
- 在加載類(lèi)期間,調(diào)用線(xiàn)程會(huì)一直持有一把鎖。基于loadClass方法的邏輯,我們可以很容易理解為什么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;
}
- 檢查類(lèi)是否已經(jīng)加載。
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
//簡(jiǎn)單的檢查加載的類(lèi)的名字是否為空或是無(wú)效
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()) {
//類(lèi)名長(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)字典查找該類(lèi)的符號(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的常量池中查找該類(lèi)的符號(hào)引用是否存在。
- 接著的邏輯就是類(lèi)的雙親加載機(jī)制的實(shí)現(xiàn)。把類(lèi)加載的任務(wù)交給父類(lèi)加載器執(zhí)行,直到父類(lèi)加載器為空,此時(shí)會(huì)返回通過(guò)JDK提供的系統(tǒng)啟動(dòng)類(lèi)加載器加載的類(lèi)。
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)該類(lèi)沒(méi)有被加載過(guò),則由當(dāng)前類(lèi)加載器調(diào)用findClass方法來(lái)繼續(xù)加載該類(lèi)。
findClass
該方法和它的名字一樣,就是根據(jù)類(lèi)的名字ClassLoader不提供該方法的具體實(shí)現(xiàn),要求我們根據(jù)自己的需要來(lái)覆寫(xiě)該方法。
所以我們可以看一看URLClassLoader對(duì)findClass方法的實(shí)現(xiàn),類(lèi)加載的工作又被代理給了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)化為類(lèi)的實(shí)例。同時(shí)definClass方法為final的,故不可以覆寫(xiě)。
同時(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);
}
//為類(lèi)創(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é)文件中為該類(lèi)解析創(chuàng)建一個(gè)klassOop對(duì)象,表示Java類(lèi)
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è)類(lèi)記載器主要是在sun.misc.Launcher類(lèi)找那個(gè)定義。sun.misc.Launcher$AppClassLoader主要用于加載java.class.path目錄下的類(lèi),sun.misc.Launcher$ExtClassLoader主要用于加載<JAVA_HOME>\lib\ext或java.ext.dirs指定路徑下的類(lèi)庫(kù)。這些類(lèi)的具體繼承關(guān)系如下:

啟動(dòng)類(lèi)加載器是無(wú)法被Java程序直接引用,它是在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建的,需要它來(lái)加載類(lèi)時(shí),都是通過(guò)navtive方法findBootstrapClass來(lái)簡(jiǎn)潔使用的。
總結(jié)
類(lèi)加載器的雙親委派模型的實(shí)現(xiàn)邏輯十分簡(jiǎn)單,都在ClassLoader的loadClass方法中,我們?cè)趯?shí)現(xiàn)自己的類(lèi)加載器時(shí),最好遵守這種模型,由于在jvm中,一個(gè)類(lèi)類(lèi)型實(shí)例的唯一性是由加載他的類(lèi)加載器與這個(gè)類(lèi)的類(lèi)型共同確定的。這對(duì)保證Java程序的穩(wěn)定運(yùn)作十分重要。
參考引用
- JDK1.7源碼