类加载器分析
1 什么是类加载
当Java程序执行new函数时,会请求Java虚拟机创建一个类对象,那么在创建类对象前会在内存中找到该类的相关信息,并将其作为创建类对象的依据。
如果Java开发者使用过反射,则会知道在JVM中有一个Class类用于描述用户类的元信息,这里的类加载就是JVM在收到类加载请求后,通过类加载器返回Class类实例
2 类加载机制
Java虚拟机把描述类的数据.class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。
在Java语言里面,类型的加载、连接和初始化过程都是在程序运行期间完成的,这种策略让Java语言进行提前编译会面临额外的困难,也会让类加载时稍微增加一些性能开销,但是却为Java应用提供了极高的扩展性和灵活性,
类加载的过程如下
3 类加载器分类
类加载器分为启动类加载器(Bootstrap Class Loader),这个类加载器使用C++语言实现,是虚拟机的一部分
另一个就是其他所有的类加载器,由Java语言实现,独立存在于虚拟机外部,并且全部继承自抽象类java.lang.ClassLoader
由于启动类加载器涉及的部分比较复杂,我们先从简单的应用类加载器说起
3.1 应用类加载器
假设虚拟机已经完成了初始化等工作,JVM处于正常运行的状态,那么应用类加载器作为Java语言编写的功能即可使用。
应用类加载器的类继承关系如下
根据上图可知,JVM使用了如下Java代码用于描述其关系。首先需创建一个抽象类ClassLoader,所有的类加载器都继承于此
/* src/java.base/share/classes/java/lang/ClassLoader.java */publicabstractclassClassLoader{类加载器至少包含有如下属性,即类加载器的名称
// class loader nameprivatefinalStringname;其中涉及一个关键的函数是loadClass,该函数根据传入的类名用于加载类
publicClass<?>loadClass(Stringname)throwsClassNotFoundException{...可见,其返回值为Class类对象。
3.1.1 类加载器加载类的方式——双亲委派
在openjdk中采用双亲委派机制实现类的加载。
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。《深入理解Java虚拟机》具体来说,假设某个类被请求加载,那么假设该类是用户自定义的类,那么第一步会采用应用类加载器进行加载,但是它不会自己尝试加载该类,而是让父类加载器去加载。
考虑到openjdk中的类加载器继承关系如下
则加载的顺序为
AppClassLoader接受加载类的请求,执行loadClass() 该loadClass会请求父类BuiltinClassLoader执行loadClass() ... 一直向上至ClassLoader执行loadClass()如果父类无法完成类加载工作,则由当前的子类进行加载。
下面的代码用于验证其过程,假设有一个Dog类
publicclassDog{privateString_name;privatedouble_weight;publicStringgetName(){return_name;}publicdoublegetWeight(){return_weight;}publicvoidsetName(Stringname){_name=name;}publicvoidsetWeight(doubleweight){_weight=weight;}publicDog(){}publicDog(Stringname){_name=name;}publicDog(Stringname,doubleweight){_name=name;_weight=weight;}publicvoideat(Stringfood){System.out.println(_name+" is eating "+food);}publicStringspeak(){return"Woof!";}publicstaticvoidmain(String[]args){Dogfido=newDog("Fido");fido.setWeight(12.5);fido.eat("a bone");System.out.println(fido.speak());}}为了解内部的运作机制,可使用IntelliJ Idea调试openjdk的类加载器
可以看到其调用堆栈如下
很明显,在第一步调用ClassLoader时采用的是AppClassLoader,然后逐步往父类调用,一直到最顶层的ClassLoader类的findLoadedClass方法
因此,在JVM实现双亲委派的Java代码中,在ClassLoader中加入了一个parent成员变量表示其父类
publicabstractclassClassLoader{...// The parent class loader for delegationprivatefinalClassLoaderparent;// class loader nameprivatefinalStringname;3.1.2 如何获得Class对象
由于类加载完成后会常驻内存,因此最终会从hotspot中获得Class的信息
public final class LauncherHelper{ private staic Class<?> loadMainClass(int mode, String what){ //获得系统类加载器,这里采用的是AppClassLoader ClassLoader scl = ClassLoader.getSystemClassLoader(); } }