(点击
上方公众号
,可快速关注)
来源:笨狐狸,
blog.csdn.net/liweisnake/article/details/17842877
如有好文章投稿,请点击 → 这里了解详情
在开始Java的类加载旅程之前,可以先参考
这里
了解一些类加载器在Tomcat中的应用(http://blog.csdn.net/liweisnake/article/details/8470285)。
在最初执行java这个命令时,便会调用 ClassLoader 的 getSystemClassLoader 方法显式或者隐式加载 main 方法所在的类及其所引用的类。getSystemClassLoader 会返回 AppClassLoader,后者是 URLClassLoader 的一个子类。
先有鸡还是先有蛋?
所以,最初的一个问题是:
先有鸡还是先有蛋
?因为 ClassLoader 的整套体系是打包在 jre/lib/rt.jar 中的。只有 rt.jar 先被加载进来,才能够加载别的类;但是 rt.jar 又是被谁加载的呢?自然就是大名鼎鼎的 BootstrapClassLoader。它就是“鸡”。所以严格来讲,BootStrapClassLoader 并不是整个体系中的一部分(可以用 -Xbootclasspath 指定bootstrap 加载的位置)。
当 rt.jar 被加载进来后,ClassLoader 会调用 getSystemClassLoader,其中最重要的一步就是初始化 Launcher、ExtClassLoader 以及AppClassLoader,另外就是将 ContextClassLoader 设为 AppClassLoader。ExtClassLoader 与 AppClassLoader 都是 URLClassLoader 的子类,分别会加载 java.ext.dirs 和 java.class.path 路径下的 jar资源,前者一般指向 jre/lib/ext 下的所有jar,后者就是我们经常念叨的classpath。区分这两个 ClassLoader 的主要目的是,让他们形成层级关系,ExtClassLoader 为 AppClassLoader 的父 ClassLoader,有了层级关系,便可随意使用双亲委托模型了。
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader();
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader");
}
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl);
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
}
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
ClassLoader究竟干了什么?
接下来一个比较重要的问题是ClassLoader究竟干了什么?通常我们只知道它加载了一个类进了jvm,但是具体做了什么呢?
Java设计者把classloader加载一个类的过程分为4步:
-
第一步,从某个地方得到我们想要的字节码二进制流;
-
第二步,读入字节码流并转化为Class;
-
第三步,链接;
-
第四步,初始化。
其中,第二步一般比较固定,因此ClassLoader提供了defineClass来完成这步;
protected final Class> defineClass(String name, byte[] b, int off, int len,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
protectionDomain = preDefineClass(name, protectionDomain);
Class c = null;
String source = defineClassSourceLocation(protectionDomain);
try {
c = defineClass1(name, b, off, len, protectionDomain, source);
} catch (ClassFormatError cfe) {
c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
source);
}
postDefineClass(c, protectionDomain);
return c;
}
而 ClassLoader 提供了另一个方法 findClass 来完成第一步及第二步,即从某个地方读入类的二进制流,然后调用 defineClass 返回 Class
protected Class> findClass(final String name)
throws ClassNotFoundException
ClassLoader提供了resolveClass方法完成第三步链接的工作。
protected final void resolveClass(Class> c)
除非特殊需要,否则
尽量重载 findClass 而不是 loadClass。
loadClass 是 Java 1.0 就存在的类,为了增强可扩展性,将findClass和resolveClass封装到了loadClass中,一般我们只需要定义类的加载路径,因此仅需覆盖findClass。
通常我们显示加载类一般会用到ClassLoader.loadClass、Class.forName,他们的区别见这里。
resolveClass做了什么?
resolveClass最终调用了一个本地方法做link,这里的link主要做了这么几步事情:
-
验证Class以确保类装载器格式和行为正确;
-
准备后续步骤所需的数据结构;
-
解析所引用的其他类。
关于这些内容的具体细节,请参考这里。