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的继承关系。

    ClassLoader浅析(一) —— Java ClassLoader

    我们来看下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,我们可以从双亲委托中找到蛛丝马迹。

双亲委托

ClassLoader浅析(一) —— Java ClassLoader

​ 类加载器在加载类或者其他资源时,使用的是如上图所示的双亲委派模型,这种模型要求除了顶层的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