虚拟机类加载机制
类加载的时机
类从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期包括7哥阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)。其中,验证、解析、准备这三个部分被统称为连接(Linking)过程如下图所示:
虚拟机规范中并没有对什么时候进行加载操作有强制约束;
对于初始化阶段,严格规定了"有且只有"以下无证情况下虚拟机会立刻对类进行
初始化,注意是 “有且只有”:
1) 遇到new、getstatic、putstatic和invokestatic这4条字节码指令时。他们对应的场景是:new 关键字实例化对象、读取或设置一个类的静态字段以及调用一个类的静态方法的时候;
2) 在对类进行反射调用的时候,如果没有进行过初始化,则需要先进性初始化;
3) 当初始化一个类的时候,如果发现他的父类还没有进行初始化的时候,则会先触发父类的初始化过程;
4) 当虚拟机启动时,需要指定一个执行的类(main()方法的类),虚拟机会先初始化这个主类;
5) 在使用动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后解析的结果为REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先进行初始化。
类加载的过程
加载阶段
加载阶段主要完成三件事:
1)通过类的全限定名来获取这个类的二进制字节流
2)将这个字节流所代表的静态存储结构转化为方法去的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据的访问入口
验证
验证的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
(具体验证的细节,感兴趣的可以去看看《深入理解jvm》这本书)
准备
准备阶段会为类变量(static修饰的变量)分配内存并设置类变量初始值的阶段,是分配在方法区中,设置的初始值是0,在程序编译阶段会赋为指定的值
解析
解析阶段是将常量池内的符号引用替换为直接引用的过程
初始化
初始化过程是通过程序制定的主观计划去初始化类变量和其他资源,即就是执行类构造器clinit()方法的过程
类加载器
从开发的角度来看,绝大部分程序都会使用到以下3中系统提供的类加载器
1)启动类加载器(Bootstrap ClassLoader):主要负责将存放在<JAVA_HOME>\lib目录中的并且被虚拟机识别的类库加载到虚拟机内存中,无法被用户直接引用
2)扩展类加载器(Extension ClassLoader):主要负责加载<JAVA_HOME>\lib\ext目录中的或者被java.ext.dirs系统变量所指定的路径中的所有类库,可以被程序直接引用
3)应用程序类加载器(Application ClassLoader):这个类加载器是CLassLoader中的getSystemClassLoader()的返回值,主要负责用户路径(ClassPath)上的类库,开发者可以使用是程序中的默认类加载器
类加载期之间的关系被称为双亲委派模型,这些类加载器一般不以继承的关系来实现,而是以组合的关系来复用父类加载器的代码
双亲委派模型的工作流程是:一个类收到加载请求的时候,会先委派给父类加载器,每个层次都是如此,知道传送到顶层的启动类加载器中,只有父类加载器反馈无法完成这个请求时,子加载器才会尝试加载
使用双亲委派的好处:类的加载也有优先级的层次关系,如java.lang.object类总是最先被加载