类加载运行全过程
当我们java程序在运行一个main方法的时候,需要经历以下这些步骤(这里以一个User类为例)
1.当需要加载User.class的时候,首先会是AppClassLoader,AppClassLoader会判断自己是否有父类加载器,很显然是有的(ExtClassLoader),所以AppClassLoader会把加载User.class的事委托他的父类加载器去加载。
2.当ExtClassLoader加载User.calss的时候,会和上一步一样,判断他自己是否有父类加载器,Ext的父类加载器是BootstrapClassLoader,所以他会把加载User.class的事委托他的父类加载器去加载。
3.当BootsrapClassLoader加载User.class的时候,会判断自己是否会有父类加载器,BootstrapClassLoader是顶级类加载器,所以他没有父类加载器了。这个时候他会校验自己是否应该加载User.class这个类,前面介绍了,BootstrapClassLoader只负责加载支撑JVM运行的位于JRE的lib目录下的核心类库,比如 rt.jar、charsets.jar等,很明显,User.class不会被这个类加载器所加载。
4.告诉ExtClassLoader,让他自己去加载User.class(其实这里在代码层次就是返回一个null,让他的子类加载器知道他没有加载这个类)
5.ExtClassLoader准备加载User.class,他会判断User.class是否该由自己加载,ExtClassLoader只加载支撑JVM运行的位于JRE的lib目录下的ext扩展目录中的JAR 类包,所以ExtClassLoader不会加载User.Class
6.告诉AppClassLoader,让他自己去加载User.class
7.当AppClassLoader准备加载User.class的时候,他会校验是否由自己加载,AppClassLoader负责加载ClassPath路径下的类包,就是加载我们自己写的那些类,所以AppClassLoader会加载User.class。
而加载类,主要是通过loadClass去完成的,在loadClass的类加载过程会经历以下几个步骤
加载>>>>验证>>>>准备>>>>解析>>>>初始化>>>>>使用>>>>>卸载
**加载:**这一步是指通过类的全限定名来定位到这个文件(这里并不一定需要是class文件),并将我们的文件从通过io读入到内存中,这里并不是直接加载到了jvm中,等通过验证的第一阶段才会进入到jvm的方法区(只有使用到一个类的时候,才会去加载。例如我们执行main()方法时,创建一个对象时,不管是通过new,还是反射,都会进行这一步,当然只有第一次创建的时候才会加载)在加载阶段会在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。
**验证:**校验字节码的准确性(很容易理解,假如我们修改了Class字节码文件里的内容,肯定不能校验通过。该阶段分为好几步,1.文件格式验证;(这个阶段是基于二进制字节流验证的,只有通过这个验证,字节流才会进入内存的方法区存储)2.元数据验证;3.字节码验证;4.符号引用验证(后面三个验证阶段都是基于方法区的存储结构进行的))
**准备:**给类的静态变量分配内存,并赋予默认值(例如int赋值为0,对象赋值null)
**解析:**将符号引用替换为直接引用,这个过程会将一些静态方法替换为方法在内存中的地址。
**初始化:**对类的静态变量赋予初始值,执行静态代码块。注意这里是指静态变量,如果是静态常量(被final修饰的变量)在准备阶段就已经赋予了值。
上面介绍的是一个类加载的全过程,下面我们分几块来介绍。
首先是类加载器,在java中,类加载器一共有四种,分别是:
**引导类加载器(启动类加载器)**BootstrapClassLoader:负责加载JRE lib目录下的核心类库
扩展类加载器ExtClassLoader:负责加载JRE lib/ext目录下的类库
程序类加载器AppClassLoader:负责加载ClassPath路径下的类包,例如我们自己写的类。
自定义类加载器:负责加载我们指定路径的类。
public class ClassLoaderTest {
public static void main(String[] args) {
//因为BootstrapClassLoader是C++实现的,所以这里打印的为null
System.out.println(String.class.getClassLoader());
//com.sun.nio.zipfs.ZipPath位于lib/ext下的zipfs.jar中
System.out.println(com.sun.nio.zipfs.ZipPath.class.getClassLoader());
System.out.println(ClassLoaderTest.class.getClassLoader());
}
}
null
sun.misc.Launcher$ExtClassLoader@5b1d2887
sun.misc.Launcher$AppClassLoader@18b4aac2
接下来我们看下每个加载器的父加载器
public class ClassLoaderParentTest {
public static void main(String[] args) {
//因为BootstrapClassLoader是C++实现的,所以这里打印的为null
try {
System.out.println(String.class.getClassLoader().getParent());
}catch (Exception e){
System.out.println(e);
}
//因为ExtClassLoader的父加载器为BootstrapClassLoader,所以输出的为null
System.out.println(com.sun.nio.zipfs.ZipPath.class.getClassLoader().getParent());
System.out.println(ClassLoaderTest.class.getClassLoader().getParent());
}
}
java.lang.NullPointerException
null
sun.misc.Launcher$ExtClassLoader@5b1d2887
从上面的结果我们可以看出,BootstrapClassLoader是最顶级的类加载器,他是C++实现的,所以我们打印出来的jvm内存地址为null。
ExtClassLoader的父类加载器输出的为null,因为他的父类加载器是BootstrapClassLoader,所以打印的为null。
AppClassLoader的父类加载器是ExtClassLoader.
口说无凭,上代码。
我们看看在Launcher中,这块是如何定义与实现的。
首先我们看看Launcher类的构造函数(这里删除了很多代码,只留下了两行我们关注的)
public Launcher() throws Exception {
// 定义扩展类加载器
Launcher.ExtClassLoader var1 = Launcher.ExtClassLoader.getExtClassLoader();
// 创建appClassLoader,这里传入的参数是ExtClassLoader
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
}
接下来我们看看ExtClassLoader的构造函数
public ExtClassLoader(File[] var1) throws IOException {
//这里的第二个参数,最终会调到this.parent = (ClassLoader) null;
super(getExtURLs(var1), (ClassLoader) null, Launcher.factory);
SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
}
再看看我们的AppClassLoader的构造函数
AppClassLoader(URL[] var1, ClassLoader var2) {
//这里的var2就是在Launcher构造函数中getAppClassLoader中的参数,所以他是ExtClassLoader
//最终会调this.parent = var2;
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
所以说,ExtCalssLoader的父加载器是BootstrapClassLoader,AppClassLoader的父加载器是ExtClassLoader.
从代码中可以看出,这里的父子关系,并不是继承关系,只是一个组合关系。