ClassLoader深入学习记录

ClassLoader是什么

  • ClassLoader是Java的类加载机制
    ClassLoader用于动态加载class文件到内存中

Java程序写好后,是由若干个class文件组织而成的一个完整的Java应用程序;程序运行时,会调用改程序的一个入口函数来调用系统的相关功能;这些功能被封装在不同的class文件中;程序启动时,不会一次性加载程序所需要的所有class文件,而是根据需要,通过ClassLoader来动态加载某个class文件到内存中;当class文件被载入到内存后,才能被其他class所引用;

Java提供的ClassLoader

  • Java提供的ClassLoader有三个
    BootStrap ClassLoader(启动类加载器):是Java类加载层最顶层的类加载器,由C++编写而成, 已经内嵌到JVM中了;主要用来加载JDK的核心类库,如:rt.jar、resources.jar、charsets.jar等;
    eg:利用以下代码获得BootStrap ClassLoader加载的jar或class文件
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();  
for (int i = 0; i < urls.length; i++) {  
    System.out.println(urls[i].toExternalForm());  
}  
System.out.println(System.getProperty("sun.boot.class.path"));
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/resources.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/rt.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jsse.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jce.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/charsets.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jfr.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/classes

Extension ClassLoader(扩展类加载器):负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的所有jar;

App ClassLoader(扩展系统类加载器):负责加载应用程序classpath目录下的所有jar和class文件


CustomClassLoader:自定义ClassLoader(继承java.lang.ClassLoader类/Extension ClassLoader/App ClassLoader);然而,不能继承Bootstrap ClassLoader,因为Bootstrap ClassLoader由底层C++编写,已迁入JVM内核,当JVM启动后,Bootstrap ClassLoader也随之启动,负责加载核心类库,并后遭Extension ClassLoader和App ClassLoader类加载器;

查看父类加载器

/**
 * 查看父类加载器
 */
private static void test1() {
    ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
    System.out.println("系统类装载器:" + appClassLoader);
    ClassLoader extensionClassLoader = appClassLoader.getParent();
    System.out.println("系统类装载器的父类加载器——扩展类加载器:" + extensionClassLoader);
    ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
    System.out.println("扩展类加载器的父类加载器——引导类加载器:" + bootstrapClassLoader);
}

ExtensionClassLoader的parent为null因为bootstrapClassLoader不是一个普通的Java类

ClassLoader加载类的原理

原理

使用双亲委托模型来搜索类;每个ClassLoader实例都有一个父类加载器的引用(是一种包含关系);而虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有福类加载器,但可以用作其他的任务委托给它的福类加载器,这个过程是自上而下一次检查的;

首先有最顶层的类加载器Bootstrap ClassLoader视图加载,若没有加载到;则将任务转交给Extension ClassLoader试图加载,若没有加载到;则转交给App ClassLoader进行加载,若没有加载成功,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类;若都没有加载到这个类,则跳出ClassNotFoundException异常;否则将这个找到的类生成一个类的定义,并将它加载到内存中,最后返回这个类在内存中的实例对象;

为什么使用双亲委托模型

因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次;考虑到安全因素,若不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非改变JDK中ClassLoader搜索类的默认算法。

JVM搜索类的时候,如何判定两个class是相同的

JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的

自定义ClassLoader

三个重要方法

  • loadClassclassloader加载类的入口,此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从父ClassLoader中寻找,如仍然没找到,则从BootstrapClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法,如加载顺序相同,则可通过覆盖findClass来做特殊的处理,例如解密、固定路径寻找等,当通过整个寻找类的过程仍然未获取到Class对象时,则抛出ClassNotFoundException。如类需要resolve,则调用resolveClass进行链接。

  • findClass此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。

  • defineClass此方法负责将二进制的字节码转换为Class对象,这个方法对于自定义加载类而言非常重要,如二进制的字节码的格式不符合JVM Class文件的格式,抛出ClassFormatError;如需要生成的类名和二进制字节码中的不同,则抛出NoClassDefFoundError;如需要加载的class是受保护的、采用不同签名的或类名是以java.开头的,则抛出SecurityException;如需加载的class在此ClassLoader中已加载,则抛出LinkageError。

在自定义ClassLoader时,一般只重写findClass而不是loadClass

loadClass源码

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;
        }
    }

JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的判断方法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可

自定义ClassLoader

来自:http://blog.csdn.net/tonytfjing/article/details/47212291

1.定义一个Person接口

public interface Person {
    public void say();
}

2.再定一个类实现这个接口

public class HighRichHandsome implements Person {

    @Override
    public void say() {
        System.out.println("I don't care whether you are rich or not");
    }

}

3.编写ClassLoader

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{
    /* 
     * 覆盖了父类的findClass,实现自定义的classloader
     */
    @Override
    public Class<?> findClass(String name) {
        byte[] bt = loadClassData(name);
        return defineClass(name, bt, 0, bt.length);
    }

    private byte[] loadClassData(String className) {
        InputStream is = getClass().getClassLoader().getResourceAsStream(
                className.replace(".", "/") + ".class");
        ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
        int len = 0;
        try {
            while ((len = is.read()) != -1) {
                byteSt.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return byteSt.toByteArray();
    }
}

4.测试类

package classloader;

public class LoaderTest {
    public static void main(String args[]) throws Exception{
        test2();
    }
    
    
    private static void test2() throws Exception{
        MyClassLoader loader = new MyClassLoader();
        Class<?> c = loader.loadClass("classloader.HighRichHandsome");
        System.out.println("Loaded by :" + c.getClassLoader());

        Person p = (Person) c.newInstance();
        p.say();

        HighRichHandsome man = (HighRichHandsome) c.newInstance();
        man.say();    
    }

    private static void test3() throws Exception{
    MyClassLoader loader = new MyClassLoader();
    Class<?> c = loader.findClass("com.alibaba.classload.HighRichHandsome");
    System.out.println("Loaded by :" + c.getClassLoader());

    Person p = (Person) c.newInstance();
    p.say();

    //注释下面两行则不报错
    HighRichHandsome man = (HighRichHandsome) c.newInstance();
    man.say();    
}
}

测试结果:

Loaded by :sun.misc.Launcher$AppClassLoader@73d16e93
I don't care whether you are rich or not
I don't care whether you are rich or not

遇到的一些问题

学习ClassLoader的教程是在网上看的;于是在使用sun.misc.Launcher的时候发现编译器找不到这个类;但是sun.misc这个包是存在于rt.jar的,只是apidoc中没有,那是因为sun开头的包不属于J2SE规范,是Sun公司的内部实现;
故,使用一下方法解决:

右键项目-》属性-》java bulid path-》jre System Library-》access rules-》resolution选择accessible,下面填上** 点击确定


参考资料:http://blog.csdn.net/xyang81/article/details/7292380
http://blog.csdn.net/tonytfjing/article/details/47212291

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容