ClassLoader的理解与双亲委派机制和违反双亲委派

2 篇文章 0 订阅

这里大量参考了如下博客
https://blog.csdn.net/javazejian/article/details/73413292
ClassLoader是一个抽象类,他有很多个实现
BootstrapClassLoader是最顶层的classloader 负责加载jvm需要的类 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中 比如rt.jar 他本身是C++实现的。
ExtClassLoader 是次级的classloader 负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,本身是java实现的sun.misc.Launcher$ExtClassLoader。

AppClassLoader 是我们最常用的。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,本身也是 sun.misc.Launcher$AppClassLoader。
BootstrapClassLoader -》ExtClassLoader -》AppClassLoader 存在这样的父子关系。
但是这三个类之间本身没什么直接继承关系,他们都是ClassLoader的子类(BootstrapClassLoader除外 它由c++实现),但是通过组合模式 实现了类似父子关系的关系。双亲委派模式
双亲委派模式的实现是依赖于 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 {     // 如果父为null 说明已经是BootstrapClassLoader了
                        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();
                   // 通过findClass 加载 类
                    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;
        }
    }

也就是说他们通过非继承关系实现了类似父子关系。
在这里插入图片描述
在这里插入图片描述
findClass()方法,方法返回Class对象,他是在loadClass父类无法加载时 由自己加载的方法。ClassLoader本身也没有实现findClass方法 我们的extClassLoader 和 appClassLoader 都是URLClassLoader的子类 而 URLClassLoader 重写了findClass方法

 protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                       // 替换为相对 路径
                        String path = name.replace('.', '/').concat(".class");
                       // 转换成Resource对象   ucp 里 的path属性包含了 能够加载的 类路径
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try { 
                                  // 转换成真正的class对象
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

extClassLoader 能够加载的目录
在这里插入图片描述appClassLoader能够加载的目录
在这里插入图片描述
在这里插入图片描述
这也就是为啥静态覆盖类能够生效的原因,因为classes的加载在 外部jar 包之前。
再来看defineClass

private Class<?> defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        // 取最后一个点的位置
        int i = name.lastIndexOf('.');
         // 获取到刚才resource 中的真实url地址
        URL url = res.getCodeSourceURL();
        if (i != -1) {
            // 取到 包名
            String pkgname = name.substring(0, i);
            // Check if package already loaded.
            Manifest man = res.getManifest();
            // 检查包名的密封性
            definePackageInternal(pkgname, man, url);
        }
        // Now read the class bytes and define the class
      // 从res 中获取 如果有就用res中的
        java.nio.ByteBuffer bb = res.getByteBuffer();
        if (bb != null) {
            // Use (direct) ByteBuffer:
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, bb, cs);
        } else {
            //获取类的byte数组
            byte[] b = res.getBytes();
            // must read certificates AFTER reading bytes.
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            // 真正读取byte数组并 创建class 对象
            return defineClass(name, b, 0, b.length, cs);
        }
    }

再到ClassLoader抽象类中

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        // 检查是否能够进行加载
        protectionDomain = preDefineClass(name, protectionDomain);
       // 获取 加载class的 加载地址 比如file:/E:/myWorkSpace/interview/target/classes/
        String source = defineClassSourceLocation(protectionDomain);
         // 加载class  native方法
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        //加载 后置方法
        postDefineClass(c, protectionDomain);
        return c;
    }
 private ProtectionDomain preDefineClass(String name,
                                            ProtectionDomain pd)
    {
        if (!checkName(name))
            throw new NoClassDefFoundError("IllegalName: " + name);

        // Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
        // relies on the fact that spoofing is impossible if a class has a name
        // of the form "java.*"
        // 包名不能以java开头
        if ((name != null) && name.startsWith("java.")) {
            throw new SecurityException
                ("Prohibited package name: " +
                 name.substring(0, name.lastIndexOf('.')));
        }
        if (pd == null) {
            pd = defaultDomain;
        }

        if (name != null) checkCerts(name, pd.getCodeSource());

        return pd;
    }

所以loadClass 是实现双亲委派机制 而findClass才是真正加载类的地方。
ExtClassLoader 和 AppClassLoader 都是Launcher的内部类 他们是在Launcher 的构造方法中创建的
Launcher

public Launcher() {
        // 创建extClassLoader
        Launcher.ExtClassLoader var1;
        try {
            var1 = Launcher.ExtClassLoader.getExtClassLoader();
        } catch (IOException var10) {
            throw new InternalError("Could not create extension class loader", var10);
        }
        // 创建AppClassLoader
        try {
            this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
        } catch (IOException var9) {
            throw new InternalError("Could not create application class loader", var9);
        }
        // 给线程上下文设置ClassLoader 为 appClassLoader
        Thread.currentThread().setContextClassLoader(this.loader);
        // 这里设置安全检查 默认为 null  不创建
        String var2 = System.getProperty("java.security.manager");
        if (var2 != null) {
            SecurityManager var3 = null;
            if (!"".equals(var2) && !"default".equals(var2)) {
                try {
                    var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
                } catch (IllegalAccessException var5) {
                } catch (InstantiationException var6) {
                } catch (ClassNotFoundException var7) {
                } catch (ClassCastException var8) {
                }
            } else {
                var3 = new SecurityManager();
            }

            if (var3 == null) {
                throw new InternalError("Could not create SecurityManager: " + var2);
            }

            System.setSecurityManager(var3);
        }

    }

违反双亲委派
1.调用findClass方法
2.继承URLClassLoader 如果重写了 loadClass方法并且 不再实现双亲委派的机制 或者直接调用了 findClass方法
违反双亲委派的用途
1.热部署
2.自定义地址class加载(从非classPath中加载)
3.SPI
包名为 java.开头的类是不允许加载的 这时如果 rt.jar 里的某个接口需要第三方提供实现(比如sql.Driver) 由于rt.jar 是由 bootstrapClassLoader 加载的 但是它又不能加载第三方jar包,这时怎么办 就需要违反双亲委派机制 由 bootstrapClassLoader 委派 appClassLoader来进行加载

//DriverManager是Java核心包rt.jar的类
public class DriverManager {
 //省略不必要的代码
    static {
        loadInitialDrivers();//执行该方法
        println("JDBC DriverManager initialized");
    }

//loadInitialDrivers方法
 private static void loadInitialDrivers() {
     sun.misc.Providers()
     AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {
    //加载外部的Driver的实现类
                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);
                 Iterator<Driver> driversIterator = loadedDrivers.iterator();
                 try{
                    while(driversIterator.hasNext()) {
                        // 实际上在这里才加载
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
    }

public static <S> ServiceLoader<S> load(Class<S> service) {
        // 这里就获取到了我们之前Launcher注册的 线程上下文加载器  也就是appClassLoader
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        // 这里创建了一个ServiceLoader 保存了 信息
        return ServiceLoader.load(service, cl);
    }

// 这里的next java.util.ServiceLoader#iterator#next
  public S next() {
                if (knownProviders.hasNext())
                    return knownProviders.next().getValue();
                 // 会进入这里
                return lookupIterator.next();
            }
// 这里是 java.util.ServiceLoader.LazyIterator #next
 public S next() {
            if (acc == null) {
                //进入这里
                return nextService();
            } else {
                PrivilegedAction<S> action = new PrivilegedAction<S>() {
                    public S run() { return nextService(); }
                };
                return AccessController.doPrivileged(action, acc);
            }
        }

private S nextService() {
            if (!hasNextService())
                throw new NoSuchElementException();
            String cn = nextName;
            nextName = null;
            Class<?> c = null;
            try {
                c = Class.forName(cn, false, loader);
            } catch (ClassNotFoundException x) {
                fail(service,
                     "Provider " + cn + " not found");
            }
            if (!service.isAssignableFrom(c)) {
                fail(service,
                     "Provider " + cn + " not a subtype");
            }
            try {
                S p = service.cast(c.newInstance());
                providers.put(cn, p);
                return p;
            } catch (Throwable x) {
                fail(service,
                     "Provider " + cn + " could not be instantiated",
                     x);
            }
            throw new Error(); // This cannot happen
        }

在这里插入图片描述
在这里插入图片描述
这就实现了委托appClassLoader给bootstrapClassLoader加载类信息 也就是为何提供了违反双亲委派的方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值