JDK类加载机制
类加载的大体流程如下:
整篇文章,都对照上图进行讲解,在对上图具体讲解之前,首先介绍一下类加载器。java中主要有以下四种类加载器:
1、引导类加载器(BootstrapClassloader),负责加载jre/lib目录下的核心类库。
2、扩展类加载器(ExtClassloader),负责加载jre/lib/ext目录下的jar包。
3、应用类加载器(AppClassloader),主要负责加载自己写的一些类。
4、自定义类加载器,加载自定义目录下的一些类库。
1 public class TestJDKClassLoader{ 2 3 public static void main(String[] args) { 4 5 //加载核心类库(String) 6 System.out.println(String.class.getClassLoader()); 7 //加载扩展类库 8 System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoade().getClass().getName()); 9 //加载自己写的类 10 System.out.println(TestJDKClassLoader.class.getClassLoader().getClass().getName()); 11 12 } 13 }
上述代码运行结果如下:String类很明显是系统jre/lib目录下的核心类库,根据上述定义,是由引导类加载器构成,由于引导类加载器等相关代码是c++实现的,这里java代码中输出结果为null。
接着回到类加载过程,java.exe调用底层的jvm.dll文件创建java虚拟机,通过c++代码创建引导类加载器(Bootstrapclassloader),由引导类加载器加载并实例化Launcher类,实例化的过程中会完成对扩展类加载器和应用类加载器的实例化,并将应用类加载器的parent属性设置为扩展类加载器。此过程的源码如下,可以看出Launcher类初始化的过程中会生成两个类加载器的实例(可以ctrl+鼠标左键单击进入,底里面是return 一个new对象),并将生成的扩展类加载器赋值给变量var1并作为参数传递给应用类加载器(此过程一直通过ctrl+鼠标左键进行追溯可看到最终将应用类加载器的parent属性设置为了扩展类加载器)。应用加载器用this.loader进行接受,loader为Launcher的一个成员变量,是单例模式实现的。
1 public Launcher() { Launcher.ExtClassLoader var1; 2 try { 3 //构造扩展类加载器,在构造的过程中将其父加载器设置为null 4 var1 = Launcher.ExtClassLoader.getExtClassLoader(); 5 } catch (IOException var10) { 6 throw new InternalError("Could not create extension class loader", var10); 7 } 8 9 try { 10 //构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader, 11 //Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自 12 13 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1); 14 } catch (IOException var9) { 15 throw new InternalError("Could not create application class loader", var9); 16 } 17 18 Thread.currentThread().setContextClassLoader(this.loader); 19 String var2 = System.getProperty("java.security.manager"); 20 //省略一些不需关注代码 21 22 }
接着是最为重要的loadClass过程,该过程为类加载过程中最为重要的一个环节!!!loadclass过程默认遵循一个名为”双亲委派的机制“,所以在细说loadclass之前要先介绍一下什么是双亲委派?
双亲委派机制:即将要加载的类先丢给父加载器,父加载器到对应的加载路径发现没有要加载的东西才会传递给子加载器进行加载,父子类关系如上图所示(这里的父子关系并不是java里的extends,而是parent属性),loadclass过程中最终会调用ClassLoader类的loadClass方法,该方法的实现如下:
1 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 2 synchronized(this.getClassLoadingLock(name)) { 3 Class<?> c = this.findLoadedClass(name);//先判断该加载器是否已经加载过该类,如果没有加载过执行if里面的代码 4 if (c == null) { 5 long t0 = System.nanoTime(); 6 7 try { 8 if (this.parent != null) {//判断有无父加载器,有就丢给父加载器进行加载。 9 c = this.parent.loadClass(name, false);//没有父加载器则自己加载 10 } else { 11 c = this.findBootstrapClassOrNull(name); 12 } 13 } catch (ClassNotFoundException var10) { 14 } 15 16 if (c == null) {//以下代码当上层加载器没有加载,轮到子类加载时执行 17 long t1 = System.nanoTime(); 18 c = this.findClass(name);//findClass方法是完成了将类从class中进行加载,这里ClasLoader中的findclass方法没有具体的实现,具体实现是由URLClassloader类实现的 19 PerfCounter.getParentDelegationTime().addTime(t1 - t0); 20 PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 21 PerfCounter.getFindClasses().increment(); 22 } 23 } 24 25 if (resolve) { 26 this.resolveClass(c); 27 } 28 29 return c; 30 } 31 }
为什么要设计双亲委派机制?
1、沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心 API库被随意篡改
2、 避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一 次,保证被加载类的唯一性
全盘负责委托机制
“全盘负责”是指当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类 所依赖及引用的类也由这个ClassLoder载入。
到此,类加载过程基本已经完成,后续jvm如何执行代码下次再说。
以上仅为本人理解,若有错误欢迎批评指出。