Java虚拟机--类加载器源码

类加载器源码分析

下面,我们就来深入的学习下类加载器的源码,看看到底做了哪些事情?

类加载体系

上图呈现是源码级别的类加载体系,ClassLoader是基类,所有的类加载器都需要继承它(启动类加载器除外)。

首先,我们通过上文中的测试类来举例,一点点剖析类加载的流程。

创建一个包下普通的类:com.jiaboyan.test.ObjectTest

public class ObjectTest {
}

将此类编译,并打包处理:

jar cvf ObjectTest.jar com\jiaboyan\test\ObjectTest.class

将此jar包放入<Java_Runtime_Home>/lib/ext目录下;

编写测试用例:

public class JVMTest5 {
   public static void main(String[] agrs) throws ClassNotFoundException {
       Class clazz = Class.forName("com.jiaboyan.test.ObjectTest");
       System.out.println(clazz.getClassLoader());
   }
}

使用Class.forName来加载com.jiaboyan.test.ObjectTest类,看内部具体流程。

类加载流程

(1)进入到Class内部,可以看到实际调用了native修饰的forName0()方法。

public static Class<?> forName(String className) throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

private static native Class<?> forName0(String name, boolean initialize,
                                        ClassLoader loader,
                                        Class<?> caller) throws ClassNotFoundException;

(2)通过debug来看,在forName0()后又调用了AppClassLoader类的loadClass(String var1, boolean var2)方法。

public Class loadClass(String var1, boolean var2) throws ClassNotFoundException {
    int var3 = var1.lastIndexOf(46);
    if (var3 != -1) {
        SecurityManager var4 = System.getSecurityManager();
        if (var4 != null) {
            var4.checkPackageAccess(var1.substring(0, var3));
        }
    }
    return super.loadClass(var1, var2);
}

在方法的末尾,调用父类中的loadClass(String name, boolean resolve)方法,也就是ClassLoader类;

(3)通过debug来看,在forName0()后又调用了ClassLoader类的loadClass(String name, boolean resolve)方法。

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
//先从缓存查找该class对象,找到就不用重新加载
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) {
//抛出异常无需理会
}

            if (c == null) {
                long t1 = System.nanoTime();
                //如果都没有找到,则通过findClass去查找并加载
                c = findClass(name);
                .....
            }
        }
        //是否需要在加载时进行解析
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

loadClass源码所示:当类加载请求到来时,先从缓存中查找该类对象,如果存在则直接返回,如果不存在则交给该类加载器的父类加载器去加载,倘若没有父类加载器则交给顶级启动类加载器去加载,最后仍没有找到,则使用findClass()方法去加载。

实际流程是,类加载请求进来时,this为AppClassLoader对象,判断AppClassLoader对象的parent父类加载器是否为空。根据双亲委派模型得知,此时的parent一定为ExtClassLoader对象。调用ExtClassLoader的loadClass(String name, boolean resolve)方法。

通过源码得知,ExtClassLoader继承ClassLoader抽象类,并且没有重写loadClass(String name, boolean resolve)方法。那么,此时又进入到了上面的loadClass(String name, boolean resolve)中。不过,此时的this是ExtClassLoader对象。

ExtClassLoader对象的parent父类加载器为null,调用findBootstrapClassOrNull(String name)方法,使用顶层启动类加载器去加载com.jiaboyan.test.ObjectTest类。

由于顶层启动类加载器是C++实现,我们无法直接获取到其引用,最终调用到了native修饰的findBootstrapClass(String name)方法。

由于,我们将ObjectTest.jar放在了<Java_Runtime_Home>/lib/ext目录下,所以顶层启动类加载器加载不到com.jiaboyan.test.ObjectTest类,继而抛出异常,返回到ExtClassLoader对象的loadClass(String name, boolean resolve)方法中来。

(4)接下来,继续调用findClass(name)方法。

protected Class<?> findClass(final String name) throws ClassNotFoundException{
    try {
        return AccessController.doPrivileged(
            new PrivilegedExceptionAction<Class>() {
                public Class run() throws ClassNotFoundException {
                    String path = name.replace('.', '/').concat(".class");
                    Resource res = ucp.getResource(path, false);
                    if (res != null) {
                        try {
                            //生成class对象
                            return defineClass(name, res);
                        } catch (IOException e) {
                            throw new ClassNotFoundException(name, e);
                        }
                    } else {
                        throw new ClassNotFoundException(name);
                    }
                }
            }, acc);
    } catch (java.security.PrivilegedActionException pae) {
        throw (ClassNotFoundException) pae.getException();
    }
}

由于现在的this为ExtClassLoader对象,所以我们调用ExtClassLoader对象的findClass(final String name)方法。通过查看源码发现,ExtClassLoader并没有findClass方法,不过再其父类URLClassLoader中有实现(代码如上)。

(5)调用defineClass(String name, Resource res)方法。

private Class defineClass(String name, Resource res) throws IOException {
    long t0 = System.nanoTime();
    int i = name.lastIndexOf('.');
    URL url = res.getCodeSourceURL();
    if (i != -1) {
        String pkgname = name.substring(0, i);
        Manifest man = res.getManifest();
        if (getAndVerifyPackage(pkgname, man, url) == null) {
            try {
                if (man != null) {
                    definePackage(pkgname, man, url);
                } else {
                    definePackage(pkgname, null, null, null, null, null, null, null);
                }
            } catch (IllegalArgumentException iae) {
                if (getAndVerifyPackage(pkgname, man, url) == null) {
                    throw new AssertionError("Cannot find package " + pkgname);
                }
            }
        }
    }
    java.nio.ByteBuffer bb = res.getByteBuffer();
    if (bb != null) {
        //获取资源内的字节流数组:
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, bb, cs);
    } else {
        //获取资源内的字节流数组:
        byte[] b = res.getBytes();
        CodeSigner[] signers = res.getCodeSigners();
        CodeSource cs = new CodeSource(url, signers);
        sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
        return defineClass(name, b, 0, b.length, cs);
    }
}

defineClass(String name, Resource res)方法是用来将byte字节流解析成JVM能够识别的Class对象,通过这个方法不仅能够通过class文件实例化class对象,也可以通过其他方式实例化class对象,如通过网络接收一个类的字节码,然后转换为byte字节流创建对应的Class对象。

最后调用了defineClass(String name, java.nio.ByteBuffer b,CodeSource cs)方法,具体内部细节笔者不在详细陈序,需要说明的是:Class对象依旧使用了native修饰的方法来完成,内部细节无从得知。

private native Class defineClass2(String name, java.nio.ByteBuffer b,
                                  int off, int len, ProtectionDomain pd,
                                  String source);

(6)至此ExtClassLoader对象的加载阶段就此结束,Class对象返回。当返回到AppClassLoader对象中时,c并不为null,所以无需再次调用c = findClass(name)方法,整个类加载完成。

通过上述源码可知,当我们自己定义一个类加载器时候,无需重写loadClass()方法,直接重写自定义的findClass(String name)即可。父类加载器加载失败时候,最终都会走到findClass(String name)中来。

说完了类加载主体流程,接下来我们来研究一点细节的东西!!!!

此时,将文章拉回上面源码体系截图中,我们来看看SecureClassLoader、URLClassLoader类起到了哪些作用。

SercureClassLoader

SercureClassLoader继承ClassLoader,扩展了ClassLoader类的功能,增加对代码源和权限定义类的验证。

URLClassLoader

URLClassLoader继承SercureClassLoader,实现了ClassLoader中定义的方法,例如:findClass()、findResource()等。

在URLClassLoader中有一个成员变量ucp--URLClassPath对象,URLClassPath的功能是通过传入的路径信息获取要加载的字节码,字节码可以是在.class文件中、可以是在.jar包中,也可以是在网络中。

public URLClassLoader(URL[] urls) {
    super();
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkCreateClassLoader();
    }
    ucp = new URLClassPath(urls);
    this.acc = AccessController.getContext();
}

在URLClassPath构造中,需要传入URL[]数组,通过这个URL[]数组中所指定的位置信息,去加载对应的文件。在URLClassPath内部会根据传递的路径是文件地址、jar包地址还是网络地址来进行判断,来生成对应Loader。

public URLClassPath(URL[] var1, URLStreamHandlerFactory var2) {
    this.path = new ArrayList();
    this.urls = new Stack();
    this.loaders = new ArrayList();
    this.lmap = new HashMap();
    this.closed = false;

    for(int var3 = 0; var3 < var1.length; ++var3) {
        this.path.add(var1[var3]);
    }

    this.push(var1);
    if (var2 != null) {
        this.jarHandler = var2.createURLStreamHandler("jar");
    }

}

根据路径的不同,来生成不同的Loader:

private URLClassPath.Loader getLoader(final URL var1) throws IOException {
    try {
        return (URLClassPath.Loader)AccessController.doPrivileged(new PrivilegedExceptionAction<URLClassPath.Loader>() {
            public URLClassPath.Loader run() throws IOException {
                String var1x = var1.getFile();
                if (var1x != null && var1x.endsWith("/")) {
                    return (URLClassPath.Loader)("file".equals(var1.getProtocol()) ? new URLClassPath.FileLoader(var1) : new URLClassPath.Loader(var1));
                } else {
                    return new URLClassPath.JarLoader(var1, URLClassPath.this.jarHandler, URLClassPath.this.lmap);
                }
            }
        });
    } catch (PrivilegedActionException var3) {
        throw (IOException)var3.getException();
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容