java中类加载器入门

前言

顾名思义,类加载器就是负责类的加载的,从内存中,class文件中,jar包中等渠道加载,对于任意一个class,都需要由加载它的类加载器和这个类本身确定其在JVM中的唯一性。

java内置类加载器

java中内置了3种类加载器

根类加载器

又叫做Bootstrap类加载器,是最顶层的类加载器,没有父类加载器,是C++语言编写的,主要负责java中核心类库的加载,如java.lang包下所有类。

public class Test {

  public static void main(String[] args) {
    System.out.println(String.class.getClassLoader());
    String property = System.getProperty("sun.boot.class.path");
    for (String path : property.split(";")) {
      System.out.println(path);
    }
  }
}

输出为

null
D:javajdkjdk1.8.0_181jrelib
esources.jar
D:javajdkjdk1.8.0_181jrelib
t.jar
D:javajdkjdk1.8.0_181jrelibsunrsasign.jar
D:javajdkjdk1.8.0_181jrelibjsse.jar
D:javajdkjdk1.8.0_181jrelibjce.jar
D:javajdkjdk1.8.0_181jrelibcharsets.jar
D:javajdkjdk1.8.0_181jrelibjfr.jar
D:javajdkjdk1.8.0_181jreclasses

根类加载器获取不到引用,所以为null,常用的类如String都是在rt.jar这个jar中的。

扩展类加载器

由java语言实现,父类加载器为根类加载器,主要加载JAVA_HOME下jrelibext下的目录

import jdk.nashorn.tools.Shell;

public class Test {

  public static void main(String[] args) {
    System.out.println(Shell.class.getClassLoader());
    String property = System.getProperty("java.ext.dirs");
    for (String path : property.split(";")) {
      System.out.println(path);
    }
  }
}

输出为

sun.misc.Launcher$ExtClassLoader@6d6f6e28
D:javajdkjdk1.8.0_181jrelibext
C:WINDOWSSunJavalibext

Shell类就是nashorn.jar中的一个类,类加载器确实为扩展类加载器。

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

    public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
      if (instance == null) {
        Class var0 = Launcher.ExtClassLoader.class;
        synchronized(Launcher.ExtClassLoader.class) {
          if (instance == null) {
            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 {
            File[] var1 = Launcher.ExtClassLoader.getExtDirs();
            int var2 = var1.length;

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

            return new Launcher.ExtClassLoader(var1);
          }
        });
      } catch (PrivilegedActionException var1) {
        throw (IOException)var1.getException();
      }
    }
private static File[] getExtDirs() {
      String var0 = System.getProperty("java.ext.dirs");
      File[] var1;
      if (var0 != null) {
        StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator);
        int var3 = var2.countTokens();
        var1 = new File[var3];

        for(int var4 = 0; var4 < var3; ++var4) {
          var1[var4] = new File(var2.nextToken());
        }
      } else {
        var1 = new File[0];
      }

      return var1;
    }
}

扩展类加载器的定义如上,使用了双重锁判断的单例写法。

系统类加载器

也是java语言实现的,父类加载器为扩展类加载器,加载classpath下的类,如我们引入的第三方jar包和我们自己定义的类。

public class Test {

  public static void main(String[] args) {
    System.out.println(Test.class.getClassLoader());
    String property = System.getProperty("java.class.path");
    for (String path : property.split(";")) {
      System.out.println(path);
    }
  }
}

输出为

sun.misc.Launcher$AppClassLoader@18b4aac2
D:javajdkjdk1.8.0_181jrelibcharsets.jar
D:javajdkjdk1.8.0_181jrelibdeploy.jar
D:javajdkjdk1.8.0_181jrelibextaccess-bridge-64.jar
D:javajdkjdk1.8.0_181jrelibextcldrdata.jar
D:javajdkjdk1.8.0_181jrelibextdnsns.jar
D:javajdkjdk1.8.0_181jrelibextjaccess.jar
D:javajdkjdk1.8.0_181jrelibextjfxrt.jar
D:javajdkjdk1.8.0_181jrelibextlocaledata.jar
D:javajdkjdk1.8.0_181jrelibext
ashorn.jar
D:javajdkjdk1.8.0_181jrelibextsunec.jar
D:javajdkjdk1.8.0_181jrelibextsunjce_provider.jar
D:javajdkjdk1.8.0_181jrelibextsunmscapi.jar
D:javajdkjdk1.8.0_181jrelibextsunpkcs11.jar
D:javajdkjdk1.8.0_181jrelibextzipfs.jar
D:javajdkjdk1.8.0_181jrelibjavaws.jar
D:javajdkjdk1.8.0_181jrelibjce.jar
D:javajdkjdk1.8.0_181jrelibjfr.jar
D:javajdkjdk1.8.0_181jrelibjfxswt.jar
D:javajdkjdk1.8.0_181jrelibjsse.jar
D:javajdkjdk1.8.0_181jrelibmanagement-agent.jar
D:javajdkjdk1.8.0_181jrelibplugin.jar
D:javajdkjdk1.8.0_181jrelib
esources.jar
D:javajdkjdk1.8.0_181jrelib
t.jar
C:Program FilesJetBrainsIntelliJ IDEA 2019.1.3libidea_rt.jar

系统类加载器的定义

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

    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    if (var2 != null) {
      SecurityManager var3 = null;
      if (!"".equals(var2) && !"default".equals(var2)) {
        try {
          var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
        } catch (IllegalAccessException var5) {
        } catch (InstantiationException var6) {
        } catch (ClassNotFoundException var7) {
        } catch (ClassCastException var8) {
        }
      } else {
        var3 = new SecurityManager();
      }

      if (var3 == null) {
        throw new InternalError("Could not create SecurityManager: " + var2);
      }

      System.setSecurityManager(var3);
    }

  }

先创建扩展类加载器,将扩展类加载器作为父类加载器创建系统类加载器,并设置到当前线程的上下文类加载器中。

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

    public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException {
      final String var1 = System.getProperty("java.class.path");
      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);
          return new Launcher.AppClassLoader(var1x, var0);
        }
      });
    }

    AppClassLoader(URL[] var1, ClassLoader var2) {
      super(var1, var2, Launcher.factory);
      this.ucp.initLookupCache(this);
    }

类加载流程

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 {
                    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);

                    // 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;
        }
    }
  1. 首先检查该类是否已经加载过,如果是,直接返回已经加载过的类
  2. 父类加载器存在,委托父类加载器加载,不存在,委托根类加载器
  3. 都不能加载的话,由当前类加载器加载,重写findClass()方法
protected Class<?> findClass(final String name)
        throws ClassNotFoundException
    {
        final Class<?> result;
        try {
            result = AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class<?>>() {
                    public Class<?> run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            return null;
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
        if (result == null) {
            throw new ClassNotFoundException(name);
        }
        return result;
    }

URLClassLoader就是通过重写findClass()实现的。

打破双亲委派机制

双亲委派机制保证了越基础的类由越上层的类加载器加载,基础类之所以被称为“基础”,是因为它们总是作为被调用的API。但是,如果基础类要调用用户的代码,那该怎么办呢。一个典型的例子就是JDBC

jdk提供了所有相关的接口,这些接口都是由根类加载器加载的,而第三方驱动的实现是由系统类加载器加载的,按照双亲委派机制,根类加载器不可能加载得到第三方驱动的实现,为了解决这个困境,java引入了一种不太优雅的设计,线程上下文类加载器,这就变成了父类加载器委派子类加载器去加载,打破了双亲委派机制。java中所有涉及到SPI的动作基本上都是采用的这种方式。



参考

类加载器如何打破双亲委派加载机制(SPI原理)

原文地址:https://www.cnblogs.com/strongmore/p/14952775.html