ClassLoader浅析(一) —— Java ClassLoader

  • 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 ClassLoader)