Java的类加载机制双亲委派模型

Java的类加载机制双亲委派模型

文章目录

      • Java的类加载机制双亲委派模型
        • Java的类加载器
          • 启动类加载器(Bootstrap ClassLoader)
          • 拓展类加载器(Extension ClassLoader)
          • 应用程序加载器(Application ClassLoader)
          • 自定义加载器(User ClassLoader)
        • Java类加载的机制
        • 类加载器的源码
          • ClassLoader#loadClass的基本步骤:
        • 简单验证Java的双亲委派机制
        • 线程上下文类加载器
        • 总结

  从Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是类加载器(Bootstrap ClassLoader),该类加载器使用C++语言实现,是虚拟机的一部分;另外一种就是其他所有的的类加载器,独立于虚拟机外部,并且完全继承自抽象类 java.lang.ClassLoader

Java的类加载器

启动类加载器(Bootstrap ClassLoader)

该类负责将存放在[JAVA_HOME]/lib目录中的或者被-Xbootclasspath参数所指定的路径中,并且是虚拟机识别的,意味着及时将你自己的jar放到该目录下也不一定被加载,因为在JVM内已经按照文件名识别,例如rt.jar。

拓展类加载器(Extension ClassLoader)

该加载器负责加载[JAVA_HOME]/lib/ext目录中的或者被java.ext.dirs系统变量指定的路径中的所有类库,该加载器由sun.misc.Launcher$ExtClassLoader实现。

应用程序加载器(Application ClassLoader)

该类加载器负责加载用户类路径(classpath)中指定的类库,开发者可以直接使用这个类加载器,该加载器由sun.misc.Launcher$AppClassLoader实现。

自定义加载器(User ClassLoader)

该类加载器由开发者自己实现,可以进行某些定制功能。

Java类加载的机制

  Java类加载机制遵循的是双亲委派模型,这个模型设计师在JDK1.2期间引入的。该模型的工作过程:如果一个类加载器接收到类加载的请求,它先不会加载,而是将该加载请求交个它的父类加载器去完成,每一层的加载器都如此,知道最顶层的加载器即Bootstrap ClassLoader,只有父类加载都没法完成加载的任务,那么才应该由子加载器去尝试加载。

  双亲委派模型的示意图如下所示:

类加载器的源码

  下面是ClassLoader的类加载器的loadClass实现,即类加载的代码:

//java.lang.ClassLoader#loadClass(java.lang.String, boolean)
protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
    	//<1> 加锁
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            //<2> 查找是否已经被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    //<3>.判断是否有父类加载器
                    if (parent != null) {
                        //<3.1> 有父类加载器,先使用父类加载器进行加载
                        c = parent.loadClass(name, false);
                    } else {
                        //<3.2> 如果没父类加载器,则表明类加载器已经是Bootstrap ClassLoader
                        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();
                    //<4> 如果该类还没有加载,则会调用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;
        }
    }
ClassLoader#loadClass的基本步骤:
  • 1.加锁,防止重复加载
  • 2.查找是否已经被加载过,如果已经被加载过了则直接返回
  • 3.判断是否有父类加载器,如果有则交给父类加载器进行加载,直到Bootstrap ClassLoader
  • 4.如果父类加载器都没有加载成功,则会调用子类加载器的findClass,尝试用字类加载器加载.

  接下来我们来查看一下ClassLoader#findClass(String name),会发现该方法是空实现,直接抛出了异常,如下所示的代码,这是什么原因了;其实在签名提到了,所有类加载器,独立于虚拟机外部,并且完全继承自抽象类java.lang.ClassLoader,所以该findClass(String name)方法由具体的的实现类来实现,也就是如果我们自己定义类加载器,在不打破双亲委派机制的前提下,则只需要重写这个findClass(String name)方法即可。

//java.lang.ClassLoader#findClass
protected Class<?> findClass(String name) throws ClassNotFoundException {
        //直接抛出了ClassNotFoundException异常,显然父类没有找到则会抛出该异常,看回上面的loadClass就可以知道,子类是由try-catch该异常,然后再有子类加载的findClass尝试加载
        throw new ClassNotFoundException(name);
    }

  下面我们看一下ClassLoader的子类sun.misc.Launcher.ExtClassLoader对于findClass(String name)的实现,ExtClassLoader的实现代码在URLClassLoader中,因为ExtClassLoader继承自URLClassLoader

//java.net.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 {
                        //将带包名的全限定的类名,转为文件路径,例如将com.learn.Student转为/com/learn/Student.class
                        String path = name.replace('.', '/').concat(".class");
                        //加载class资源
                        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();
        }
        //如果加载失败,则会抛出ClassNotFoundException
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

简单验证Java的双亲委派机制

  首先编写一个Student和测试的类ClassLoaderDemo,如下所示:

//Student类
public class Student {
    private String name = "Coco";
    private int age = 20;
}
//ClassLoaderDemo类
public class ClassLoaderDemo {
    public static void main(String[] args) {
        Student student = new Student();
        //打印该类的加载器
        System.out.println("class loader:"+student.getClass().getClassLoader());
    }
}

  先直接在IDE工具中直接运行ClassLoaderDemo后,得到下列的结果,类加载器为AppClassLoader
Java的类加载机制双亲委派模型_第1张图片
  根据父类委派机制AppClassLoader的父类加载器有ExtClassLoaderBootstrapClassLoader,因此可以知道ExtClassLoader和BootstrapClassLoader类加载器肯定没有Student类的class文件,按照父类委派模型,如果将Student的class文件放到[JAVA_HOME]/lib/ext目录下,则类加载器应该变为ExtClassLoader。将Student类打包成JAR文件,将它放到对应的ext目录下,下面是打包后的Student类:
Java的类加载机制双亲委派模型_第2张图片
  在IDE工具中再次运行ClassLoaderDemo后,得到下列的结果,类加载器为ExtClassLoader,可见先加载的是ext目录下的Student类,而不是classpath下的:
Java的类加载机制双亲委派模型_第3张图片

线程上下文类加载器

  双亲委派模型并不是一个强制性的约束,而仅仅只是Java设计者推荐开发者的类加载器实现方式。故如果有需要,则可以破坏双亲委派模型,实现自己的类加载器。双亲委派模型,解决了哥哥类加载器的基础类统一的问题,但是如果基础类又要回调用户的代码,也就是基础类的实现类,那要怎么办。为了解决这个问题,Java设计团队,只好引入:线程上下文类加载器(Thread Context ClassLoader)。

  线程上下文类加载器(Context Class Loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。这样基础类,例如ExtClassLoader加载的类中,可以通过Thead.getContextClassLoader()获得比它低层次的类加载器,例如获取到AppClassLoader,则可以将ExtClassLoader加载的接口定义的实现加载进JVM中。典型的实现由JNDI,JDBC,JCE,Slf4j日志框架等。

  如果不做任何的设置,Java 应用的线程的上下文类加载器默认就是系统上下文类加载器,也可以指定线程上下文中的类加载器。

/**
     * Returns the context ClassLoader for this Thread. The context
     * ClassLoader is provided by the creator of the thread for use
     * by code running in this thread when loading classes and resources.
     * If not {@linkplain #setContextClassLoader set}, the default is the
     * ClassLoader context of the parent Thread. The context ClassLoader of the
     * primordial thread is typically set to the class loader used to load the
     * application.
     *
     * 

If a security manager is present, and the invoker's class loader is not * {@code null} and is not the same as or an ancestor of the context class * loader, then this method invokes the security manager's {@link * SecurityManager#checkPermission(java.security.Permission) checkPermission} * method with a {@link RuntimePermission RuntimePermission}{@code * ("getClassLoader")} permission to verify that retrieval of the context * class loader is permitted. * 如果为null,则默认返回的是System ClassLoader * @return the context ClassLoader for this Thread, or {@code null} * indicating the system class loader (or, failing that, the * bootstrap class loader) * * @throws SecurityException * if the current thread cannot get the context ClassLoader * * @since 1.2 */ @CallerSensitive public ClassLoader getContextClassLoader() { if (contextClassLoader == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm != null) { ClassLoader.checkClassLoaderPermission(contextClassLoader, Reflection.getCallerClass()); } return contextClassLoader; }

总结

  类加载器是 Java 语言的一项创新,完成了Java的Class文件加载的过程,通过分层,委托的机制,来确保基础类的Class不被恶意覆盖和修改,解决了哥哥类加载器的基础类的统一问题。

你可能感兴趣的:(Java)