类加载器的理解——基于Launcher类

Posted 默辨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类加载器的理解——基于Launcher类相关的知识,希望对你有一定的参考价值。



阅读本文你将收获:

1、应用类加载器的父加载器为什么是扩展类加载器。

2、扩展类加载器的父加载器为什么是null。

3、双亲委派机制的向上委派过程

一、理论入口

类加载器分类:

  • 引导类加载器:加载lib目录下的核心类库,如rt.jar、charsets.jar等
  • 扩展类加载器:加载lib目录下ext扩展目录的jira包
  • 应用类加载器:自己编写的类的加载器
  • 自定义类加载器:自己实现ClassLoader类,并重写对应的findClass方法



测试代码:

public class Mobian {
	public static void main(String[] args) {
		System.out.println(String.class.getClassLoader());
		System.out.println(SunEC.class.getClassLoader());
		System.out.println(Mobian.class.getClassLoader());
		System.out.println("=======");
		System.out.println(Mobian.class.getClassLoader());
		System.out.println(Mobian.class.getClassLoader().getParent());
		System.out.println(Mobian.class.getClassLoader().getParent().getParent());
	}
}




由于引导类加载器是C++编写,所以Java获取时只能为null

根据打印结果,我们不难发现,这两个类加载器(App、Ext)是Launcher的内部类。

记住它们都实现了URLClassLoader,这对你调试代码时理清逻辑十分重要





二、创建加载器

加载器实现的核心类为Launcher类,接下来展示该类中实例化加载器的具体顺序

记住一定要是sun.misc包路径下的Launcher类

// 默认的无参构造方法
public Launcher() {
    Launcher.ExtClassLoader var1;
    try {
        // 一、获取扩展类加载器
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
        // 二、根据扩展类加载器获取应用加载器
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
    ...
}



1、扩展类加载器(Ext)

static class ExtClassLoader extends URLClassLoader {
    private static volatile Launcher.ExtClassLoader instance;

    // 这里是一个经典的双重检测锁单例模式
    // 1、前面获取扩展类加载器的入口
    public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
        if (instance == null) {
            Class var0 = Launcher.ExtClassLoader.class;
            synchronized(Launcher.ExtClassLoader.class) {
                if (instance == null) {
                    
                    // 2、由于使用单例模式,则可以确定扩展类加载器只会存在一个
                    instance = createExtClassLoader();
                }
            }
        }
        return instance;
    }

    private static Launcher.ExtClassLoader createExtClassLoader() throws IOException {
        try {
            return (Launcher.ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
                public Launcher.ExtClassLoader run() throws IOException {
                    
                    // 3、获取扩展类加载器的目录
                    // 其本质就是处理改目录下的jar包,System.getProperty("java.ext.dirs");
                    File[] var1 = Launcher.ExtClassLoader.getExtDirs();
                    int var2 = var1.length;

                    for(int var3 = 0; var3 < var2; ++var3) {
                        MetaIndex.registerDirectory(var1[var3]);
                    }

                    // 4、通过扩展类加载器的目录转化的数组,创建我们的扩展类加载器
                    return new Launcher.ExtClassLoader(var1);
                }
            });
        } catch (PrivilegedActionException var1) {
            throw (IOException)var1.getException();
        }
    }
    ...
}
// 4.1 第二个参数为null,该参数则表示扩展类加载器的父加载器为null
public ExtClassLoader(File[] var1) throws IOException {
    super(getExtURLs(var1), (ClassLoader)null, Launcher.factory);
    SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}

// Creates a new class loader using the specified parent class loader for delegation.
// 1)上面的super方法最终会调用到这里,
protected ClassLoader(ClassLoader parent) {
    this(checkCreateClassLoader(), parent);
}

// this指向下面的方法
// 2)完成该加载器的父加载器赋值,并完成其他附属操作,parent属性为null
private ClassLoader(Void unused, ClassLoader parent) {
    this.parent = parent;
    if (ParallelLoaders.isRegistered(this.getClass())) {
        parallelLockMap = new ConcurrentHashMap<>();
        package2certs = new ConcurrentHashMap<>();
        assertionLock = new Object();
    } else {
        // no finer-grained lock; lock on the classloader instance
        parallelLockMap = null;
        package2certs = new Hashtable<>();
        assertionLock = this;
    }
}



2、应用类加载器(App)

static class AppClassLoader extends URLClassLoader {
    final URLClassPath ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);

    // 1、创建应用加载器方法调用入口
    public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
        
        // 2、获取系统配置的参数变量,同上面的System.getProperty("java.ext.dirs")操作
        final String var1 = System.getProperty("java.class.path");
        
        // 3、将获取的字符串转化为File的数组
        final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
        return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction<Launcher.AppClassLoader>() {
            public Launcher.AppClassLoader run() {
                URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
                
                // 4、返回一个应用类加载器
                return new Launcher.AppClassLoader(var1x, var0);
            }
        });
    }

    AppClassLoader(URL[] var1, ClassLoader var2) {
        
        //5、调用对应的super方法,完成创建
        super(var1, var2, Launcher.factory);
        this.ucp.initLookupCache(this);
    }
    ...
}
// 5.1 调用父类的构造方法
public URLClassLoader(URL[] urls, ClassLoader parent,
                      URLStreamHandlerFactory factory) {
    // 调用ClassLoader的构造方法
    super(parent);
    // this is to make the stack depth consistent with 1.1
    SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkCreateClassLoader();
    }
    acc = AccessController.getContext();
    ucp = new URLClassPath(urls, factory, acc);
}

// 最终最终,它会和上面创建扩展类加载器走到同一段逻辑
// 1)不同的时,此时的parent是扩展类加载器(创建扩展类加载器的时候,parent参数为null)
protected ClassLoader(ClassLoader parent) {
    this(checkCreateClassLoader(), parent);
}

// parent参数完成赋值,即表示应用类加载器的父加载器是扩展类加载器
private ClassLoader(Void unused, ClassLoader parent) {
    // 2)完成父加载器属性的赋值
    this.parent = parent;
    if (ParallelLoaders.isRegistered(this.getClass())) {
        parallelLockMap = new ConcurrentHashMap<>();
        package2certs = new ConcurrentHashMap<>();
        assertionLock = new Object();
    } else {
        // no finer-grained lock; lock on the classloader instance
        parallelLockMap = null;
        package2certs = new Hashtable<>();
        assertionLock = this;
    }
}



3、补充

public class TestSysProperty {
	public static void main(String[] args) {
        
        // 引导类加载器时扫描的jar包位置
        System.out.println("boot加载器:");
		URL[] urls = Launcher.getBootstrapClassPath().getURLs();
		for (int i = 0; i < urls.length; i++) {
			System.out.println(urls[i]);
        }
        
		// 扩展类加载器扫描的jar包
		System.out.println("ext加载器:"+System.getProperty("java.ext.dirs"));

		// 应用类加载器扫描的jar包
		System.out.println("app加载器"+System.getProperty("java.class.path"));
	}
}

boot加载器:
file:/E:/Environment/java/jdk8/jre/lib/resources.jar
file:/E:/Environment/java/jdk8/jre/lib/rt.jar
file:/E:/Environment/java/jdk8/jre/lib/sunrsasign.jar
file:/E:/Environment/java/jdk8/jre/lib/jsse.jar
file:/E:/Environment/java/jdk8/jre/lib/jce.jar
file:/E:/Environment/java/jdk8/jre/lib/charsets.jar
file:/E:/Environment/java/jdk8/jre/lib/jfr.jar
file:/E:/Environment/java/jdk8/jre/classes

ext加载器:E:\\Environment\\java\\jdk8\\jre\\lib\\ext;C:\\WINDOWS\\Sun\\Java\\lib\\ext

// 尽管这里获取了很多jar,但是只有在我们项目的target目录下的才有用
app加载器E:\\Environment\\java\\jdk8\\jre\\lib\\charsets.jar;E:\\Environment\\java\\jdk8\\jre\\lib...





三、双亲委派机制

双亲委派机制这个词已经见怪不怪了,当程序加载一个类的加载器会先去它的父加载器中加载…,此处不谈概念,只有代码逻辑

以AppClassLoader类(Launcher类的内部类)的loadClass方法为起点

public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
    ...
    if (this.ucp.knownToNotExist(var1)) {
        Class var5 = this.findLoadedClass(var1);
        if (var5 != null) {
            if (var2) {
                this.resolveClass(var5);
            }

            return var5;
        } else {
            throw new ClassNotFoundException(var1);
        }
    } else {
        // 调用父类的loadClass方法,获取类加载器
        return super.loadClass(var1, var2);
    }
}
// AppClassLoader继承了URLClassLoader,所以调用到父类的loadClass方法
// URLClassLoader又调用了它的父类的父类的loadClass方法
// SecureClassLoader继承了ClassLoader类,但是没有重写loadClass方法
super.loadClass(var1, var2);
// 最终它调用的是ClassLoader类的这个方法loadClass
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        // 如果存在,直接返回
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 父加载器为null,就表示是引导类加载器
                // 不为null,就找该加载器的父加载器
                if (parent != null) {
                    // 此时的调用parent的loadClass方法表示
                    c = parent.loadClass(name, false);
                } else {
                    // parent == null,则表示该类加载器是扩展类加载器,则类去引导类加载器中加载
                    // 底层调用native Class<?> findBootstrapClass 如果引导类加载器中没有找到,则直接返回null
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            ...
        }
        ...
        return c;
    }
}



上面这个方法重点为c = parent.loadClass(name, false)方法。回顾我们上面创建加载器时的parent参数,它表示我们的加载器,紧接上面的示例,AppClassLoader的parent参数表示它的父加载器,即ExtClassLoader,那么这就回到了我们该节示例最开始的位置(我们是从AppClassLoader的loadClass方法开始的),刚好一圈。下一圈就是走ExtClassLoader的loadClass方法,然后一层一层,此时ExtClassLoader的parent参数为null,那么它就会调用c = findBootstrapClassOrNull(name)方法

以上是关于类加载器的理解——基于Launcher类的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Java虚拟机——类加载器

深入理解Java虚拟机——类加载器

Launcher类源码分析

Java类加载器的工作原理

Java类加载器的理解

Java中类加载器的分析与理解!详细解析类的加载过程