自言自语
说来惭愧 ,项目都写了好几个,Java底层稍深一点的内容、数据结构、算法,半知半解..
前言
本文试图在Java代码层面讲讲,Java类加载器是如何加载一个类1,以及其中的过程,希望对在看的你有所帮助,但更重要的是对我有帮助-.-
如果你发现有错误的地方,还请指正,以免误人误己,非常感谢。
一个.class字节码,在计算机上本质上就是一个文件,一份二进制流,如何把这份二进制流验证、解析,成为一个java.lang.Class的内存模型,可以被实例化的对象,类加载器(ClassLoader)的工作就是第一步;JVM内部默认有三个类加载器,来介绍一下。
Bootstrap ClassLoader (引导类加载器)
使用C/C++编写,属于JVM的一部分,随虚拟机启动而加载,不能在Java中实例化,它负责加载java的核心类库,默认加载%JAVA_HOME%/jre/lib/*目录下的jar包 2 (当然并不是什么包都加载,按照名称识别) 例如:rt.jar、jsse.jar、jce.jar
ExtClassLoader (扩展类加载器)
使用Java实现,默认它加载%JAVA_HOME%/jre/lib/ext/*目录下的所有类库 注意,是所有jar包,而且在这里放入jar包,优先覆盖掉项目引用同样的包。(看完本文你会明白)
AppClassLoader (应用类加载器)
又叫系统类加载器(SystemClassLoader),使用Java实现,默认它加载%CLASSPATH%目录下的类库,以及自身应用的所有类、类库。
注意,以上三个类加载器的加载路径均使用了默认字眼,可在JVM虚拟机的启动参数中指定它们的加载路径。
blablabla..说了这么多,何以证明?
来,打开eclipase新建test项目,代码走一波。刚好有mysql的驱动包,顺便引到项目上去,show package explorer 视图,点开JRE system library,各jar包的位置一目了然,看图
测试代码:
代码class.getClassLoader()
可以获得它的加载器
public class ClassLoaderTest {
public static void main(String[] args) {
//UserBean类,它属于应用自身的类。
System.out.println(UserBean.class.getClassLoader());
//mysql-connector-java-5.1.42-bin.jar 的com.mysql.fabric.FabricConnection类,它属于应用类库。
System.out.println( FabricConnection.class.getClassLoader());
//cldrdata.jar 下的 sun.text.resources.cldr.FormatData类, 它在%JAVA_HOME%/jre/lib/ext 目录下。
System.out.println( FormatData.class.getClassLoader());
//rt.jar 下的 java.lang.String类,它在%JAVA_HOME%/jre/lib/ 目录下。
System.out.println(String.class.getClassLoader());
}
}
class UserBean{
}
运行,不出意外,会类似打印出:
…AppClassLoader@hashcode
…AppClassLoader@hashcode
…ExtClassLoader@hashcode
…BootstrapClassLoader@hashcode
实际输出:
呃…实际输出 Bootstrap ClassLoader 为”null”,看上面关于它的介绍,使用纯C/C++编写的,它没有对应java.lang.Class 模型,所以无法被引用访问,实例化..
请正确理解这个“null”
相信好奇的同学都跟我一样有疑问
- 类加载器自己本身何时何地被加载?
- 它们之间又有什么关系?
- 会不会重复加载到类?如果不会,加载类的时候有什么机制? 如果会,为什么?
OK,有疑问就去探寻答案,一个一个来
上面输出图,有个$符号,说明这两个类加载器,是sun.misc.Latncher
的内部类,那就找这个Launcher源码看看呗。
然而很遗憾Oracle JDK不开源,并没有sun.misc.Latncher
的源码,但是可以参考Open JDK 8 Latncher的源码
了解一下sun.misc.Latncher
类的作用
看它的注释:
/**
* This class is used by the system to launch the main application.Launcher
* (系统通过使用此类来启动主应用程序) [渣英文=.=]
* */
public class Launcher {
....
说明它是系统启动时的一个入口类,JVM启动时会从这里开始运行代码,被引导类加载器加载,位于rt.jar。
继续研究 sun.misc.Latncher 源码,在Latncher 的构造方法可以看到:
...
67 public Launcher() {
68 // Create the extension class loader
69 ClassLoader extcl;
70 try {
71 extcl = ExtClassLoader.getExtClassLoader();
72 } catch (IOException e) {
73 throw new InternalError(
74 "Could not create extension class loader", e);
75 }
76
77 // Now create the class loader to use to launch the application
78 try {
79 loader = AppClassLoader.getAppClassLoader(extcl);
80 } catch (IOException e) {
81 throw new InternalError(
82 "Could not create application class loader", e);
83 }
84
...
}
AppClassLoader (应用类加载器)
关键第79行:loader = AppClassLoader.getAppClassLoader(extcl);
在 sun.misc.Latncher
中实现并通过给定扩展类加载器的引用创建;
AppClassLoader 的构造方法:
..掐头..
292 /*
293 * Creates a new AppClassLoader
294 */
295 AppClassLoader(URL[] urls, ClassLoader parent) {
296 super(urls, parent, factory);
..去尾..
( parent 即是extcl的参数传递名)
继承链是:
AppClassLoader 继承自 URLClassLoader继承自 SecureClassLoader继承自 ClassLoader
直捣黄龙:
ClassLoader
构造源码:
279 private ClassLoader(Void unused, ClassLoader parent) {
280 this.parent = parent;
AppClassLoader 有一个 parent 变量引用指向 ExtClassLoader
好,来研究 ExtClassLoader.
ExtClassLoader (扩展类加载器)
第71行: extcl = ExtClassLoader.getExtClassLoader();
同样在sun.misc.Latncher
中实现并创建,同样的继承链:
ExtClassLoader 继承自 URLClassLoader 继承自 SecureClassLoader 继承自 ClassLoader
ExtClassLoader 的构造方法:
160 /*
161 * Creates a new ExtClassLoader for the specified directories.
162 */
163 public More ...ExtClassLoader(File[] dirs) throws IOException {
164 super(getExtURLs(dirs), null, factory);
对应的 URLClassLoader构造方法:
185
186 public URLClassLoader(URL[] urls, ClassLoader parent,
187 URLStreamHandlerFactory factory) {
188 super(parent);
ExtClassLoader parent
为null
。
Bootstrap ClassLoader (引导类加载器):
正如前面关于它的介绍,随虚拟机启动而加载,纯C/C++编写,只要虚拟机成功启动它便已存在,它在Java层面是“null
”。
稍微有点想象力,应该可以想象出来。
应用类加载器 被 扩展类加载器 加载,而扩展类加载器被 引导类加载器 加载
上面的代码改一下可以证明:
//UserBean类,它属于应用自身创建类。
System.out.println(UserBean.class.getClassLoader());
System.out.println(UserBean.class.getClassLoader().getParent());
System.out.println(UserBean.class.getClassLoader().getParent().getParent());
输出:
是的,单纯从设计层面来讲,它们都可以独立、非依赖、组合,而是关联关系;应用类加载器 和 扩展类加载器,最终都继承自ClassLoader 抽象类
ClassLoader有个ClassLoader parent;
成员变量,AppClassLoader 的 parent
是 ; ExtClassLoader 的 parent
是 null
;
那么问题来了,ExtClassLoader 的 parent
是 null
;那它怎么被 Bootstrap ClassLoader加载?
直接看 ClassLoader这个抽象类的源码;
重点关注它加载类的loadClass();
方法,应用类加载器和扩展类加载器都没有覆盖该方法。
...
401 protected Class> loadClass(String name, boolean resolve)
402 throws ClassNotFoundException
403 {
404 synchronized (getClassLoadingLock(name)) {
405 // First, check if the class has already been loaded
406 Class> c = findLoadedClass(name);
407 if (c == null) {
408 long t0 = System.nanoTime();
409 try {
410 if (parent != null) {
411 c = parent.loadClass(name, false);
412 } else {
413 c = findBootstrapClassOrNull(name);
414 }
415 } catch (ClassNotFoundException e) {
416 // ClassNotFoundException thrown if class not found
417 // from the non-null parent class loader
418 }
419
420 if (c == null) {
421 // If still not found, then invoke findClass in order
422 // to find the class.
423 long t1 = System.nanoTime();
424 c = findClass(name);
425
426 // this is the defining class loader; record the stats
427 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
428 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
429 sun.misc.PerfCounter.getFindClasses().increment();
430 }
431 }
432 if (resolve) {
433 resolveClass(c);
434 }
435 return c;
436 }
437 }
...
关键410行:
如果 parent 不为null ,调用parent 的loadClass
否则,findBootstrapClassOrNull(name);
给引导类加载器加载;
如果引导类加载器都加载不了,则第424行调用 findClass
尝试加载。
而findClass 默认实现是抛出ClassNotFoundException
异常:
529 protected Class> findClass(String name) throws ClassNotFoundException {
530 throw new ClassNotFoundException(name);
531 }
到这里已经可以解答 ExtClassLoader 的 parent
是 null
的疑问,类加载器只要它的parent
是 null
,就会委托给引导类加载器加载。
有人认为基于这种双亲委托机制,是为了避免加载到跟java核心类同名的字节码(例如:java.lang.String
),从而损害虚拟机的安全,其实不然,ClassLoader 的loadClass();
方法并不是final 的,可以继承后覆盖它,破坏这种委托机制;
这种机制主要目的是避免重复加载,当需要加载一个Class 优先委托给 parent
加载,这样上层的ClassLoader 加载过,可以直接返回,遍历所有缓存;而不是自己直接去加载;尴尬造成虚拟机存在两份同样的Class,省去加载过程、内存损耗。
如果需要自定义自己的类加载,应该继承自ClassLoader给定它的 parent
是AppClassLoader覆盖 findClass(); 在里面定义自己获取Class的二进制,保持这种委托机制。
THE END