美文网首页
ClassLoader浅析(一) —— Java ClassL

ClassLoader浅析(一) —— Java ClassL

作者: 红Bean | 来源:发表于2018-12-06 22:20 被阅读56次
    • ClassLoader的具体作用就是将字节码格式文件加载到虚拟机中去。Java中是把class文件加载到JVM。Android中是把dex/odex文件加载入虚拟机。
    • 当JVM启动的时候,不会一下子把所有的class文件加载进JVM,而是根据需要去动态加载。

    JAVA类加载

    • 在Java中有三个类加载器
      1. Bootstrap ClassLoader:启动类加载器,最顶层的加载类。负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。他是由C++实现的,并不继承自 java.lang.ClassLoader
      2. Extention ClassLoader:扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。
      3. Application ClassLoader:应用类加载器,负责加载应用程序classpath目录下的所有jar和class文件。一般来说,Java 应用的类都是由它来完成加载的。

    父加载器

    • 父加载器不是父类。 先看下AppClassLoader和ExtClassLoader的继承关系。

      image

      我们来看下ClassLoader的源码:

      public abstract class ClassLoader {
        //父加载器
        private final ClassLoader parent;
      
        private static ClassLoader scl;
      
        private ClassLoader(Void unused, ClassLoader parent) {
            this.parent = parent;
            ...
        }
          
        protected ClassLoader(ClassLoader parent) {
            this(checkCreateClassLoader(), parent);
        }
          
        protected ClassLoader() {
            this(checkCreateClassLoader(), getSystemClassLoader());
        }
          
        public final ClassLoader getParent() {
            if (parent == null)
                    return null;
            return parent;
        }
          
        public static ClassLoader getSystemClassLoader() {
            initSystemClassLoader();
            if (scl == null) {
                return null;
            }
            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;
                    //通过Launcher获取ClassLoader
                    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 {
                            throw new Error(oops);
                        }
                    }
                }
                sclSet = true;
            }
        }
          ...
      }
      

      再来看看sun.misc.Launcher,它是一个java虚拟机的入口:

      public class Launcher {
          
          private static Launcher launcher = new Launcher();
          
          private static String bootClassPath = System.getProperty("sun.boot.class.path");
      
          public static Launcher getLauncher() {
              return launcher;
          }
      
          private ClassLoader loader;
      
          public Launcher() {
              ClassLoader extcl;
              try {
                  //初始化ExtClassLoader
                  extcl = ExtClassLoader.getExtClassLoader();
              } catch (IOException e) {
                  throw new InternalError(
                      "Could not create extension class loader", e);
              }
              try {
                  //初始化AppClassLoader
                  loader = AppClassLoader.getAppClassLoader(extcl);
              } catch (IOException e) {
                  throw new InternalError(
                      "Could not create application class loader", e);
              }
              //设置AppClassLoader为线程上下文类加载器
              Thread.currentThread().setContextClassLoader(loader);
          }
          
          public ClassLoader getClassLoader() {
              return loader;
          }
         
          static class ExtClassLoader extends URLClassLoader {
            private File[] dirs;
      
              public static ExtClassLoader getExtClassLoader() throws IOException
              {
                  final File[] dirs = getExtDirs();
                  return new ExtClassLoader(dirs);
              }
      
              public ExtClassLoader(File[] dirs) throws IOException {
                  super(getExtURLs(dirs), null, factory);
                  this.dirs = dirs;
              }
              ...
          }
      
          static class AppClassLoader extends URLClassLoader {
              public static ClassLoader getAppClassLoader(final ClassLoader extcl)
                  throws IOException{
                  final String s = System.getProperty("java.class.path");
                  final File[] path = (s == null) ? new File[0] : getClassPath(s);
                  URL[] urls = (s == null) ? new URL[0] : pathToURLs(path);
                  return new AppClassLoader(urls, extcl);
              }
      
              AppClassLoader(URL[] urls, ClassLoader parent) {
                  super(urls, parent, factory);
              }
              ...                                                
          }
      }
      

      从以上的源码中我们可以知道parent的赋值是在ClassLoader对象的构造方法中,它有两个情况:

      1. 由外部类创建ClassLoader时直接传入一个ClassLoader为parent。

      2. 外界不指定parent时,由getSystemClassLoader()方法生成,也就是在sun.misc.Laucher通过getClassLoader()获取,也就是AppClassLoader。直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。

    loader = AppClassLoader.getAppClassLoader(extcl);说明AppClassLoader的parent是ExtClassLoader。

    但是ExtClassLoader并没有直接对parent赋值。它调用了它的父类也就是URLClassLoder的构造方法并传递了3个参数。

    public  URLClassLoader(URL[] urls, ClassLoader parent,URLStreamHandlerFactory factory) {
         super(parent);
    }
    

    真相大白,ExtClassLoader的parent为null。但是实际上ExtClassLoader父类加载器是BootstrapClassLoader,我们可以从双亲委托中找到蛛丝马迹。

    双亲委托

    java 双亲委派.png

    ​ 类加载器在加载类或者其他资源时,使用的是如上图所示的双亲委派模型,这种模型要求除了顶层的BootStrap 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 {
                    if (parent != null) {
                         //父加载器不为空则调用父加载器的loadClass
                        c = parent.loadClass(name,false);
                    } else {
                         //父加载器为空则调用Bootstrap Classloader
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }
    
                if (c == null) {
                    // 如果父类加载器无法加载,才尝试加载
                    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 (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    

    ​ 在双亲委托把类加载事件一直往上传递,一直传到ExtClassLoader,由于ExtClassLoader中的parent为null而传给BootStrapClassLoader。所以说ExtClassLoader的父加载器为BootStrapClassLoader。之所以ExtClassLoader不持有BootStrapClassLoader的引用,是因为Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代d码中获取它的引用。

    • 优点:通过双亲委托可以避免重复加载和保证安全性。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。如果我们自定义一个String来动态替换java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

    参考

    深入分析Java ClassLoader原理

    一看你就懂,超详细java中的ClassLoader详解

    深入理解JVM之ClassLoader

    相关文章

      网友评论

          本文标题:ClassLoader浅析(一) —— Java ClassL

          本文链接:https://www.haomeiwen.com/subject/cqiacqtx.html