前言

以下所述,基于java8所写;类加载器通过完全限定名(比如,com.mypak.module1.MyClass)来加载类,它使类可以动态加载类到jvm中,java并未规定类的位置,可以来自本地文件系统,也可以来自网络。

加载器分类

类加载器分为根加载器(bootstrap classloader)扩展类加载器(ext classloader)系统类加载器(system classloader)自定义类加载器(通常继承java.net.URLClassLoader,重写findClass()),它们的关系通常如下。

java 类加载器 双亲委派 根加载器、扩展类加载器、系统类加载器_类加载器


从图中可以看出,每个类加载器都有一个父加载器(加载器有引用指向父加载器,而不是继承),在加载类时,首先check本身是否已加载,如果已加载,则返回,如果未加载,如果有父加载器则交给父加载器,如果没有父加载器则使用根加载器,如果仍没找到,则本加载器加载类,每一级加载器都执行相同的操作,这种机制称为委托机制,英语是parents delegation model,翻译过来是双亲委派机制(其实有点词不达意)。

  • 由于双亲委派机制,加载java.lang.String 时会一直往上委派,直到根加载器,而根加载器只会加载java_home/jre/lib/rt.jar中的java.lang.String,从而确保自定义的java.lang.String不会加载到jvm中,而不会让jvm错乱。
  • 类加载器+类的完全限定名,组成了在jvm中的唯一标识,如果类加载器不一样,即使类限定名相同,也不相等。

类加载器用抽象类java.lang.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); //首先,check自身加载器是否已加载目标类
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);//有父加载器,则委托给父加载器
                    } else {
                        c = findBootstrapClassOrNull(name); //没有父加载器,则委托给根加载器
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name); //调用findClass()方法

                    // 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;
        }
    }

根加载器

根加载器主要是用来加载java_home/jre/lib下的jar包,比如rt.jar(含有全部java api的类),根加载器用C/C++实现,用null表示,在java代码中无法获取到根加载器。

java 类加载器 双亲委派 根加载器、扩展类加载器、系统类加载器_类加载器_02


rt.jar如下

java 类加载器 双亲委派 根加载器、扩展类加载器、系统类加载器_类加载器_03

扩展类加载器

用来加载System.getproperty("java.ext.dirs")也就是java_home/jre/lib/ext`下的jar包,扩展类加载器的父加载器是根加载器。

java 类加载器 双亲委派 根加载器、扩展类加载器、系统类加载器_加载器_04

系统类加载器

用来加载System.getproperty("java.class.path")也就是我们常说的classpath下的类,此路径下都是应用程序的类,所以也可称为应用程序类加载器,它的父加载器是扩展类加载器,classLoader.getSystemClassLoader()返回的就是系统类加载器

自定义类加载器

在程序运行时,如需自定义类加载器,通常继承java.net.URLClassLoader,重写findClass方法,这样符合双亲委派机制。

java 类加载器 双亲委派 根加载器、扩展类加载器、系统类加载器_java_05

实例

系统类加载器–>扩展类加载器–>根加载器

public class MyClassLoader {
    public static void main(String[] args) {
        // 系统类加载器(应用类加载器)
        System.out.println(MyClassLoader.class.getClassLoader());

         //扩展类加载器
        System.out.println(MyClassLoader.class.getClassLoader().getParent());
        //系统类加载器
        System.out.println(MyClassLoader.class.getClassLoader().getParent().getSystemClassLoader());
        
		//根加载器
        System.out.println(MyClassLoader.class.getClassLoader().getParent().getParent());

		//扩展类加载器,java_home/jre/lib/ext/dnsns.jar
        System.out.println(DNSNameService.class.getClassLoader());

		//根加载器
        System.out.println(String.class.getClassLoader());
    }
}

输出

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586
sun.misc.Launcher$AppClassLoader@18b4aac2
null
sun.misc.Launcher$ExtClassLoader@1b6d3586
null

查看源码得知,ExtClassLoader加载System.getProperty("java.ext.dirs")路径下的类,在我的电脑上是D:\jdk1.8.0_211\jre\lib\ext;

static class ExtClassLoader
    extends URLClassLoader
  {
	 private static File[] getExtDirs()
	    {
	      String str = System.getProperty("java.ext.dirs");
	     //省略
	    }
    }

AppClassLoader源码得知,它加载System.getProperty("java.class.path")下的类,在我的系统上是D:\jdk1.8.0_211\jre\lib\ext;D:\jdk1.8.0_211\jre\lib\;还有应用的类,可以看到它包含了ExtClassLoader的加载路径。

static class AppClassLoader
    extends URLClassLoader
  {
	 public static ClassLoader getAppClassLoader(final ClassLoader paramClassLoader)
	      throws IOException
	    {
	      String str = System.getProperty("java.class.path");
	 		//省略
	    }
}

类加载器+类全限定名在jvm中是类的唯一标识

public class MyClassLoader extends ClassLoader{
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String fileName = name.substring(name.lastIndexOf(".")+1) + ".class";
        InputStream is = getClass().getResourceAsStream(fileName);
        assert is != null;
        byte[] b;
        try {
            b = new byte[is.available()];
            is.read(b);
            return defineClass(name,b,0,b.length);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;


    }

    public static void main(String[] args) {
        try {
            Class<?> aClass = new MyClassLoader().findClass("com.jun.javase.MyClassLoader");
            System.out.println(aClass+"---"+MyClassLoader.class);
            System.out.println(aClass.getClassLoader()+"---"+MyClassLoader.class.getClassLoader());
            System.out.println(MyClassLoader.class == aClass);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

输出

class com.jun.javase.MyClassLoader---class com.jun.javase.MyClassLoader
com.jun.javase.MyClassLoader@383534aa---sun.misc.Launcher$AppClassLoader@18b4aac2
false

输出false,因为类加载器不同,即使同一个类,也不相等。