Java虚拟机——类加载器

  • Java虚拟机设计团队有意把类加载阶段中的"通过一个类的权限定名来获取描述该类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序来决定自己如何去获取所需的类。
  • 实现这个动作的代码称为"类加载器"

1 类与类加载器

  • 类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用确远超类加载阶段。
  • 对于任意一个类,都必须由加载它的类加载器和它本身一起共同决定其在Java虚拟机中的唯一性。
  • 即使两个类来自于同一个Class文件,被同一个Java虚拟机加载,但是只要加载它们的类加载器不同,那这两个类就必定不相等。
package specialTest;

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

public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {

        ClassLoader myLoader = new ClassLoader() {
            @Override
             public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(filename);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);

                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        //loadClass参数代表类的全限定名

        Object obj = myLoader.loadClass("specialTest.ClassLoaderTest");
        System.out.println(obj.getClass());
        System.out.println(obj instanceof ClassLoaderTest);

    }
}
  • 这里构造了一个简单的类加载器,他可以加载与自己在同一路径下的Class文件,我们使用这个类加载器去加载我们的类,并实例化了这个类对象。
  • 但是在类型检查的时发现返回了faslse,因为Java虚拟机中同时存在了两个ClassLoaderText类,一个是由Java虚拟机的应用程序类加载器所加载的。一个是由我们自定义的类加载器加载的。
    在这里插入图片描述

2 双亲委派模型

  • 以Java虚拟机角度来看,只存在两种不同的类加载器
  1. 一种是启动类加载器,这个类加载器使用C++语言实现,是虚拟机自身的一部分。
  2. 另外一种就是其他所有的类加载器,这些类加载器都由Java语言实现,独立存在于Java虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader
  • 自JDK1.2以来,Java一直保持着三层类加载器、双亲委派的类加载架构。

2.1 三层类加载器

  1. 启动类加载器
  • 负责加载JRE核心库(rt.jar等)以及其他核心类库。这些类库是由本地代码实现的,无法在Java中访问它们。
  • 启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器去处理,那直接使用null代替即可。
  1. 拓展类加载器
  • 它是一种Java系统类库的拓展机制,可以将通用性的类库放置在ext目录里以拓展Java SE的功能,在JDK 9之后被模块化带来的天然的拓展功能代替。
  1. 应用程序类加载器
  • 这个类加载器由sun.misc.Launcher$AppClassLoader来实现的。
  • 由于应用程序类加载器是ClassLoader类中的getSystemClassLoader()方法的返回值,所以也可以称他为系统类加载器。
  • 他负责加载用户类路径上所有的类库,开发者也可以直接在代码中使用这个类加载器。
    在这里插入图片描述

2.2 双亲委派模型

  • 指的就是各种类加载器之间的层次关系
    在这里插入图片描述
  • 双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都要有自己的父类加载器。
  • 这里的父类加载器一般不是以继承的关系来实现的,而是通过组合关系来复用父加载器的代码。

2.3 双亲委派模型的工作过程

  • 如果一个类加载器收到了类加载的请求。
  • 他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。
  • 所有加载的请求都会传送到最顶层的启动类加载器中,只有父类加载器反馈自己无法完成这个加载请求。(它的搜索范围中没有找到这个类),子加载器才会去尝试自己解决。、

2.4 使用双亲委派模型的好处?

  1. Java中的类会随着类加载一起具备一种带有优先级的层次关系。
  • 例如类java.lang.Object,他放在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载。因此Object类在程序的各种类加载环境中都能保证是同一个类。
  1. 如果不用这个模型,让类加载器自行去加载的话,用户自己也编写了一个名为java.lang.Object类,放在程序的ClassPath中,系统就会出现多个不同的Object类。

2.5 双亲委派模型的实现

  • 实现非常简单,主要集中在java.lang.ClassLoader的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
                //说明父类加载器无法完成加载的请求
            }

            if (c == null) {
                //如果父类加载器无法加载时
                //调用本身的findClass方法进行类加载
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

3 破坏双亲委派模型

  • Java设计者推荐使用这种类加载器实现方式,直到Java模块化出现为止,双亲委派模型主要出现过3次较大规模的"被破坏"的情况。
  1. 第一次破坏
  • 父类的loadClass有被子类覆盖的可能性
    在这里插入图片描述
  1. 第二次破坏
  • 双亲委派很好的解决了各个类加载器协作时基础类型的一致性问题(越基础的类由越上层的加载器进行加载)。基础的类型之所以基础,就是因为它们总是被用户代码继承、调用的API。
  • 但是如果基础类型要调用回用户的代码就会有问题。
    在这里插入图片描述
  1. 第三次破坏
  • 用户对程序的动态性的追求,希望可以实现类似于热插拔的效果,在不重启的情况下。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值