类加载器

类加载器

java被编译以后会生成一个.class的文件,这个文件是存放了一些对象的字节码信息,而这些字节码需要被加载到内存中,,这时候就需要我们用到ClassLoade


先看下面的代码

ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
// sun.misc.Launcher$AppClassLoader
//sun.misc.Launcher$ExtClassLoader
while (classLoader != null) {
    System.out.println(classLoader.getClass().getName());
   classLoader = classLoader.getParent();
}
        
// System类的加载器的名称:null
System.out.println("System类的加载器的名称:"+System.class.getClassLoader());
// List类的加载器的名称:null
System.out.println("List类的加载器的名称:"+ List.class.getClassLoader());
// this类的加载器的名称:sun.misc.Launcher$AppClassLoader
System.out.println("this类的加载器的名称:"+ ClassLoaderTest.class.getClassLoader().getClass().getName());

从打印结果中可以看出来 ClassLoaderTest类时由AppClassLoader类加载器加载的

Java虚拟机中类加载器

java系统默认三个主要的类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader;
前两我们我们看到过了,没有见过的就是第一个BootStrap,那么这个是干什么用的,这里我们先思考一个问题,ExtClassLoader,AppClassLoader这两个都是java文件,既然是java文件,那么它们也需要个类加载器,所以可以认为加载它们的应该不能再是一个java类了,那是什么东东?其实就是BootStrapç是用c/c++写的,封装到JVM内核当中了

  • BootStrap 负责将 它负责将<Java_Runtime_Home>/lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中,由于是虚拟机内核中的代码,开发者是无法引用的
  • ExtClassLoader 由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
  • AppClassLoader 由Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。

JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

ClassLoader

类加载器的委托机制:

当Java虚拟机要加载第一个类的时候,到底派出哪个类加载器去加载呢?

  • 首先当前线程的类加载器去加载线程中的第一个类(当前线程的类加载器:Thread类中有一个get/setContextClassLoader(ClassLoader cl);方法,可以获取/指定本线程中的类加载器)
  • 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B
  • 还可以直接调用ClassLoader.loadClass(String className)方法来指定某个类加载器去加载某个类
    每个类加载器加载类时,又先委托给其上级类加载器当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则会抛出ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild()方法。
  • 如上图所示: CustomClassLoader->AppClassLoader->ExtClassLoader->BootStrap.自定定义的CustomClassLoader首先会先委托给AppClassLoader,AppClassLoader会委托给ExtClassLoader,ExtClassLoader会委托给BootStrap,这时候BootStrap就去加载,如果加载成功,就结束了。如果加载失败,就交给ExtClassLoader去加载,如果ExtClassLoader加载成功了,就结束了,如果加载失败就交给AppClassLoader加载,如果加载成功,就结束了,如果加载失败,就交给自定义的CustomClassLoader类加载器加载,如果加载失败,就报ClassNotFoundException异常,结束。

现在咱们再想一下上面的代码的打印结果,System类,List,Map等这样的系统提供jar类都在rt.jar中,所以由BootStrap类加载器加载,因为BootStrap是祖先类,不是Java编写的,所以打印出class为null,默认情况它的类加载时由AppClassLoader来完成的,如果我们将项目打包成.jar并将文件拷贝到Java的安装目录中的Java/jre7/lib/ext/目录下,这时候就发,这时候就说明了上面的结论是正确的,因为ExtClassLoader加载jre/ext/*.jar,首先AppClassLoader类加载器发请求给ExtClassLoader,然后ExtClassLoader发请求给BootStrap,但是BootStrap没有找到ClassLoaderTest类,所以交给ExtClassLoader处理,这时候ExtClassLoader.jar中找到了ClassLoaderTest类,所以就把它加载了,然后结束了。

自定义类加载器

先看下他的构造方法

     protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }

通过上面的分析,默认的类加载器是AppClassLoader,如果我们自定义CloassLoader的话,主要是自定义加载Class文件的方式,可以查看loadClass里面的源码

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 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
                }
            }
            return c;
    }

仔细分析源码发现,只需定义findClass方法即可,findClass这个方法就是根据name来查找到class文件,在loadClass方法中用到,所以我们只能重写这个方法了,只要在这个方法中找到class文件,返回一个Class对象即可。
问题来了,如果将一个class文件变成Class对象呢?我们需要用到下面的这个方法:defineClass,这个方法很简单就是将class文件的字节数组编程一个Class对象,这个方法不能重写,内部实现是在C/C++代码中实现的

下面我们重新定义个ClassLoader,重写里面的findClass方法,我们去加载一个加密的文件,在findClass里面去解密

protected Class<?> findClass(String name) throws ClassNotFoundException {  
        //class文件的路径  
        String classPathFile = classDir + "/" + name + ".class";  
        try {  
            //将class文件进行解密  
            FileInputStream fis = new FileInputStream(classPathFile);  
            ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
            // 这里是解密操作 
            encodeAndDecode(fis,bos);  
            byte[] classByte = bos.toByteArray();  
            //将字节流变成一个class  
            return defineClass(classByte,0,classByte.length);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return super.findClass(name);  
    } 

这个时候我们调用我们自定义的那个ClassLoader的话能够调用成功,通过getClass().getClassLoader().getClass().getName()打印出来的ClassLoader就是我们自己定义的ClassLoader,

到此不要以为结束了,这里还有很多的问题呀,看一下问题的结果是没有问题,但是这里面还有很多的东西需要去理解的,首先来看一下,按照我们之前说的类加载机制,应该是先交给父级的类加载器,AppClassLoader->ExtClassLoader->BootStrap,ExtClassLoader和BootStrap没有找到ClassLoaderAttach.class,但是AppClassLoader类加载器应该能找到呀,可以为什么也没有找到呢?这时因为loadClass方法在使用系统类加载器的时候需要传递全称(包括包名),我们传递ClassLoaderAttachment的话,AppClassLoader也是没有找到这个ClassLoaderAttachment,所以还是MyClassLoader处理了,如果我们这么处理后,应该是能够正常加载的

Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");

改成

Class classDate = new MyClassLoader("class_temp").loadClass("com.ding.demo.ClassLoaderAttachment");

这样修改后就变成AppClassLoader了,这个时候我们如果用了加密后的ClassLoaderAttachment文件,是会报错的,无法加载该文件的.

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

推荐阅读更多精彩内容