深入JVM二:类加载器和双亲委派机制

目录

  • 类加载器的分类
  • Java中各个类加载器的层次关系
  • 自定义类加载器
  • 双亲委派机制
    • 双亲委派流程
    • 双亲委派机制源码剖析
  • 打破双亲委派
    • 打破双亲委派示例
    • Tomcat中的打破双亲委派

在上面了解了类的加载过程,那么类的加载是如何实现的呢?答案是通过类加载器进行加载的,jvm中类加载也是一个普通的类,但是在jvm中针对不同的jar包或者class由不同的类加载器进行加载,jvm中存在多种类加载器,接下来了解jvm中的各个类加载器。

类加载器的分类

Java里有如下几种类加载器

  1. 启动类加载器:负责加载支撑JVM运行的位于JRE的lib目录下的核心类库比如 rt.jar、charsets.jar等。
  2. 扩展类加载器(ExtClassLoader):负责加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR类包。
  3. 应用程序类加载器(AppClassLoader):负责加载ClassPath路径下的类包,主要就是加载你自己写的那些类。
  4. 自定义加载器:负责加载用户自定义路径下的类包。

通过以下实例来了解各个类加载器:

public class ClassLoaderTest {
     

    public static void main(String[] args) {
     
        System.out.println(Object.class.getClassLoader());
        // java提供的与DNS服务交互的api
        System.out.println(DNSNameService.class.getClassLoader());
        System.out.println(ClassLoaderTest.class.getClassLoader());

    }
}

运行结果如下:

null
sun.misc.Launcher$ExtClassLoader@6d6f6e28
sun.misc.Launcher$AppClassLoader@58644d46

启动类加载器是在有jvm底层创建的实例,所以在获取时为null,Object类是有启动类加载器进行加载的,所以获取其加载器时为null,而DNSNameService为JAVA_HOME/jre/lib目录下ext文件夹在的dnsns.jar包中的类,由扩展类加载器(ExtClassLoader)加载。而自己编写的类ClassLoaderTest 则由AppClassLoader进行加载。

Java中各个类加载器的层次关系

在上面已经介绍过,java的类加载器也是普通的类,ExtClassLoader和AppClassLoader均是URLClassLoader的子类,而URL的继承关系如下:
深入JVM二:类加载器和双亲委派机制_第1张图片
那么AppClassLoader和ExtClassLoader为ClassLoader的子类。在上面已经已经介绍过在jvm启动时会通过sun.misc.Launcher的getLauncher方法从而获取Launcher的实例,那么在这个过程中Launcher会通过构造方法创建该类的实例。sun.misc.Launcher的构造方法如下:

public Launcher() {
     
    Launcher.ExtClassLoader var1;
    try {
     
        // 创建ExtClassLoader
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
     
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
     
        // 创建AppClassPoader
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
     
        throw new InternalError("Could not create application class loader", var9);
    }
      // 省略代码 ....
    }
}

通过分析sun.misc.Launcher构造方法我们知道在sun.misc.Launcher类的实例创建是会创建AppClassLoader实例和ExtClassLoader实例。同时由于两个类加载器均继承自ClassLoader,而ClassLoader中有一个ClassLoader的全局变量parent,该类类型也是ClassLoader:

public abstract class ClassLoader {
     

    private static native void registerNatives();
    static {
     
        registerNatives();
    }

    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;
    
    // 省略代码 ....
}

而在sun.misc.Launcher创建时,实例化ExtClassLoader和AppClassLoader时均指定其parent属性分别为null和ExtClassLoader。那么java中的类加载器的机构就如下:
深入JVM二:类加载器和双亲委派机制_第2张图片

自定义类加载器

自定义类加载器需要继承 java.lang.ClassLoader 类,该类有两个核心方法,一个是 loadClass(String, boolean),实现了双亲委派机制,大体逻辑

  1. 首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再 加载, 直接返回。

  2. 如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器, 则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用 Bootstrap类加载器来加载。

  3. 如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类 加载器 的findClass方法来完成类加载。还有一个方法是findClass,默认 实现是抛出异常,所以我们自定义类加载器主要是重写 findClass方法。

接下来看一个示例,首先我们编写需要自定义类加载器加载的类,如下:

package com.dp.jvm;


import java.io.PrintStream;


public class User
{
     
  public void say()
  {
     
    System.out.println("hello");
  }
}

需要注意的是,该类编写完成需要在工程中删除,避免AppClassLoader加载。编译完成后将该类的class文件放置指定的目录下:
深入JVM二:类加载器和双亲委派机制_第3张图片
然后编写自定义的类加载器,代码如下:

class MyClassLoader extends ClassLoader{
     

    private final String path;


    MyClassLoader(String path) {
     
        this.path = path;
    }

    /**
     * 重写ClassLoader的findClass方法,获取到类的Class对象
     * @param name
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
     
        byte[] byteArrayFromClassName = getByteArrayFromClassName(name);
        return defineClass(name, byteArrayFromClassName, 0, byteArrayFromClassName.length);
    }

    /**
     * 通过类的全限定名称获取到类的二进制数据
     * @param name
     * @return
     */
    private byte[] getByteArrayFromClassName(String name) {
     
        String classPath = convertNameToPath(name);
        byte[] data = null;
        int off = 0;
        int length = 0;
        try(BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(classPath))) {
     
            data = new byte[bufferedInputStream.available()];
            while ((length = bufferedInputStream.read(data, off, data.length - off)) > 0) {
     
                off += length;
            }
        } catch (Exception ex) {
     
            ex.printStackTrace();
        }
        return data;
    }

    /**
     * 通过类的全限定名称获取到对应类文件的的字节码文件路径
     * @param name
     * @return
     */
    private String convertNameToPath(String name) {
     
        String relativePath = name.replace(".", File.separator);
        String absolutePath = path + File.separator + relativePath + ".class";
        return absolutePath;
    }
}

编写测试类,通过使用自定义的类加载将User加载并实例化,然后调用其say方法,如下:

public class CustomClassLoaderTest {
     

    public static void main(String[] args) throws Exception {
     
        MyClassLoader myClassLoader = new MyClassLoader("F:\\test");
        Class<?> clazz = myClassLoader.loadClass("com.dp.jvm.User");
        Object o = clazz.newInstance();
        Method say = clazz.getDeclaredMethod("say");
        say.invoke(o);
    }
}

通过上面了实例,简单的实现了一个自定义的类加载器。接留下来了解一下类加载器的双亲委派机制。

双亲委派机制

JVM类加载器是有亲子层级结构的,如下图:
深入JVM二:类加载器和双亲委派机制_第4张图片
需要注意的是,这里的额亲子层级结构不是指的java中的继承关系,而是每一个类加载实现类都具有一个parent全局变量,而该全局变量的类型为ClassLoader。这里可能有一个疑问,在自定义类加载器中并未看到名称parent的全局变量。这是因为这个全局变量是在ClassLoader中定义声明的。

public abstract class ClassLoader {
     


    private static native void registerNatives();
    static {
     
        registerNatives();
    }


    // The parent class loader for delegation
    // Note: VM hardcoded the offset of this field, thus all new fields
    // must be added *after* it.
    private final ClassLoader parent;
    
    // 省略代码 ....

还有一个问题也需要说明一下,我们自定义的类加载器的parent属性是如何设置的呢?怎么知道设置的为AppClassLoader呢?因为自定义的类加载器继承自ClassLoader,而ClassLoader中有一个无参的构造函数,如下:

protected ClassLoader() {
     
   //调用有参构造函数 
    this(checkCreateClassLoader(), getSystemClassLoader());
}

private ClassLoader(Void unused, ClassLoader parent) {
     
    //设置父加载器
    this.parent = parent;
    if (ParallelLoaders.isRegistered(this.getClass())) {
     
        parallelLockMap = new ConcurrentHashMap<>();
        package2certs = new ConcurrentHashMap<>();
        domains =
            Collections.synchronizedSet(new HashSet<ProtectionDomain>());
        assertionLock = new Object();
    } else {
     
        // no finer-grained lock; lock on the classloader instance
        parallelLockMap = null;
        package2certs = new Hashtable<>();
        domains = new HashSet<>();
        assertionLock = this;
    }
}

从ClassLoader的实现来看,通过getSystemClassLoader()方法获取系统类加载器然后将其赋值给parent属性。那么来看一下getSystemClassLoader()具体实现:

public static ClassLoader getSystemClassLoader() {
     
    initSystemClassLoader();
    if (scl == null) {
     
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
     
        checkClassLoaderPermission(scl, Reflection.getCallerClass());
    }
    return scl;
}

// 初始化系统类加载器
private static synchronized void initSystemClassLoader() {
     
    if (!sclSet) {
     
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        if (l != null) {
     
            Throwable oops = null;
            scl = l.getClassLoader();
            try {
     
                scl = AccessController.doPrivileged(
                    new SystemClassLoaderAction(scl));
            } catch (PrivilegedActionException pae) {
     
                oops = pae.getCause();
                if (oops instanceof InvocationTargetException) {
     
                    oops = oops.getCause();
                }
            }
            if (oops != null) {
     
                if (oops instanceof Error) {
     
                    throw (Error) oops;
                } else {
     
                    // wrap the exception
                    throw new Error(oops);
                }
            }
        }
        sclSet = true;
    }
}

在getSystemClassLoader方法中获取sun.misc.Lanucher实例(单例),然后调用其getClassLoader方法获取系统类加载器,然后设置给parent方法。最后来看一下sun.misc.Lanucher的getClassLoader方法:

public ClassLoader getClassLoader() {
     
    return this.loader;
}

public Launcher() {
     
    Launcher.ExtClassLoader var1;
    try {
     
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
     
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
     
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
     
        throw new InternalError("Could not create application class loader", var9);
    }
}

结合sun.misc.Lanucher的getClassLoader和构造方法可知系统类加载器就是AppClassLoader。
在了解了jvm中类加载器的组成结构后,我们再来看一下jvm中各个类加载器的组成的结构:
深入JVM二:类加载器和双亲委派机制_第5张图片
在了解了jvm中各个类加载器的层次结构之后,加下来来解析双亲委派机制就相对来说简单多了,首先从双亲委派的流程说起。

双亲委派流程

双亲委派流程如下:
深入JVM二:类加载器和双亲委派机制_第6张图片
加载某个类时会先委托父加载器寻找目标类,找不到再委托上层父加载器加载,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。
比如我们的PrintTest 类,最先会找应用程序类加载器加载,应用程序类加载器会先委托扩展类加载器加载,扩展类加载器再委托启动类加载器,顶层启动类加载器在自己的类加载路径里找了半天没找到PrintTest 类,则向下退回加载PrintTest 类的请求,扩展类加载器收到回复就自己加载,在自己的类加载路径里找了半天也没找到PrintTest 类,又向下退回PrintTest 类的加载请求给应用程序类加载器,应用程序类加载器于是在自己的类加载路径里找Math类,结果找到了就自己加载了。
双亲委派机制说简单点就是,先找父亲加载,不行再由儿子自己加载。

那么为什么要设置双亲委派机制呢?

  • 沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改。
  • 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性

双亲委派机制源码剖析

双亲委派的原理体现在ClassLoader的loadClass方法中:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
     
    synchronized (getClassLoadingLock(name)) {
     
        // 首先检查类是够已经被加载
        Class<?> c = findLoadedClass(name);
        
        // 类还未加载则加载,使用双亲委派机制
        if (c == null) {
     
            long t0 = System.nanoTime();
            try {
     
                // 判断当前类加载器是否设置了父加载器,设置了则
                // 调用父加载器的loadClass进行加载,如果父加载也是ClassLoader
                // 的子类则会再次进入该方法,判断是否有父类加载器,依次递归
                if (parent != null) {
     
                    c = parent.loadClass(name, false);
                } else {
     
                    // 当类加载没有设置parent父加载,那么就使用启动类加载器加载
                    // 由于启动类加载器是底层创建的实例,所以该方法会调用本地
                    // native方法
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
     
               
            }

            if (c == null) {
     
                // 向上委派所有父加载器仍然没有加载到参数类,那么调用当前
                // 类加载器进行类的加载
                long t1 = System.nanoTime();
                c = findClass(name);
                // ... 省略 
   
            }
        }
        if (resolve) {
     
            resolveClass(c);
        }
        return c;
    }
}

打破双亲委派

打破双亲委派示例

通过了解双亲委派机制,知道了双亲委派机制的逻辑是在ClassLoader中的loadClass方法中实现的,如果要打破双亲委派机制,那么通过重载loadClass方法,不再通过parent父类加载器加载,而是由自己加载。实现打破双亲委派的类加载器如下:

public class BreakParentalDelegaClassLoader extends ClassLoader {
     

    private final String sourcePath;

    public BreakParentalDelegaClassLoader(String sourcePath) {
     
        this.sourcePath = sourcePath;
    }

    /**
     * 功能描述: 通过重载loadClass方法,变更原先的双签委派逻辑,
     * 而是有当前类加载器直接去配置的路径中查找字节码文件并加载
     * @param: [name, resolve]
     * @return: java.lang.Class
     * @auther: binga
     * @date: 2020/8/18 17:47
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
     
        synchronized (getClassLoadingLock(name)) {
     
            // 首先检查类是否加载过
            Class<?> c = findLoadedClass(name);

            /**
             * 接下来不再像ClassLoader中的loadClass的逻辑一样,通过
             * 父类加载器加载,而是通过自己的findClass进行类的查找和加载
             */
            if (c == null) {
     
                long t0 = System.nanoTime();
                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 (c == null) {
     
                throw new ClassNotFoundException();
            }

            if (resolve) {
     
                resolveClass(c);
            }
            return c;
        }
    }

    /**
     * 功能描述: 重载CLassLoader的findClass方法,在该方法中通过指定的path,通过
     *          指定的类的全限定名称转换为路径从而读取class文件,然后通defineClass方法
     *          加载类并返回指定类的Class对象。
     * @param: [name]
     * @return: java.lang.Class
     * @auther: binga
     * @date: 2020/8/18 17:03
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
     
        byte[] byteCode = getByteArrayFromClassName(name);
        return defineClass(name, byteCode, 0, byteCode.length);
    }

    /**
     * 功能描述: 加载class文件返回字节数组
     * @param: [name]
     * @return: byte[]
     * @auther: binga
     * @date: 2020/8/18 17:05
     */
    private byte[] getByteArrayFromClassName(String name) throws ClassNotFoundException {
     
        String classPath = convertNameToPath(name);
        byte[] data = null;

        int off = 0;
        int length;

        try(BufferedInputStream bufferedInputStream =
                    new BufferedInputStream(new FileInputStream(classPath))) {
     
            data = new byte[bufferedInputStream.available()];
            while ((length = bufferedInputStream.read(data, off, data.length - off)) > 0) {
     
                off += length;
            }
        } catch (Exception ex) {
     
            ex.printStackTrace();
            throw new ClassNotFoundException();
        }
        return data;
    }

    /**
     * 功能描述: 类全限定名称转换为文件路径
     * @param: [name]
     * @return: java.lang.String
     * @auther: binga
     * @date: 2020/8/18 17:06
     */
    private String convertNameToPath(String name) {
     
        String classPath = name.replace(".", File.separator);
        classPath = sourcePath + File.separator + classPath + ".class";
        return classPath;
    }
}

仍然沿用User类,测试代码如下:

public class BreakParentalDelegaTest {
     

    public static void main(String[] args) throws Exception {
     
        BreakParentalDelegaClassLoader classLoader = new BreakParentalDelegaClassLoader("F:\\test");
        Class<?> userClass = classLoader.loadClass("com.binga.jvm.classloader.User", false);
        Object user = userClass.newInstance();

        Method say = userClass.getDeclaredMethod("say", null);
        say.invoke(user, null);
    }
}

将User.class方至F:/test/com/binga/jvm/classloader目录下运行测试结果如下:

java.io.FileNotFoundException: F:\test\java\lang\Object.class (系统找不到指定的路径。)
	at java.io.FileInputStream.open(Native Method)
	at java.io.FileInputStream.<init>(FileInputStream.java:131)
	at java.io.FileInputStream.<init>(FileInputStream.java:87)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.getByteArrayFromClassName(BreakParentalDelegaClassLoader.java:90)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.findClass(BreakParentalDelegaClassLoader.java:72)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.loadClass(BreakParentalDelegaClassLoader.java:42)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.findClass(BreakParentalDelegaClassLoader.java:73)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.loadClass(BreakParentalDelegaClassLoader.java:42)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaTest.main(BreakParentalDelegaTest.java:15)
Exception in thread "main" java.lang.NoClassDefFoundError: java/lang/Object
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.findClass(BreakParentalDelegaClassLoader.java:73)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.loadClass(BreakParentalDelegaClassLoader.java:42)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaTest.main(BreakParentalDelegaTest.java:15)
Caused by: java.lang.ClassNotFoundException
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.getByteArrayFromClassName(BreakParentalDelegaClassLoader.java:98)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.findClass(BreakParentalDelegaClassLoader.java:72)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.loadClass(BreakParentalDelegaClassLoader.java:42)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 6 more

可以看到抛出ClassNotFoundException,可能会纳闷,为什么加载的是User类,而抛出的是Object,因为Object是所有类的父类,那么在加载User类时会先加载User类,但是由于我们在自定义加载器中打破了双亲委派机制,所以有当前自定义类加载器加载Object类,但是在F:/test并没有Object的字节码文件,那么将JDK中的Object字节码文件拷贝过来进行测试:
深入JVM二:类加载器和双亲委派机制_第7张图片
再次运行代码如下:

Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
	at java.lang.ClassLoader.preDefineClass(ClassLoader.java:659)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:758)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.findClass(BreakParentalDelegaClassLoader.java:73)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.loadClass(BreakParentalDelegaClassLoader.java:42)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.findClass(BreakParentalDelegaClassLoader.java:73)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaClassLoader.loadClass(BreakParentalDelegaClassLoader.java:42)
	at com.binga.jvm.classloader.brealparentaldelega.BreakParentalDelegaTest.main(BreakParentalDelegaTest.java:15)

可以看到这次不再是Object找不到了,而是禁止的包名称,JVM中对像java.lang等自有的包是禁止的,所以抛出安全异常,那么如何处理呢?解决方法就是通过在重载的loadClass中通过判断,只有自己指定的通过自己加载,如下修改loadClass方法:

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
     
    synchronized (getClassLoadingLock(name)) {
     
        // 不是自己指定的类则使用双亲委派
        if (!"com.binga.jvm.classloader.User".equals(name))
            return super.loadClass(name, false);

        // 首先检查类是否加载过
        Class<?> c = findLoadedClass(name);

        /**
         * 接下来不再像ClassLoader中的loadClass的逻辑一样,通过
         * 父类加载器加载,而是通过自己的findClass进行类的查找和加载
         */
        if (c == null) {
     
            long t0 = System.nanoTime();
            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 (c == null) {
     
            throw new ClassNotFoundException();
        }

        if (resolve) {
     
            resolveClass(c);
        }
        return c;
    }
}

运行结果如下:

hello

Tomcat中的打破双亲委派

Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?
我们思考一下:Tomcat是个web容器, 那么它要解决什么问题:

  1. 一个web容器可能需要部署两个应用程序,不同的应用程序可能会依赖同一 个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份, 因此要保证每个应用程序的类库都是独立的,保证相互隔离。比如:在tomcat容器中存在存在两个应用A和B,A使用的是Spring4,而应用B使用的是Spring5,如果使用双亲委派,那么可能会导致版本冲突从而报错,如果在版本4中不存在x方法,但是先加载了版本4的字节码,那么版本5的就不会在加载了(类限定名相同),那么在程序B中调用x方法则会抛出方法不存在异常。
  2. 部署在同一个web容器中相同的类库相同的版本可以共享。否则,如果服务器有10个应用程序,那么要有10份相同的类库加载进虚拟机。我们常见的web应用中的Servlet依赖,一般在maven中依赖作用于都是provided的,web程序都是使用的容器的Serlvert,如果都是各自的那么造成类的重复加载。
  3. web容器也有自己依赖的类库,不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。
  4. web容器要支持jsp的修改,我们知道,jsp文件最终也是要编译成class文件才能在虚拟机中运行,但程序运行后修改jsp已经是司空见惯的事情,web容器需要支持 jsp修改后不用重启.了解jsp机制的都知道,是将jsp解析成一个对应的Servlet(就是常说的一个jsp就是一个servlet),jsp就是通过动态生成.class文件从而实现动态资源的。

再看看我们的问题:
Tomcat 如果使用默认的双亲委派类加载机制行不行?

  • 第一个问题,如果使用默认的类加载器机制,那么是无法加载两个相同类库的不同版本的,
    默认的类加器是不管你是什么版本的,只在乎你的全限定类名,并且只有一份。
  • 第二个问题,默认的类加载器是能够实现的,因为他的职责就是保证唯一性。
  • 第三个问题和第一个问题一样。
  • 我们再看第四个问题,我们想我们要怎么实现jsp文件的热加载,jsp文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?我们可以直接卸载掉这jsp文件的类加载器,所以你应该想到了,每个jsp文件对应一个唯一的类加载器,当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。
    示例代码

你可能感兴趣的:(jvm,java,jvm)