虚拟机类加载机制
Java中类型的加载、连接和初始化过程在运行期间完成的。
一. 类加载的时机
类的生命周期从类被加载、连接和初始化开始,到类被卸载结束。
类的生命周期包括:加载、连接、初始化、使用、卸载。连接阶段包括验证、准备、解析过程。其中加载、验证、准备、初始化、卸载这5个阶段顺序是确定的。解析阶段在有些情况下可以在初始化之后再开始(动态绑定)。
JVM规定这5种情况必须立即对类进行初始化:(对类进行主动引用)
- 遇到new, getstatic, putstatic, invokestatic这4条字节码指令时。最常见的场景:new实例化对象时,读取或设置一个类的静态字段以及调用一个类的静态方法 时。
- 对类进行反射调用时
- 当初始化一个类时,若其父类还没初始化,对其父类进行初始化
- 当VM启动时,初始化包含main()的主类
- 若一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic, REF_putStatic, REF_invokeStatic的方法句柄,并且这个句柄所对应的类没有进行过初始化,则需先初始化。
除这5种情况之外,所有引用类的方式都不会触发初始化,称为被动引用。
- 通过子类引用父类的静态字段,只会触发父类的初始化,不会导致子类初始化
- 只有直接定义静态字段的类才会被初始化
- 通过数组定义来引用类,不会触发此类的初始化 (new T[])
- 访问一个类的常量字段不会触发该类的初始化
对接口进行初始化:
- 编译器为接口生成“()"类构造器,用于初始化接口中所定义的成员变量
- 类初始化中的第3种,接口在初始化时,不要求其父接口全都完成初始化,只有在真正用到父接口时才会初始化。
二. 类加载的过程:
- 加载
- 通过类的全限定名获取该类的二进制字节流(class文件,jar包,网络,运行时计算生成,从数据库中读取)
- 将字节流所代表的的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个该类的class对象(有的VM在堆中,有的在方法区),作为方法区该类的各种数据的访问入口
数组类本身不通过类加载器创建,有Java虚拟机直接创建。但数组类的元素类型还是要靠类加载器去创建
数组类的可见性默认是其中元素类型的可见性。若是基本类型,默认为public。
- 连接 加载阶段与连接阶段的部分内容可以交叉进行,但他们的开始时间仍然保持着固定的先后顺序。
- 验证:确保class文件的字节流中包含的信息符合当前VM, 验证阶段不是必要的,对程序运行没有影响。
- 文件格式验证
- 魔数开头
- 主次版本号是否在VM处理范围之内
- 常量池中的常量是否有不被支持的常量类型(检查常量tag标志)
- 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
- 。。。
- 该阶段基于二进制字节流进行验证,该阶段验证后,字节流进入内存的方法区,后三个验证阶段基于方法区的存储结构进行验证.
- 元数据验证 :对字节码描述的信息进行语义分析
- 该类是否有父类
- 父类是否继承了不允许被继承的类(final修饰的类)
- 若该类不是抽象类,是否实现了父类或接口之中要求实现的所有方法
- 类中的字段,方法是否与父类产生矛盾
- 字节码验证:对类的方法体进行校验分析,确保程序语义合法,符合逻辑
- 类型转换
- 。。。
- 符号引用验证:发生在VM将符号引用转化为直接引用时(该过程发生在解析阶段)
- 符号引用验证的目的是确保解析动作能正常执行
- 文件格式验证
- 准备:为类变量(static)分配内存并设置类变量及常量初始值,在方法区中分配内存。
类变量该阶段的初始化为数据类型的零值。赋真正的值发生在初始化阶段。 - 解析:VM将常量池内的符号引用替换为直接引用的过程。
符号引用:通过字面量来描述所引用的目标。
直接引用:直接指向目标的指针,相对偏移量或能间接定位到目标的句柄。
- 验证:确保class文件的字节流中包含的信息符合当前VM, 验证阶段不是必要的,对程序运行没有影响。
- 初始化:执行类构造器()方法的过程
- ()由编译器自动收集类中的所有类变量的赋值操作和静态语句块(static{})语句合并产生,按静态变量赋值动作和static代码块在源文件中出现的顺序。
- 若类中没有静态变量赋值操作以及static代码块,可以不生成()
- 接口中没有static语句块,但仍然有变量初始化的赋值操作,故接口会生成().但与类不同的是,不需要先执行父接口的().
- 同一个类加载器下,一个类型只会初始化一次。
三. 类加载器
类的class对象的equals(),isInstance()方法 必须是同一个类加载器加载的类才是同一个类。
类加载器:
- 启动类加载器(Bootstrap classloader):虚拟机中,C++实现。将jdk/lib目录中,或-Xbootclasspath参数指定的路径中的,并且被VM识别(仅按照文件名识别,名字不符合的类库不会被加载)的类库加载到VM内存中。不能被程序直接引用
- 扩展类加载器(Extension classloader):加载jdk/lib/ext目录中,或被java.ext.dirs系统变量所指定的路径中的所有类库。程序中可以直接使用扩展类加载器。
- 应用程序类加载器(Application classloader): 加载用户类路径(classpath)上所指定的类库,程序中可以直接使用应用程序类加载器。
类加载器的双亲委派模型(Parents Delegation Model):
若一个类加载器收到了类加载的请求,他不会自己尝试去加载这个类,而是将请求委派给父类加载器去完成,因此所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父加载器反馈无法完成加载请求(搜索范围中没有找到相应的类)时,子加载器才会尝试自己去加载。