前言
java语言之所以能够编译一次,到处运行,字节码文件功不可没。字节码文件是将java代码编译后得到。编译好的字节码文件需要通过JVM加载到内存,最终才能与CPU进行交流,java程序才能被执行起来。JVM把描述类的数据从字节码文件加载到内存里,并对数据进行校验,解析转化和初始化,最新形成可以被虚拟机直接使用的Java类型,这个过程被称为虚拟机的类加载机制。
一个类从被加载到虚拟机内存中开始,到卸载出内存为止,会经过加载,验证,准备,解析,初始化,使用,卸载共七个阶段。其中验证,准备,解析又被称为连接。具体如图:
加载,验证,准备,初始化和卸载的顺序是确定的。但是解析却不一定,某些情况可以在初始化之后再开始。同时,这里是顺序指定是开始,并不是按部就班的进行或者完成。意思是说这些阶段是穿插执行的,可能在某个阶段会调用,激活其他阶段。
加载
加载是类加载过程的第一个阶段。在这个阶段中,JVM会完成这三件事
1:通过一个类的全限定名来获取定义此类的二进制字节流
2:将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3:在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
总的来说,加载阶段读取类文件产生二进制流,并转化为特定的数据结构,然后再创建对应的类的java.lang.Class对象
验证
验证阶段是连接阶段的第一步,这一个阶段是确保字节流的信息符合安全要求,不会危害虚拟机。如果不检查字节流的话,很有可能会载入恶意的字节流从而可能导致整个虚拟机瘫痪。因此,验证阶段非常重要。大致来说,验证阶段又分为四个阶段的检验,分别是文件格式验证,元数据验证,字节码验证,符号引用验证。
文件格式验证:
就是检验字节流文件是够符合文件格式规范,并且能被当前的虚拟机处理,比如说是否是魔法数cofeBabe开头,主次版本号是否在当前虚拟机接受范围等等。只有通过了这个阶段的验证之后,这些字节流才会进入JVM内存的方法区进行存储,后面的三个验证阶段是基于内存上存储结构进行验证的
元数据验证:
这个阶段是对语义进行验证。比如是是否有父类,是否继承了不应该继承的类(比如说final修饰的),如果有实现接口,是否实现了全部的方法等等。
字节码验证:
这个阶段是最复杂的阶段。对数据流和控制流进行分析,确定程序语义是否合法,符合逻辑的。
符号引用验证:
这一阶段的目的是确保解析行为能够正常执行
准备
准备阶段是正式为静态变量分配内存并赋值。
解析
将常量池内的符号引用替换为直接引用的过程。符号引用是以一组符号来描述所引用的目标。直接引用是可以直接指向目标的指针,相对偏移量或者是一个能间接定位到目标的句柄。
初始化
这个类加载过程的最后一个步骤。这个阶段就是执行类构造器<clinit()>方法的过程
类加载器
类加载就是用于实现类加载动作的代码。类加载器又分为这四类
启动类加载器(Bootstrap Class Loader):由C/C++实现,是最根基的类加载器,负责加载最核心的Java类,即<JAVA_HOME>\lib目录或者被-Xbootclasspath指定路径存放的,比如Object,System等。
扩展类加载器(Extension Class Loader):java代码实现。在包sun.misc.Launcher$ExtClassLoader中。负责加载<JAVA_HOME>\lib\ext目录中的类
引用类加载器(Application Class Loader):由sun.misc.Launcher AppClassLoader实现,负责加载用户路径ClassPath的所有类。可以直接在代码中使用这个类加载器,如果没有自定义过自己的类加载器,那么这个就是程序默认的类加载器。
自定义类加载器(User Class Loader):用户自定义的类加载器
双亲委派模型
不同的类加载器之间的层次关系被称为双亲委派模型。双亲委派模型要求除了启动类加载器之外,其他的类加载器都要有自己的父类加载器。这里的关系又不是一般的继承关系,而是通过组合关系来实现的
双亲委派模型的工作流程是:当一个类加载器拿到一个类时,它并不会先自己去尝试加载这个类,而是委派给父类进行加载,每个层次都是这样工作的。最终,所有的请求都是到达位于顶层的启动类加载器。而只有父类反馈自己无法加载时,子类才会尝试进行加载。
双亲委派模型的好处是能够确保类不会被重复加载,并且能够确保类能够被正确的类加载器加载。比如说java.lang.Object,无论哪个类加载器加载这个类,最终都是会委派给模型最顶端的启动类加载器加载。如果没有双亲委派模型,都是由各个类加载器自行去加载的话,那么自己也编写一个java.lang.Object类,也被去加载,那么系统就会有不同的Object类,会导致整个应用程序变得混乱。
双亲委派机制的代码如下:
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();
c = findClass(name);//再调用本身的findClass方法进行类加载
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}