Java
文件最终会被编译成Class
文件,Class
文件最终须要加载到JVM
中才能运行和使用,虚拟机把描述类的Class
文件加载到内存,并对数据进行校验,转换解析和初始化,最终造成能够被直接使用的Java
类型,这就是虚拟机的类加载机制java
在java的语言里,类的加载,连接,初始化
,都是在运行期间进行的,这样虽然会增长一些性能开销,可是会让Java
程序更加的灵活,Java
天生能够动态扩展语言的特性就是依赖运行期动态加载和动态连接来实现的安全
类被加载到虚拟机内存开始,到卸载出内存为止,他的整个生命周期包括,加载,验证,准备,解析,初始化,使用,和卸载
七个阶段,其中验证,准备,解析
3个部分统称为链接
网络
其中加载,验证,准备,初始化,卸载这5个阶段的开始顺序是肯定的,类加载过程必须按照这种顺序循序渐进的开始
,而解析则不肯定,他某些状况下能够在初始化以后开始,注意这里咱们说的是开始
,而不是进行或完成,强调这一点是由于这些阶段是交叉混合的完成,一般在一个阶段进行过程当中激活另外一个阶段数据结构
下面咱们全面了解一下类加载的全过程性能
加载
是类加载
的一个阶段,不要混淆这俩个概念spa
加载阶段主要完成了下面三个事情代理
这三个定义不算具体,第一个经过一个类的全限定名获取定义此类的二进制字节流
,没有指定从哪里获取,怎样获取,这样就给了开发人员很大的灵活性,好比code
加载阶段完成后,虚拟机外部的二进制字节流,就按照虚拟机所需的格式存入到了方法区之中,而后实例化一个java.lang.class对象,虽然他是对象,可是他存在方法区里面cdn
验证是链接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流包含的信息是否符合当前虚拟机的要求,而且不会危害虚拟机的安全对象
准备阶段是正式为类变量(static变量)分配内存并设置初始值的阶段,这些变量所使用的内存,将在方法区分配,这里咱们重点说一下初始值,好比下面这个变量
public static int value=123;
复制代码
这个阶段为他赋值为0,而不是123,由于这时还没有执行任何java方法,把变量赋值为123,是在初始化阶段才会执行,下面列出,java基本数据类型的零值
解析阶段是虚拟机把常量池内的符号替换为直接引用的过程
初始化是类加载的最后一步,前面的类加载过程当中,除了加载阶段用户能够控制之外,其他动做都由虚拟机主导,到了初始化阶段,才是真正执行类中定义的java程序代码
关于类加载的初始化阶段,在虚拟机规范严格规定了有且只有5种场景必须对类进行初始化:
java.lang.invoke.MethodHandle
实例最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic
的方法句柄,而且这个方法句柄对应类没有初始化时,必须触发其初始化(这点看不懂就算了,这是1.7的新增的动态语言支持,其关键特征是它的类型检查的主体过程是在运行期而不是编译期进行的,这是一个比较大点的话题,这里暂且打住)虚拟机把类加载阶段的经过一个类的全限定名获取定义此类的二进制字节流
,这个动做放到java虚拟机外部去实现,以便让用户来决定如何去获取须要的类。实现这个动做的代码块叫作类加载器
类加载器虽然最用于类的加载阶段,可是他在java程序起到的做用不限类的加载阶段,好比,如何判断俩个类相等
,只有俩个类是被同一个加载器加载的前提下才有意义,不然即便俩个类源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不一样,俩个类一定不相等
这里说的相等,包括类的Class
对象equals方法
,也包括Instance of
关键字判断
从java虚拟机的角度来说,只存在俩种不一样的类加载器
从java开发人员来看,类加载器还能够划分更为细致一些,绝大部分都会用到如下3种
Bootstrap ClassLoader(启动列加载器)
这个上面已经介绍过了,这个类加载器负责将放在<JAVA_HOME>\lib
目录中的或者被-Xbootclasspath
参数所指定路径中的,而且被虚拟机识别的类库
加载到虚拟机内存中
Java虚拟机启动就是经过启动类加载器建立一个初始类完成的,因为这个加载器是C++实现的,因此该加载器不能被java代码访问
Extension ClassLoader(扩展类加载器)
他负责加载<JAVA_HOME>\lib\ext
目录中的文件,或者被java.ext.dirs系统变量所指定的路径中的全部类库,开发者能够直接使用扩展类加载器
Application ClassLoader(应用程序类加载器)
因为这个类类加载器是,ClassLoad中的getSystemClassLoader方法的返回值,因此又称为系统类加载器,他负责加载用户路径(classpath)所指定的类库,开发者能够直接使用,若是开发者没有自定义ClassLoader,这个就是程序的默认类加载器
下图中展现的类加载器的层次关系,被称为双亲委派模型,双亲委派模型要求除了顶层的启动类加载器外,其余应当有本身的父加载器,这里的加载器如父子关系通常,不是以继承实现,而是以组合实现
双亲委派模型的工做过程
若是一个加载器收到了类加载的请求,他首先不会本身尝试加载这个类,而是把这个请求委派给父类加载器完成,每个层次的加载器都是如此,所以全部的加载请求,都应该传入到顶层的启动加载器中,只有父类加载器反馈,没法完成这个加载请求,自加载器才会尝试本身去加载
双亲委托模型的好处
String
类来代替系统的String
类,这样会致使风险,可是在双亲委托模型中,String
类在java
虚拟机启动时就被加载了,你自定义的String
类是不会被加载的双亲委托模型的实现源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException{
// 首先检查类是否被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//若是父类抛出ClassNotFoundException异常
//则说明父类不能加载该类
}
if (c == null) {
//若是父类没法加载,则调用自身的findClass进行加
c = findClass(name);
}
}
return c;
}
复制代码
上方逻辑很清楚,首先检查类是否被加载过,若是没有被加载过,就调用父类的加载器加载,若是父类加载器为空就调用启动加载器加载,若是父类加载失败,就调用本身的findClass
加载
参考:《深刻理解Java虚拟机》