顾名思义,类加载器就是负责类的加载的,从内存中,class文件中,jar包中等渠道加载,对于任意一个class,都需要由加载它的类加载器和这个类本身确定其在JVM中的唯一性。
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:\java\jdk\jdk1.8.0_181\jre\lib\resources.jar D:\java\jdk\jdk1.8.0_181\jre\lib\rt.jar D:\java\jdk\jdk1.8.0_181\jre\lib\sunrsasign.jar D:\java\jdk\jdk1.8.0_181\jre\lib\jsse.jar D:\java\jdk\jdk1.8.0_181\jre\lib\jce.jar D:\java\jdk\jdk1.8.0_181\jre\lib\charsets.jar D:\java\jdk\jdk1.8.0_181\jre\lib\jfr.jar D:\java\jdk\jdk1.8.0_181\jre\classes
根类加载器获取不到引用,所以为null,常用的类如String都是在rt.jar这个jar中的。
由java语言实现,父类加载器为根类加载器,主要加载JAVA_HOME下jre\lib\ext下的目录
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:\java\jdk\jdk1.8.0_181\jre\lib\ext C:\WINDOWS\Sun\Java\lib\ext
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:\java\jdk\jdk1.8.0_181\jre\lib\charsets.jar D:\java\jdk\jdk1.8.0_181\jre\lib\deploy.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\access-bridge-64.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\cldrdata.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\dnsns.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\jaccess.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\jfxrt.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\localedata.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\nashorn.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunec.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunjce_provider.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunmscapi.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\sunpkcs11.jar D:\java\jdk\jdk1.8.0_181\jre\lib\ext\zipfs.jar D:\java\jdk\jdk1.8.0_181\jre\lib\javaws.jar D:\java\jdk\jdk1.8.0_181\jre\lib\jce.jar D:\java\jdk\jdk1.8.0_181\jre\lib\jfr.jar D:\java\jdk\jdk1.8.0_181\jre\lib\jfxswt.jar D:\java\jdk\jdk1.8.0_181\jre\lib\jsse.jar D:\java\jdk\jdk1.8.0_181\jre\lib\management-agent.jar D:\java\jdk\jdk1.8.0_181\jre\lib\plugin.jar D:\java\jdk\jdk1.8.0_181\jre\lib\resources.jar D:\java\jdk\jdk1.8.0_181\jre\lib\rt.jar C:\Program Files\JetBrains\IntelliJ IDEA 2019.1.3\lib\idea_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; } }
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原理)