类加载器

  类加载器负责将class文件加载进入虚拟机中。类加载过程中,第一步就是类加载器去加载class,然后在是链接、初始化。

虚拟机自带的类加载器

启动类加载器(引导类加载器,Bootstrap ClassLoader)

  1. 这个类加载使用C/C++语言实现的,嵌套在JVM内部
  2. 它用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jar、resources.jar或sun.boot.class.path路径下的内容),用于提供JVM自身需要的类
  3. 并不继承自java.lang.ClassLoader,没有父加载器
  4. 加载扩展类和应用程序类加载器,并作为他们的父类加载器
  5. 出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类

扩展类加载器(Extension ClassLoader)

  1. Java语言编写,由sun.misc.Launcher$ExtClassLoader实现
  2. 派生于ClassLoader类
  3. 父类加载器为启动类加载器
  4. 从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载

应用程序类加载器(也称为系统类加载器,AppClassLoader)

  1. Java语言编写,由sun.misc.LaunchersAppClassLoader实现
  2. 派生于ClassLoader类
  3. 父类加载器为扩展类加载器
  4. 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库
  5. 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载
  6. 通过classLoader.getSystemclassLoader()方法可以获取到该类加载器

各个加载器之间的关系

  类加载器之间存在父子关系,但不是java中的继承关系。其中AppClassLoader的父加载器是Extension ClassLoader,Extension ClassLoader的父加载器是Bootstrap ClassLoader。自定义的类加载器的父加载器是AppClassLoader。

 		//获取系统类加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //获取其上层:扩展类加载器
        ClassLoader extClassLoader = systemClassLoader.getParent();
        System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1540e19d

        //获取其上层:获取不到引导类加载器
        ClassLoader bootstrapClassLoader = extClassLoader.getParent();
        System.out.println(bootstrapClassLoader);//null

        //对于用户自定义类来说:默认使用系统类加载器进行加载
        ClassLoader classLoader = Dog.class.getClassLoader();
        System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2

        //String类使用引导类加载器进行加载的。---> Java的核心类库都是使用引导类加载器进行加载的。
        ClassLoader classLoader1 = String.class.getClassLoader();
        System.out.println(classLoader1);//null

        //线程上下文加载器
        System.out.println(Thread.currentThread().getContextClassLoader());

在这里插入图片描述
  打印出null是因为引导类加载器是用其他语言写的,所以为空。线程上下文加载器其实就是获取当前线程所用的入口加载器(没有手动设置的情况下),本质上是没有专门的线程上下文加载器,只是一种获取其他加载器的手段。

双亲委托机制

  Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式

  1. 如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
  2. 如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
  3. 如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
  4. 父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常
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;
        }
    }

  loadClass(String name, boolean resolve)(重载的方法)是加载类的一个方法入口,它有两个参数,一个是string类型,它是要加载的类名。还有一个boolean,代表是否要链接。不链接代表着该类中的静态资源不会立马加载。forName(String name, boolean initialize, ClassLoader loader)这也是一个重载的方法。也可以用来加载类,本质上也是去调用loadClass方法,initialize代表者是否要链接。
  loadClass中上了锁,防止一个类被多次加载。findLoadedClass是查找已经被加载过的类,返回它的Class。然后判断是否被加载过,没有加载过就调用父加载器的loadclass去处理,如果到启动类加载器还未找到该类的class文件,就会抛出文件未找到异常,拓展类加载器会捕获这个异常,但不进行异常处理,拓展类加载器再去找,如果未找到就继续抛出异常,下层加载器再捕获,再去找。
如果在入口加载器也未找到,就抛出异常。(这里我将第一个使用loadClass方法的类加载器叫入口加载器,最终使用findClass方法找到类的叫最终加载器。比如我自己编写的类,其中有使用String.那么String的入口加载器是系统类加载器,最终加载器是启动类加载器)

自定义类加载器

public class SelfLoader extends ClassLoader {


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {

            try {
                byte[] result = getClassFromCustomPath(name);
                if (result == null) {
                    throw new FileNotFoundException();
                } else {
                    //defineClass和findClass搭配使用
                    return defineClass(name, result, 0, result.length);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            throw new ClassNotFoundException(name);
        }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if(!name.equals("com.tb.jicheng.Benchi")){//确保顶级父类Object用默认的类加载器加载
            System.out.println(name);
            return getParent().loadClass(name);
        }else {
            System.out.println(name+"==========");
        }
       return findClass(name);
    }

    //自定义流的获取方式
        private byte[] getClassFromCustomPath(String name) throws IOException, ClassNotFoundException {
            //从自定义路径中加载指定类:细节略
            //如果指定路径的字节码文件进行了加密,则需要在此方法中进行解密操作。
                FileInputStream in = new FileInputStream(new File("G:\\Tbstudy\\baseType\\out\\production\\baseType\\com\\tb\\jicheng\\Benchi.class"));
                byte[] by = new byte[in.available()];
                in.read(by);
                return by;

        }
        public static void main(String[] args) {
            SelfLoader selfLoader = new SelfLoader();
            try {
                //name参数必须为类路径全称com.tb.jicheng.Dog
//                selfLoader.loadClass("com.tb.jicheng.Benchi",true);
                Class<?> clazz = Class.forName("com.tb.jicheng.Benchi", true, selfLoader);
                Car car= (Car) clazz.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

  自定义类加载器可以继承ClassLoader ,重写findClass方法。这个方法就是去寻找class文件,返回一个Class对象。defineClass方法一般和它搭配使用,他可以将一个字节数组转换为一个Class对象。前提是这个数组存的就是class文件。所以不用管class文件的来源、文件是否加密,只要保证最终数组中存的是最初编译生成的class文件就行。
  如果你只重写这个方法,想要自定义类加载器加载自己写的类,你就必须将该类的class文件放在其他三个加载器加载不到的地方,不然最终还是由系统类加载器加载。因为还是遵循双亲委托。如果你不想改变class文件的位置,你就必学重写loadClass方法。loadClass方法中是委托逻辑,不过你要确保原先由启动类、拓展类加载器加载的类最终还是他们加载,不然会抛出文件未找到异常。自定义的类就不委托给父加载器,直接自己去调用findClass。

使用自定义加载器加载的类

public class Benchi implements Car {

    @Override
    public void move() {
        System.out.println("嘟嘟");
    }

    public static void main(String[] args) {
        SelfLoader selfLoader = new SelfLoader();
        try {
            //name参数必须为类路径全称com.tb.jicheng.Dog
            Class<?> clazz = Class.forName("com.tb.jicheng.Benchi", true, selfLoader);
            Car car= (Car) clazz.newInstance();
            car.move();

            Method method = clazz.getMethod("skip");
            method.invoke(clazz.newInstance());

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public interface Car {
    void move();

  default   void skip(){
      System.out.println("铛铛");
    }
}

  如果要使用自定义类加载器加载的类,一种是直接用反射调用方法。还有一种是用Class对象生成对象再强转,不过这要借助接口,因为不同类加载器加载的同一类得到的Class,不属于同一类,也不能相互转换。

类的默认类加载器和线程上下文加载器

  类的默认类加载器这里指的是入口加载器。假设一个A对象,其中有B对象属性。那么B的入口加载器就是A的最终加载器(可以在自定义类加载器的loadClass方法中输出提示去验证)。如果你加载A时没有让A链接,那么B不会立马加载。如果A链接,也只会加载A中的静态资源。如果非静态B对象要在A加载后就加载,就必须要生成A对象。从这也可以看出资源的初始化顺序。不过最先由子类调用loadClass方法.
  线程上下文加载器一般默认是应用类加载器,你不手动设置,线程的上下文加载器就和父线程一样。通常就是由main方法所在的线程就决定了线程上下文加载器是什么。
  通常在spi(Service Provider Interface)中就有用到线程上下文加载器,这个spi可以自己去查一下,就是要在核心类中调用自定义的类。正如上面所说,那么B的入口加载器就是A的最终加载器,这里我们核心类是A,自定义的类是B。A的最终类是启动类加载器,而自定义类的入口加载器就是启动类,在它所加载的路径肯定找不到该类,并且它就是最顶级的加载器,也无法向上委托。所以会抛出文件未找到异常。所以你必须找到对应的类加载器去加载这自定义的类。用线程上下文加载器默认就可以获得应用类加载器。这里我是有个疑问的,就是为什么不直接获取应用加载器,想用哪个加载器就获取哪个?要再通过一层去拿,不麻烦吗。我自己的观点是更加灵活,我不管你线程上下文加载器set一个什么加载器,我程序中只管在里面取。我在main线程中设置什么就是取的什么。或者在对应父线程中设置,所有子线程都会取到相应的。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值