一、JVM类加载过程
类加载过程:加载->链接->初始化->使用->卸载。链接过程可分为三步:验证->准备->解析。
编辑切换为居中
类加载过程
一个非数组类在加载阶段会由类加载器以二进制流的形式读入内存进行加载,JVM并没有对二进制流设定规范,因此我们可以通过自定加载类加载器重写loadClass方法自定义加载流程。比如读取加密class文件等。数组类不通过类加载器加载,由JVM直接创建。
二、类加载器介绍
JVM中有内置了三个初始的类加载器。
-
BootstrapClassLoader(引导类、启动类加载器),顶层类加载器,C++实现。加载%JAVA_HOME%lib目录下jar包和类或者或被 -Xbootclasspath参数指定的路径中的所有类,加载java.*。
-
ExtensionClassLoader(扩展类加载器) :负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。
-
AppClassLoader(应用程序类加载器) :加载当前应用classpath下的所有jar包和类(在spring加载中起到了重要作用)。
三、双亲委派模型
一个类加载的时候先交由父类加载器去加载,如果父类加载器也存在父类加载器,继续交由父类加载,以此递归,如果父类加载无法加载再由自己去加载。
(说白了就是有事情先叫老爸解决,老爸叫爷爷解决,爷爷解决不了老爸解决,老爸解决不了自己才解决。)
编辑切换为居中
双亲委派模型
四、代码测试
@Test void contextLoads() { ClassLoader app = DemoApplication.class.getClassLoader(); ClassLoader extension = app.getParent(); ClassLoader bootstrap = extension.getParent(); System.out.println("app = " + app); System.out.println("extension = " + extension); System.out.println("bootstrap = " + bootstrap); }
编辑切换为居中
运行结果
DemoApplication为当前类,可以看到当前类的加载器为AppClassLoader。
AppClassLoader加载器的父类为ExtensionClassLoader。
ExtensionClassLoader的父类为null,但null不是代码没有父类加载器,
而是BootstrapClassLoader。
五、源码解析
双亲委派模型的实现代码非常简单,代码在 java.lang.ClassLoader中 。
主要方法是loadClass。
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 { //找引导类加载器加载,调用native方法 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; } }
classloader具体加载class的方法在findClass方法中,由子类具体实现
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
如果想打破双亲委派机制可以自定义一个类加载器继承java.lang.ClassLoader,重写loadClass方法,不交由父类去加载。
如果只是想自定义加载class的方法,比如读取二进制流后进行解密等。只需要重写findClass就行了。
loadClass方法负责加载流程,findClass方法负责具体加载细节。
六、双亲委派模式的特点
-
避免重复加载class,保证数据安全。
-
保护核心class无法修改。
-
不同加载器加载同一个class不是同一个class对象,保证class执行安全。
七、参考资料
面试官:java双亲委派机制及作用www.jianshu.com/p/1e4011617650
深入分析Java ClassLoader原理blog.csdn.net/xyang81/article/details/7292380