类加载器(ClassLoader)概述

ClassLoader层级关系

类加载器示意图

每个类加载器都有自己预先定义好的加载class的路径。
1、BootStrap class loader(根类加载器)
负责加载jdk/libs/rt.jar中所有的class。它是最上层的classLoader,如果打印它的父classLoader,会显示null。
2、Extension class loader(扩展类加载器)
加载jdk/lib/ext中的class
3、System or Application class loader (系统/应用类加载器)
加载classpath指定的路径下的class

类加载器表示
根类加载器null
扩展类加载器sun.misc.Launcher$ExtClassLoader
系统/应用类加载器sun.misc.Launcher$AppClassLoader

双亲委托模型

一句话概括就是:父亲能找到的class,我就懒得找了,直接继承,多省事啊。
至于为什么叫双亲委托模型,知乎上已有吐槽。

Java 中的双亲委派的“双”怎么理解 ?
https://www.zhihu.com/question/288949359

如果将class放到根类加载器加载class的目录下,那么就轮不到系统/应用类加载器。
如何查看类加载器的加载class目录?

System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));

输出

C:\Program Files\Java\jdk1.8.0_231\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\rt.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\sunrsasign.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_231\jre\classes
换行
C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
换行
C:\Program Files\Java\jdk1.8.0_231\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_231\jre\lib\rt.jar;D:\github_projects\jvm\jvm-base\target\classes;

类的名字

ClassLoader加载类的时候,需要先知道class的名字,jdk中class的名字包含包名。

例如java.lang.String。
如果加入包含内部类,名字命名如下,以$隔开。

package com.sss.jvm.unclassified;

public class Test1 {
    static class A{
        static class A1{

        }
    }

    static class B{

    }
    public static void main(String[] args) {
        System.out.println(Test1.A.class);
        System.out.println(Test1.A.A1.class);
        System.out.println(Test1.B.class);
    }
}

输出

class com.sss.jvm.unclassified.Test1$A
class com.sss.jvm.unclassified.Test1$A$A1
class com.sss.jvm.unclassified.Test1$B

自定义ClassLoader

自定义ClassLoader必须继承java.lang.ClassLoader抽象类。
里面有几个重要的方法:
1、loadClass:加载class
2、findClass:寻找name指定的class,遵循双亲委托模型,被loadClass调用。如果findClass找不到指定的class,throws java.lang.ClassNotFoundException,如果找到了调用defineClass方法。
3、defineClass:将入参中的byte[]数组转换成Class对象。

package com.sss.jvm.classloader.test7;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * 自定义类加载器
 */
public class MyClassLoader extends ClassLoader {
    private String classLoaderName; //自定义类加载器的名字,仅仅起标识作用

    private final String fileExtension = ".class";

    private String path;

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public MyClassLoader(){
    }

    public MyClassLoader(String classLoaderName){
        super(); //将系统类加载器当作该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }

    /**
     * 给自定义加载器指定父加载器
     * @param parent
     * @param classLoaderName
     */
    public MyClassLoader(ClassLoader parent, String classLoaderName){
        super(parent);//显式指定该类加载器的父加载器
        this.classLoaderName = classLoaderName;
    }

    /**
     *
     * @param name
     * @return
     */
    private byte[] loadClassData(String name){
        ByteBuffer data = null;
        FileChannel fileChannel;
        FileInputStream fileInputStream;
        try{
            name  = name.replace(".","/");
            fileInputStream = new FileInputStream(this.path + name + this.fileExtension);
            fileChannel = fileInputStream.getChannel();
            data = ByteBuffer.allocate((int) fileChannel.size());
            fileChannel.read(data);
        }catch(Exception ex){
            ex.printStackTrace();
        } finally {
            //try-with-resource
            /*fileChannel.close();
            fileInputStream.close();*/
        }
        return data.array();
    }

    /**
     * 寻找name指定的class,遵循双亲委托模型,被loadClass调用。
     * @param name
     * @return
     */
    @Override
    protected Class<?> findClass(String name)  {
        byte[] data = this.loadClassData(name);
        return this.defineClass(name,data,0,data.length);
    }
}

ClassLoader的基本原则

Java的类加载器在运行期加载class。遵循3个基本原则:委托、可见性、唯一性。
委托性:加载class的请求会先转发给父加载器,父加载器找不到class才会由自己加载。
可见性:子加载器能看到父加载器加载的所有class,反之不行。
唯一性:父子加载器保证某个class只会加载一次,不会出现多个。

测试一

用上面的自定义类加载器测试一:

public static void main(String[] args) throws Exception {
    MyClassLoader loader1 = new MyClassLoader("自定义的类加载器");
    loader1.setPath("D:/");
    Class<?> clazz = loader1.loadClass("com.sss.jvm.dump.JvmDemo01");
    Object object = clazz.newInstance();
    System.out.println(object.hashCode());
    System.out.println(object.getClass().getClassLoader());
    System.out.println(object.getClass().getClassLoader().hashCode());

    System.out.println("-----------------------------------");

    MyClassLoader loader2 = new MyClassLoader("自定义的类加载器");
    loader2.setPath("D:/");
    clazz = loader2.loadClass("com.sss.jvm.dump.JvmDemo01");
    object = clazz.newInstance();
    System.out.println(object.hashCode());
    System.out.println(object.getClass().getClassLoader());
    System.out.println(object.getClass().getClassLoader().hashCode());
}

idea运行,输出肯定是:可以看到由系统/应用类加载器加载。使用2次类加载器创建类,两次类加载器都是AppClassLoader。因为根据双亲委托模型,根本轮不到我们自己创建的类加载发挥作用。

834600351
sun.misc.Launcher$AppClassLoader@18b4aac2
414493378
-----------------------------------
471910020
sun.misc.Launcher$AppClassLoader@18b4aac2
414493378

但是如果将target里将要加载的目标class删掉,再次运行:

java.io.FileNotFoundException: D:\com\sss\jvm\dump\JvmDemo01.class (系统找不到指定的路径。)
	at java.io.FileInputStream.open0(Native Method)
	at java.io.FileInputStream.open(FileInputStream.java:195)
	at java.io.FileInputStream.<init>(FileInputStream.java:138)
	at java.io.FileInputStream.<init>(FileInputStream.java:93)
	at com.sss.jvm.classloader.test7.MyClassLoader.loadClassData(MyClassLoader.java:54)
	at com.sss.jvm.classloader.test7.MyClassLoader.findClass(MyClassLoader.java:75)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at com.sss.jvm.classloader.test7.MyClassLoader.main(MyClassLoader.java:83)
Exception in thread "main" java.lang.NullPointerException
	at com.sss.jvm.classloader.test7.MyClassLoader.loadClassData(MyClassLoader.java:65)
	at com.sss.jvm.classloader.test7.MyClassLoader.findClass(MyClassLoader.java:75)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
	at com.sss.jvm.classloader.test7.MyClassLoader.main(MyClassLoader.java:83)

再将目标类手动拷贝到D盘指定文件夹下运行:就会发现我们自定义的类加载器起作用了。

531885035
com.sss.jvm.classloader.test7.MyClassLoader@31befd9f
834600351
-----------------------------------
135721597
com.sss.jvm.classloader.test7.MyClassLoader@548c4f57
1418481495

测试二

还是用上面自定义类加载器进行测试二:

public static void main(String[] args) throws Exception {
    MyClassLoader2 loader1 = new MyClassLoader2("自定义的类加载器");
    loader1.setPath("D:/");
    Class<?> clazz = loader1.loadClass("com.sss.jvm.dump.JvmDemo01");
    Object object = clazz.newInstance();
    System.out.println(object.hashCode());
    System.out.println(object.getClass().getClassLoader());
    System.out.println(object.getClass().getClassLoader().hashCode());

    System.out.println("-----------------------------------");

    MyClassLoader2 loader2 = new MyClassLoader2(loader1,"自定义的类加载器");
    loader2.setPath("D:/");
    clazz = loader2.loadClass("com.sss.jvm.dump.JvmDemo01");
    object = clazz.newInstance();
    System.out.println(object.hashCode());
    System.out.println(object.getClass().getClassLoader());
    System.out.println(object.getClass().getClassLoader().hashCode());
}

删掉idea工具target里的目标class,运行:

531885035
com.sss.jvm.classloader.test7.MyClassLoader2@31befd9f
834600351
-----------------------------------
1418481495
com.sss.jvm.classloader.test7.MyClassLoader2@31befd9f
834600351

情况和测试一有所不同。测试二是将loader1作为了loader2的父加载器,所以JvmDemo01类的类加载器是同一个。而测试一,loader1和loader2无关系,两者均能加载目标JvmDemo01类。由此也可证明,只要类加载器不同,是可以存在同名的类的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值