当前位置: 首页 > 图灵资讯 > java面试题> 金三银四精选面试题-简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。

金三银四精选面试题-简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。

来源:图灵教育
时间:2023-11-15 09:27:47
 

简单说说你了解的类加载器,可以打破双亲委派么,怎么打破。

  • 启动类加载器:由C++实现,负责加载JAVA_HOME\lib目录中的,或通过-Xbootclasspath参数指定路径中的,且被虚拟机认可(按文件名识别,如rt.jar)的类。
  • 扩展类加载器:负责加载JAVA_HOME\lib\ext目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
  • 应用程序类加载器:负责加载用户路径(classpath)上的类库。
  • 自定义类加载器:通过继承java.lang.ClassLoader实现自定义的类加载器。

什么是双亲委派机制?

为了防止内存中存在多份同样的字节码,使用了双亲委派机制(它不会自己去尝试加载类,而是把请求委托给父加载器去完成,依次向上),双亲委派模型可以确保安全性,可以保证所有的java类库都是由启动类加载器加载。

什么是打破了双亲委派机制?

因为加载class核心的方法在LoaderClass类的loadClass方法上(双亲委派机制的核心实现),那只要我自定义个ClassLoader,重写loadClass方法(不依照往上开始寻找类加载器),那就算是打破双亲委派机制了。

Tomcat是怎么打破双亲委派机制的呢?

虽说现在都流行使用 Springboot 开发 web 应用,Tomcat 内嵌在 SpringBoot 中。而在此之前,我们会使用最原生的方式,servlet + Tomcat 的方式开发和部署 web 程序。

一个 Tomcat 可能会部署多个这样的 web 应用,不同的 web 应用可能会依赖同一个第三方库的不同版本,为了保证每个 web 应用的类库都是独立的,需要实现类隔离。而Tomcat 的自定义类加载器 WebAppClassLoader 解决了这个问题。

每一个 web 应用都会对应一个 WebAppClassLoader 实例,不同的类加载器实例加载的类是不同的,Web应用之间通各自的类加载器相互隔离。

当然 Tomcat自定义类加载器不只解决上面的问题,WebAppClassLoader 打破了双亲委派机制,即它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的是优先加载Web应用定义的类

Tomcat 的类加载器是怎么打破双亲委派的机制的。我们先看代码:

findClass 方法

@Override
public Class<?> findClass(string name) throws ClassNotFoundException {
    // Ask our superclass to locate this class, if possible
    // (throws ClassNotFoundException if it is not found)
    Class<?> clazz = null;

    // 先在自己的 Web 应用目录下查找 class
    clazz = findClassInternal(name);

    // 找不到 在交由父类来处理
    if ((clazz == null) && hasExternalRepositories) {  
        clazz = super.findClass(name);
    }
    if (clazz == null) {
         throw new ClassNotFoundException(name);
    }
    return clazz;
}

对于 Tomcat 的类加载的 findClass 方法:

  • 首先在 web 目录下查找。(重要)
  • 找不到再交由父类的 findClass 来处理。
  • 都找不到,那就抛出 ClassNotFoundException。

loadClass方法

public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        Class<?> clazz = null;
        //1. 先在本地cache查找该类是否已经加载过
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        //2. 从系统类加载器的cache中查找是否加载过
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
       // 3. 尝试用ExtClassLoader类加载器类加载
        ClassLoader JavaSELoader = getJavaseClassLoader();
        try {
            clazz = javaseLoader.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        // 4. 尝试在本地目录搜索class并加载
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        // 5. 尝试用系统类加载器(也就是AppClassLoader)来加载
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
       }
    //6. 上述过程都加载失败,抛出异常
    throw new ClassNotFoundException(name);
}

加载的步骤:

  1. 先在本地cache查找该类是否已经加载过,看看 Tomcat 有没有加载过这个类。
  1. 如果Tomcat 没有加载过这个类,则从系统类加载器的cache中查找是否加载过。
  1. 如果没有加载过这个类,尝试用ExtClassLoader类加载器类加载,重点来了,这里并没有首先使用 AppClassLoader 来加载类。这个Tomcat 的 WebAPPClassLoader 违背了双亲委派机制,直接使用了 ExtClassLoader来加载类。这里注意 ExtClassLoader 双亲委派依然有效,ExtClassLoader 就会使用 Bootstrap ClassLoader 来对类进行加载,保证了 JRE 里面的核心类不会被重复加载。 比如在 Web 中加载一个 Object 类。WebAppClassLoader → ExtClassLoader → Bootstrap ClassLoader,这个加载链,就保证了 Object 不会被重复加载。
  1. 如果 BoostrapClassLoader,没有加载成功,就会调用自己的 findClass 方法由自己来对类进行加载,findClass 加载类的地址是自己本 web 应用下的 class。
  1. 加载依然失败,才使用 AppClassLoader 继续加载。
  1. 都没有加载成功的话,抛出异常。

总结一下以上步骤,WebAppClassLoader 加载类的时候,故意打破了JVM 双亲委派机制,绕开了 AppClassLoader,直接先使用 ExtClassLoader 来加载类。

  • 保证了基础类不会被同时加载。
  • 由保证了在同一个 Tomcat 下不同 web 之间的 class 是相互隔离的。