虚拟机类加载机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以使用的java类型
类加载的时机
类整个生命周期:加载、验证、准备、解析、初始化、使用和卸载
初始化阶段,有且只有5种情况必须进行初始化
1 遇到new、getstatic、putstatic或invokestatic这4个字节码指令时,如果类没有进行初始化,需要先初始化
2 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化,需要先初始化
3 一个类初始化的时候,如果发现父类没有初始化,需要父类先初始化
4 当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类)。虚拟机会先初始化这个类
5 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果
REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且这个方法句柄所对应的类没有初始化,需要初始化
类加载的过程
加载
1 通过一个类的全限定名来获取定义此类的二进制字节流
2 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
一个非数组类的加载阶段即可以使用系统提供的引导类加载器来完成,也可以由用户自定义的类加载器去完成
一个数组类不通过类加载器创建,由java虚拟机直接创建,遵循以下规则:
1 如果数组的项是引用类型,那就递归加载这个类型,
2 如果数组的项不是引用类型,java虚拟机会把数组标记为与引导类加载器关联
3 数组类的可见性与它的项的可见性一致,如果项类型不是引用类型,那数组类的可见性将默认为public
加载完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区
验证
验证是连接阶段的第一步,为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,不会危害虚拟机
1 文件格式验证
验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
2 元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求
3 字节码验证
通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的
4 符号引用验证
对类自身以外的信息进行匹配性校验
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配
这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
符号引用:
符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要能无歧义定位到目标即可
直接引用:
可以是直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄
初始化
根据程序员通过程序指定的主观计划去初始化类变量和其他资源,执行类构造器<clinit>()方法的过程
<clinit>()特点:
1 方法由编译器自动收集类中的所有类变量的赋值动作和静态语句块中的语句合并产生的
2 不需要显式地调用父类构造器
3 父类中的静态语句块要优先于子类的变量赋值操作
4 对于类或接口来说不是必需的
5 接口与类都会生成此方法
6 虚拟机会保证一个类的<clinit>方法在多线程中被正确的加锁、同步
类加载器
虚拟机把类加载中的“通过一个类的全限定名来获取描述此类的二进制字节流”放到java虚拟机外部去实现
以便让程序自己决定如何去获取所需要的类,实现这个动作的代码模块称为"类加载器"
类与类加载器
任何一个类,都需要由加载它的类加载器和这个类本身确定其在java虚拟机中的唯一性
每一个类加载器,都拥有一个独立的类名称空间
双亲委派模型
从java虚拟机的角度讲,只存在两种不同的类加载器:
1 启动类加载器,C++实现,是虚拟机的一部分
2 其他的类加载器,由java实现,独立于虚拟机外部,全都继承自java.lang.ClassLoader
从开发人员的角度看,有3种系统提供的类加载器
1 启动类加载器,将存放在<JAVA_HOME>\lib目录中,或者被-Xbootclasspath参数所指定的路径中
并且是虚拟机识别的类库加载到虚拟内存中
2 扩展类加载器,加载<JAVA_HOME>\lib\ext,或者被java.ext.dirs系统变量所指定的路径中的所有类库
开发者可以直接使用扩展类加载器
3 应用程序类加载器,一般称为系统类加载器,负责加载用户类路径所指定的类库,开发者可以直接使用
这个就是程序中默认的类加载器
双亲委派模型的工作过程:
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器
只有父加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己加载