Java类加载器

类加载器

虚拟机设计团队将类加载阶段中“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部实现,以便让应用程序自己决定如何获取所需要的类。实现这个动作的代码模块叫做**“类加载器”**。

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确定其在JAva虚拟机中的唯一性:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义。

类加载器种类

  • BootstrapClassLoader(启动类加载器) :最顶层的加载类,由C++实现,负责加载 %JAVA_HOME%/lib目录下的jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类。
  • ExtensionClassLoader(扩展类加载器) :主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。开发者可以直接使用。
  • AppClassLoader(应用程序类加载器) :面向用户的加载器,负责加载当前应用用户类路径classpath下的所有jar包和类。开发者可以直接使用,如果应用程序没有自定义过自己的类加载器,一般情况下这就是程序中默认的类加载器。

双亲委派模型

除了顶层的启动类加载器外,其余类加载器都应当有自己的父类加载器,使用组合关系复用父加载器代码。
双亲委派模型
如果一个类加载器收到类加载的的请求,先把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器无法完成这个加载请求时,子加载器才会尝试自己去加载。

Java类随着它的加载器一起具备了一种带有优先级的层次关系。

双亲委派模型的实现代码非常简单,逻辑非常清晰,都集中在 java.lang.ClassLoader 的 loadClass() 中,相关代码如下所示。

private final ClassLoader parent; 
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 首先,检查请求的类是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {//父加载器不为空,调用父加载器loadClass()方法处理
                        c = parent.loadClass(name, false);
                    } else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                   //抛出异常说明父类加载器无法完成加载请求
                }

                if (c == null) {
                    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(),若父加载器为null则默认使用启动类加载器作为父加载器。如果父类加载失败,抛出异常,再调用自己的findClass()进行加载。

JDK1.2后已经不提倡用户去覆盖loadClass(),而是应当把自己的类加载逻辑写到findClass()中,保证新写出的类加载器符合双亲委派规则。

双亲委派模型的好处

双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的 Object 类。

如果我们不想用双亲委派模型怎么办?

为了避免双亲委托机制,我们可以自己定义一个类加载器,然后重写 loadClass() 即可。

自定义类加载器

除了 BootstrapClassLoader 其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader。如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader。

破坏双亲委派模型

  • 第一次: 版本向前兼容
  • 第二次:基础类调用回用户代码怎么办?
    线程上下文类加载器(Thread Context ClassLoader)
    Java所有设计SPI的加载动作都采用这种方式:例如JNDI,JDBC等。
  • 第三次:追求程序动态性,代码热替换,模块热部署等
    OSGi
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值