ClassLoader 深入解析学习笔记(六)

经过前面的实例演示,我们弄清楚了以下几点:
java 类加载分为
1、启动类加载器,也叫根类加载器(BootStrapClassLoader),负责加载java核心api,如rt.jar等。需要清除的是它不是java.lang.ClassLoader的子类,它是JVM自身内部由C/C++实现的,并不是Java实现的。

2、扩展类加载器(Launcher$ExtClassLoader),负责加载扩展目录(%JAVA_HOME%/jre/lib/ext)下的jar包,我们可以自己打包放到此目录下,扩展java核心api以外的功能。ExtClassLoader的父类加载器是启动类加载器,特别注意的是它与启动类加载器并不是子类与父类的关系。

3、系统类加载器或称为应用程序类加载器(Launcher$AppClassLoader),是加载CLASSPATH环境变量所指定的jar包与类路径。一般来说,我们自定义的类就是由APP ClassLoader加载的。同样它的父加载器是扩展类加载器,但并不是扩展类加载器的子类。
4、用户自定义加载器,用于加载上面三种路径以外的jar或者class。父类加载器为应用类加载。

java 类加载机制
双亲委托加载机制,即当一个类加载收到加载类请求时,它不会马上去加载类,而是先委托自己的父类加载器去加载,一致委托到启动类加载,然后开始从启动类加载器去加载这个类,如果启动类加载器加载失败,再反馈给子类加载器,直到加载完成,如果回到类加载发起的加载器时还未加载成功,则抛出classNotFound异常。
双亲委托的作用:
1、避免重复加载,如果父类加载器加载了该类,子类便不会再去加载。
2、安全,避免用户修改java核心api,如String类。

现在已经知其然,当然就要知其所以然,那就得看源码。在之前的实例中,我们获取加载器都是通过Class的getClassLoader()方法,class主要源码如下:

  /**
     * Returns the class loader for the class.  Some implementations may use
     * null to represent the bootstrap class loader. This method will return
     * null in such implementations if this class was loaded by the bootstrap
     * class loader.
     *
     * <p> If a security manager is present, and the caller's class loader is
     * not null and the caller's class loader is not the same as or an ancestor of
     * the class loader for the class whose class loader is requested, then
     * this method calls the security manager's {@code checkPermission}
     * method with a {@code RuntimePermission("getClassLoader")}
     * permission to ensure it's ok to access the class loader for the class.
     *
     * <p>If this object
     * represents a primitive type or void, null is returned.
     *
     * @return  the class loader that loaded the class or interface
     *          represented by this object.
     * @throws SecurityException
     *    if a security manager exists and its
     *    {@code checkPermission} method denies
     *    access to the class loader for the class.
     * @see java.lang.ClassLoader
     * @see SecurityManager#checkPermission
     * @see java.lang.RuntimePermission
     */
    @CallerSensitive
    public ClassLoader getClassLoader() {
        ClassLoader cl = getClassLoader0();
        if (cl == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
        }
        return cl;
    }

    // Package-private to allow ClassLoader access
    ClassLoader getClassLoader0() { return classLoader; }

    // Initialized in JVM not by private constructor
    // This field is filtered from reflection access, i.e. getDeclaredField
    // will throw NoSuchFieldException
    private final ClassLoader classLoader;

我们可以很清楚的看到,在getClassLoader方法的注释是这样说的:
返回这个类的加载器,有些实现可能会用null来代表启动类加载器(BootStrap ClassLoader)。如果这个类是由启动类加载器(BootStrap ClassLoader)加载,则这个方法将返回null。
其次我们根据代码可以看出,类的加载器是通过 不可变变量 classLoader 绑定,注释中说它是由JVM初始化,并不是有class的构造器初始化的,而且它也被反射方法getDeclaredField过滤掉了,getDeclaredField方法不能访问到它。所以这里ClassLoader的初始化,我们这里暂不做讨论(主要是自己太菜,不会玩JVM)。

我们再来看ClassLoader类本身,附上部分代码

public abstract class ClassLoader {
/**
*Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method.  </p>
*ClassLoader的子类建议重写findClass方法,而不是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) {//如果父类加载器不是启动类加载器
                        c = parent.loadClass(name, false);//委托给父类加载器加载
                    } else {
                             c = findBootstrapClassOrNull(name);//父类加载器是启动类加载,委托给启动类加载器加载,                                          
                                                              //启动类加载器没有父类加载器了。
                    }
                } catch (ClassNotFoundException e) {
                   //如果在非启动类的父类加载器中没有找到,抛出异常
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    //如果仍就没有找到该类,再执行findClass 方法去加载该类
                    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);//解析类,属于类加载的link阶段,类的加载过程必须link
            }
            return c;
        }
    }
    //可知在这里,findClass方法直接抛出异常,方法的实现需要ClassLoader的子类去实现。
  protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
}

通过上面的代码,我们也就明白自定义加载器中只重写了findClass方法。
因为启动类加载器是再jvm中由C++实现,我们能看源码的就只有扩展类加载器以及系统类加载器,其实细心的同学应该在
ClassLoader 深入解析学习笔记(一)中的第一个实例,控制台输入的结果中看到,ExtClassLoader 以及AppClassLoader都是Launcher的内部类。

sun.misc.Launcher$AppClassLoader@6d06d69c
sun.misc.Launcher$ExtClassLoader@2503dbd3
null

所以我们来看Launcher的源码:


public class Launcher {
    private ClassLoader loader;
public Launcher() {
      //创建扩展类加载器
        ClassLoader extcl;
        try {
            extcl = ExtClassLoader.getExtClassLoader();
        } catch (IOException e) {
            throw new InternalError(
                "Could not create extension class loader", e);
        }

        // 创建应用类加载器即系统类加载器
        try {
            loader = AppClassLoader.getAppClassLoader(extcl);//把扩展类加载器作为AppClassLoader的父类加载器
        } catch (IOException e) {
            throw new InternalError(
                "Could not create application class loader", e);
        }

        // 为原始线程创建上下文类加载器,这里系统默认的上下文加载器就是系统类加载器。
        Thread.currentThread().setContextClassLoader(loader);

        // Finally, install a security manager if requested
        String s = System.getProperty("java.security.manager");
        if (s != null) {
            SecurityManager sm = null;
            if ("".equals(s) || "default".equals(s)) {
                sm = new java.lang.SecurityManager();
            } else {
                try {
                    sm = (SecurityManager)loader.loadClass(s).newInstance();
                } catch (IllegalAccessException e) {
                } catch (InstantiationException e) {
                } catch (ClassNotFoundException e) {
                } catch (ClassCastException e) {
                }
            }
            if (sm != null) {
                System.setSecurityManager(sm);
            } else {
                throw new InternalError(
                    "Could not create SecurityManager: " + s);
            }
        }
    }
 /*
     * 用于加载扩展类
     */
    static class ExtClassLoader extends URLClassLoader {
 public static ExtClassLoader getExtClassLoader() throws IOException
        {
            final File[] dirs = getExtDirs();

            try {
                // Prior implementations of this doPrivileged() block supplied
                // aa synthesized ACC via a call to the private method
                // ExtClassLoader.getContext().

                return AccessController.doPrivileged(
                    new PrivilegedExceptionAction<ExtClassLoader>() {
                        public ExtClassLoader 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() {
//扩展类加载 加载文件路径,系统配置文件中java.ext.dirs
            String s = System.getProperty("java.ext.dirs");
//运行System.out.println(System.getProperty("java.ext.dirs"));
//输出结果 C:\Program Files\Java\jdk1.8.0_171\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
            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;
        }

.........
//应用类加载器
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
            throws IOException
        {
            final String s = System.getProperty("java.class.path");
            //打印s 得到 
            //D:\workspace\classLoaderSourceCodeStudy\bin;D:\Mye2017\plugins\org.junit_4.12.0.v201504281640\junit.jar;            
            //d:\Mye2017\plugins\org.hamcrest.core_1.3.0.v201303031735.jar;         
            //D:/Mye2017/configuration/org.eclipse.osgi/852/0/.cp/;/D:/Mye2017/configuration/org.eclipse.osgi/851/0/.cp/
//其实就是当前工程的目录,里面存放在class文件。
final File[] path = (s == null) ? new File[0] : getClassPath(s);

            // Note: on bugid 4256530
            // Prior implementations of this doPrivileged() block supplied
            // a rather restrictive ACC via a call to the private method
            // AppClassLoader.getContext(). This proved overly restrictive
            // when loading  classes. Specifically it prevent
            // accessClassInPackage.sun.* grants from being honored.
            //
            return AccessController.doPrivileged(
                new PrivilegedAction<AppClassLoader>() {
                    public AppClassLoader run() {
                    URL[] urls =
                        (s == null) ? new URL[0] : pathToURLs(path);
                    return new AppClassLoader(urls, extcl);
                }
            });
        }

}
}

即Launcher 构造器中先给扩展器加载初始化,然后再扩展加载器作为应用类加载器的父类加载器并初始化。最后再设置上下文加载器,每个线程都有一个ContextClassLoader。
通过源码我们知道了AppClassLoder以及ExtClassloader 都是Launcher的内部类,都继承自URLClassLoader,还有就是这里多出了个上下文加载器,不是说加载器只有三种吗?答案是,我们去看Thread源码发现,其实是ClassLoader作为Thread的一个成员变量,并不是新的加载器,而是一种概念,其默认值为AppClassLoader(Launcher源码中初始化时,用AppClassLoader传值)。
最后附上看源码网址,真心不错
http://www.grepcode.com/
Launcher的源码就是在里面看到的,我也是找了许久才找到。

猜你喜欢

转载自blog.csdn.net/whandgdh/article/details/80380252