深入理解JVM(二)-类加载器双亲委托机制详解

一、双亲委托机制介绍

  1. 双亲委托机制,各个加载器按照父子的关系形成了树形机构,除了根类加载器之外,其余的类加载器有且只有一个父加载器。
  2. 类加载器的双亲委派加载机制(重点):当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的时候(在它的加载路径下没有找到所需加载的Class),子类加载器才会尝试自己去加载。
  3. 双亲委托机制是HotSpot虚拟机的默认类加载机制。(OSGi了解一下)
  4. 根类加载器、扩展类加载器、系统类加载器分别从指定的目录加载类:
    • 启动类加载器:负责加载类目录jre/lib/rt.jar里面的所有class,由C++实现,不属于ClassLoader的子类。
    • 扩展类加载器:负责加载java平台的扩展功能的jar包,包括jre/lib/*.jar或-Djava.ext.dirs指定目录的jar包。
    • 系统类加载器:负责加载classpath中指定jar包和classpath中的class。
  5. 如果由一个类加载器能加载Test类,那么这个类加载器叫定义类加载器,所有能成功返回Class对象引用的类加载器,包括定义类加载器,统称为初始类加载器。

二、 双亲委派模型实现

主要体现在ClassLoader的loadClass()方法中,思路很简单:先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父类加载器为空则默认使用启动类加载器作为父类加载器。如果父类加载器加载失败,抛出ClassNotFoundException异常后,调用自己的findClass()方法进行加载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/**
* Loads the class with the specified <a href="#name">binary name</a>.
* This method searches for classes in the same manner as the {@link
* #loadClass(String, boolean)} method. It is invoked by the Java virtual
* machine to resolve class references. Invoking this method is equivalent
* to invoking {@link #loadClass(String, boolean) <tt>loadClass(name,
* false)</tt>}.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class was not found
*/
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:
*
* <ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
*
* <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>
*
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li>
*
* </ol>
*
* <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>
*
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @param resolve
* If <tt>true</tt> then resolve the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
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;
}
}

二、代码实例

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.alyenc.jvm;
public class Test {
public static void main(String[] args) throws Exception {
Class<?> clazz1 = Class.forName("java.lang.String");
System.out.println(clazz1.getClassLoader()); //null,String位于rt.jar,由根类加载器加载,根类加载器不属于java类,返回null

Class<?> clazz2 = Class.forName("com.alyenc.jvm.NewTest");
System.out.println(clazz2.getClassLoader()); //sun.misc.Launcher$AppClassLoader@18b4aac2,AppClassLoader应用类加载器加载
}
}

class NewTest {
}
  • NewTest类是用户定义的类,位于CLASSPATH下,由系统/应用程序类加载器加载。
  • String类属于Java核心类,由启动类加载器加载,而启动类加载器是在JVM内部通过C/C++实现的,并不是Java,自然也就不能继承ClassLoader类,自然就不能输出其名称。
  • 把我们自定义的类打成jar包,拷贝入%JAVA_HOME%/jre/lib/ext目录下,再次运行Test类,此时类加载器为sun.misc.Launcher$Ext,ClassLoader,因为类的Jar包放到了ExtClassLoader的加载目录下,所以在根目录找不到相应类后,在ExtClassLoader处就完成了类加载,而忽略了APPClassLoader阶段。
坚持原创技术分享,您的支持将鼓励我继续创作!