• classloader加载class文件的原理和机制


    Classloader的两个任务:

    1、Classloader负责将Class加载到JVM中,并且确定由那个ClassLoader来加载(父优先的等级加载机制)。

    2、还有一个任务就是将Class字节码重新解释为JVM统一要求的格式

    Classloader的分类:

        Java类加载器采用双亲委派模型:

    1. BootStrapClassLoader:启动类加载器,该ClassLoader是jvm在启动时创建的,用于加载 $JAVA_HOME/jre/lib下面的类库(或者通过参数-Xbootclasspath指定)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不能直接通过引用进行操作。

    2. ExtClassLoader:扩展类加载器,该ClassLoader是在sun.misc.Launcher里作为一个内部类ExtClassLoader定义的(即 sun.misc.Launcher$ExtClassLoader),ExtClassLoader会加载 $JAVA_HOME/jre/lib/ext下的类库(或者通过参数-Djava.ext.dirs指定)。

    3. AppClassLoader:应用程序类加载器,该ClassLoader同样是在sun.misc.Launcher里作为一个内部类AppClassLoader定义的(即 sun.misc.Launcher$AppClassLoader),AppClassLoader会加载java环境变量CLASSPATH所指定的路径下的类库,而CLASSPATH所指定的路径可以通过System.getProperty("java.class.path")获取;当然,该变量也可以覆盖,可以使用参数-cp,例如:java -cp 路径 (可以指定要执行的class目录)。

    4. CustomClassLoader:自定义类加载器,该ClassLoader是指我们自定义的ClassLoader,比如tomcat的StandardClassLoader属于这一类;当然,大部分情况下使用AppClassLoader就足够了。

    ClassLoader初始化源码

    上述四种类加载器中CustomClassLoader是用户自定义的,BootStrapClassLoader是jvm创建的,就不展示了;这里展示下AppClassLoader和ExtClassLoader的启动过程

    public Launcher(){
        ExtClassLoader extclassloader;
        try{
            extclassloader = ExtClassLoader.getExtClassLoader();
        }
        catch(IOException ioexception) {
            throw new InternalError("Could not create extension class loader");
        }
        try{
            loader = AppClassLoader.getAppClassLoader(extclassloader);
        }
        catch(IOException ioexception1){
            throw new InternalError("Could not create application class loader");
        }
        Thread.currentThread().setContextClassLoader(loader);
        String s = System.getProperty("java.security.manager");
        if(s != null){
            SecurityManager securitymanager = null;
            if("".equals(s) || "default".equals(s))
            securitymanager = new SecurityManager();
            else
            try{
                securitymanager = (SecurityManager)loader.loadClass(s).newInstance();
            }
            catch(IllegalAccessException illegalaccessexception) { }
            catch(InstantiationException instantiationexception) { }
            catch(ClassNotFoundException classnotfoundexception) { }
            catch(ClassCastException classcastexception) { }
            if(securitymanager != null)
              System.setSecurityManager(securitymanager);
            else
              throw new InternalError((new StringBuilder()).append("Could not create SecurityManager: ").append(s).toString());
        }
    }

    可以看到在Launcher构造函数的执行过程如下:

    1. 通过ExtClassLoader.getExtClassLoader()创建了ExtClassLoader;

    2. 通过AppClassLoader.getAppClassLoader(ExtClassLoader)创建了AppClassLoader,并将ExtClassLoader设为AppClassLoader的parent ClassLoader;

    3. 通过Thread.currentThread().setContextClassLoader(loader)把AppClassLoader设为线程的上下文 ClassLoader;

    4. 根据jvm参数-Djava.security.manager创建安全管理器(安全管理器的相关内容会在后续博客安全管理器及Java API中介绍),此时jvm会设置系统属性"java.security.manager"为空字符串""。

    再贴下ExtClassLoader源码: 

    static class ExtClassLoader extends URLClassLoader {
        private File[] dirs;
    
        public static ExtClassLoader getExtClassLoader() throws IOException {
            // 用调getExtDirs()方法取获配置的扩展类路径
            final File[] dirs = getExtDirs();
            try {
                // 应用getExtDirs()方法返回的路径生成一个新的ClassLoader实例
                return (ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction() {
                    public Object run() throws IOException {
                        int len = dirs.length;
                        for (int i = 0; i < len; i++) {
                            MetaIndex.registerDirectory(dirs[i]);
                        }
                        return new ExtClassLoader(dirs);
                    }
                });
            } catch (java.security.PrivilegedActionException e) {
                throw (IOException) e.getException();
            }
        }
    
    
        // 再看这个方法
        private static File[] getExtDirs() {
            // 取获配置的扩展类路径
            String s = System.getProperty("java.ext.dirs");
            File[] dirs;
            if (s != null) {
                StringTokenizer st = new StringTokenizer(s, File.pathSeparator);
                int count = st.countTokens();
                dirs = new File[count];
                for (int i = 0; i < count; i++) {
                    dirs[i] = new File(st.nextToken());
                }
            } else {
                dirs = new File[0];
            }
            return dirs;
        }
    
        // 其他码代略
        ...
    }

    反编译的源码,大家将就看下;这里大家关注下getExtDirs()这个方法,它会获取属性"java.ext.dirs"所对应的值,然后通过系统分隔符分割,然后加载分割后的字符串对应的目录作为ClassLoader的类加载库。

    下面看看AppClassLoader源码:

    public static ClassLoader getAppClassLoader(ClassLoader classloader) throws IOException{
        String s = System.getProperty("java.class.path");
        File afile[] = s != null ? Launcher.getClassPath(s) : new File[0];
        return (AppClassLoader)AccessController.doPrivileged(new PrivilegedAction(s, afile, classloader) {
        public Object run() {
            URL aurl[] = s != null ? Launcher.pathToURLs(path) : new URL[0];
            return new AppClassLoader(aurl, extcl);
        }
    
        final String val$s;
        final File val$path[];
        final ClassLoader val$extcl;
    
        {
            s = s1;
            path = afile;
            extcl = classloader;
            super();
        }
        });
    }

    首先获取"java.class.path"对应的属性,并转换为URL[]并设置为ClassLoader的类加载库,注意这里的方法入参classloader就是ExtClassLoader,在创AppClassLoader会传入ExtClassLoader作为parent ClassLoader。

    上面就是ClassLoader的启动和初始化过程,后面会把loader作为应用程序的默认ClassLoader使用,看下面的测试用例:

    public static void main(String... args) {
        ClassLoader loader = Test.class.getClassLoader();
        System.err.println(loader);
        while (loader != null) {
            loader = loader.getParent();
            System.err.println(loader);
        }
    }

    可以看到ClassLoader的层次结构,输出结果为:

    ClassLoader双亲委派机制源码

    前面谈到了ClassLoader的几类加载器,而ClassLoader使用双亲委派机制来加载class文件的。

    ClassLoader的双亲委派机制是这样的(这里先忽略掉自定义类加载器CustomClassLoader):

    1. 当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

    2. 当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

    3. 如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

    4. 若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

    下面贴下ClassLoader的loadClass(String name, boolean resolve)源码:

    protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // First, check if the class has already been loaded
        Class c = findLoadedClass(name);
        if (c == null) {
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    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.
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }

    代码很明朗:首先找缓存(findLoadedClass),没有的话就判断有没有parent,有的话就用parent来递归的loadClass,然而ExtClassLoader并没有设置parent,则会通过findBootstrapClassOrNull来加载class,而findBootstrapClassOrNull则会通过JNI方法”private native Class findBootstrapClass(String name)“来使用BootStrapClassLoader来加载class。

    然后如果parent未找到class,则会调用findClass来加载class,findClass是一个protected的空方法,可以覆盖它以便自定义class加载过程。

    另外,虽然ClassLoader加载类是使用loadClass方法,但是鼓励用 ClassLoader 的子类重写 findClass(String),而不是重写loadClass,这样就不会覆盖了类加载默认的双亲委派机制。

    双亲委派机制为什么安全

    前面谈到双亲委派机制是为了安全而设计的,但是为什么就安全了呢?举个例子,ClassLoader加载的class文件来源很多,比如编译器编译生成的class、或者网络下载的字节码。而一些来源的class文件是不可靠的,比如我可以自定义一个java.lang.Integer类来覆盖jdk中默认的Integer类,例如下面这样:

    package java.lang;
    
    /**
     * hack
     */
    public class Integer {
        public Integer(int value) {
            System.exit(0);
        }
    }

    初始化这个Integer的构造器是会退出JVM,破坏应用程序的正常进行,如果使用双亲委派机制的话该Integer类永远不会被调用,以为委托BootStrapClassLoader加载后会加载JDK中的Integer类而不会加载自定义的这个,可以看下下面这测试个用例:

    public static void main(String... args) {
         Integer i = new Integer(1);
         System.err.println(i);
    }

    执行时JVM并未在new Integer(1)时退出,说明未使用自定义的Integer,于是就保证了安全性。

  • 相关阅读:
    @从零开始实现一个插件化框架(一)
    @从零开始实现一个插件化框架(二)
    @从零开始实现一个插件化框架(三)
    @CoordinatorLayout使用详解: 打造折叠悬浮效果
    Oracle 11g数据库详细安装过程
    web service 的跨语言特性
    struts2--值栈
    事务处理中如何获取同一个connection 对象
    jsp中文乱码问题
    设置工作集
  • 原文地址:https://www.cnblogs.com/aitree/p/14499525.html
Copyright © 2020-2023  润新知