java类加载机制升职加薪之旅--重点笔记
⼀、快速梳理JAVA类加载机制
三句话总结JDK8的类加载机制: 1. 类缓存:每个类加载器对他加载过的类都有⼀个缓存。 2. 双亲委派:向上委托查找,向下委托加载。 3. 沙箱保护机制:不允许应⽤程序加载JDK内部的系统类。
先来⼀个简单的Demo
public class LoaderDemo { public static String a ="aaa"; public static void main(String[] args) throws ClassNotFoundException { // ⽗⼦关系 AppClassLoader <- ExtClassLoader <- BootStrap Classloader ClassLoader cl1 = LoaderDemo.class.getClassLoader(); System.out.println("cl1 > " + cl1); System.out.println("parent of cl1 > " + cl1.getParent()); // BootStrap Classloader由C++开发,是JVM虚拟机的⼀部分,本身不是JAVA类。 System.out.println("grant parent of cl1 > " + cl1.getParent().getParent()); // String,Int等基础类由BootStrap Classloader加载。 ClassLoader cl2 = String.class.getClassLoader(); System.out.println("cl2 > " + cl2); System.out.println(cl1.loadClass("java.util.List").getClass().getClassLoader()); // java指令可以通过增加-verbose:class -verbose:gc 参数在启动时打印出类加载情况 // 这些参数来⾃于 sun.misc.Launcher 源码 // BootStrap Classloader,加载java基础类。 System.out.println("BootStrap ClassLoader加载⽬录:" + System.getProperty("sun.boot.class.path")); // Extention Classloader 加载⼀些扩展类。 可通过-D java.ext.dirs另⾏指定⽬录 System.out.println("Extention ClassLoader加载⽬录:" + System.getProperty("java.ext.dirs")); // AppClassLoader 加载CLASSPATH,应⽤下的Jar包。可通过-D java.class.path另⾏指定⽬录 System.out.println("AppClassLoader加载⽬录:" + System.getProperty("java.class.path")); } }
可以看到JDK8中的两个类加载体系:

左侧是JDK中实现的类加载器,通过parent属性形成⽗⼦关系。应⽤中⾃定义的类加载器的parent都是AppClassLoader
右侧是JDK中的类加载器实现类。通过类继承的机制形成体系。未来我们就可以通过继承相关的类实现⾃定义类加载器
JDK8中的类加载器都继承于⼀个统⼀的抽象类ClassLoader,类加载的核⼼也在这个⽗类中。其中,加载类的核
⼼⽅法如下:
//类加载器的核⼼⽅法 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) { long t1 = System.nanoTime(); // ⽗类加载起没有加载过,就⾃⾏解析class⽂件加载。 c = findClass(name); sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } //这⼀段就是加载过程中的链接Linking部分,分为验证、准备,解析三个部分。 // 运⾏时加载类,默认是⽆法进⾏链接步骤的。 if (resolve) { resolveClass(c); } return c; } }
这个⽅法就是最为核⼼的双亲委派机制。并且这个⽅法是protected声明的,这意味着,这个⽅法是可以被⼦类覆盖的。所以,双亲委派机制也是可以被打破的。
当⼀个类加载器要加载⼀个类时,整体的过程就是通过双亲委派机制向上委托查找,如果没有查找到,就向下委托加载。整个过程整理如下图:

二 沙箱保护机制
双亲委派机制有⼀个最⼤的作⽤就是要保护JDK内部的核⼼类不会被应⽤覆盖。⽽为了保护JDK内部的核⼼类,JAVA在双亲委派的基础上,还加了⼀层保险。
private ProtectionDomain preDefineClass(String name, ProtectionDomain pd) { if (!checkName(name)) throw new NoClassDefFoundError("IllegalName: " + name); // 不允许加载核⼼类 if ((name != null) && name.startsWith("java.")) { throw new SecurityException ("Prohibited package name: " + name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
三 Linking链接过程
在ClassLoader的loadClass⽅法中,还有⼀个不起眼的步骤,resolveClass。这是⼀个native⽅法。⽽其实现的过程称为linking-链接。链接过程的实现功能如下图:

四 多个点总结
1,URLClassLoader 引入外部jar包
URL jarPath = new URL("file:/Users/roykingw/DevCode/ClassLoadDemo/out/artifacts/SalaryCaler_jar/SalaryCaler. jar"); URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {jarPath});
2,⾃定义类加载器重写findclass和defindClass方法
3,例如IDEA中的JRebel插件,还有之前介绍过的Arthas。(热加载插件)
4,java的SPI机制
public class SPITest { public static void main(String[] args) { List<String> names = SpringFactoriesLoader.loadFactoryNames(ApplicationContextInitializer.class, null); names.forEach(System.out::println); System.out.println("=============="); List<ApplicationContextInitializer> applicationContextInitializers = SpringFactoriesLoader.loadFactories(ApplicationContextInitializer.class, null); applicationContextInitializers.forEach(System.out::println); } }
5,java --classpath 启动类 引入外部jar包。

浙公网安备 33010602011771号