一、概述
1、类加载机制: 虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化(三者都在程序运行期间完成),最终形成可以被虚拟机直接使用的java类型
2 、类加载的过程
类从被加载到内存到卸载出内存的生命周期:加载,验证,准备,解析(统称为连接),初始化,使用和卸载
二、类加载过程详解
1、加载
加载阶段虚拟机完成的事情:
1)通过一个类的全限定名来获取此类的二进制字节流
(获取方式)如从zip包中读取,从网络中获取,运行时计算生成,由其他文件生成
2)将这个字节流所代表的的静态存储结构转化为方法去运行时的数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法去这个类的各种数据的访问入口
2、验证
4个检验动作:文件格式验证,元数据验证,字节码验证,符号引用验证
1)文件格式验证
基于二进制字节流进行,保证输入的字节流能正确的解析并存储于方法区之内,格式上符合描述一个java类型信息的要求
2) 元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求
3)字节码验证
通过对数据流和控制流分析,确定程序语义是合法的,符合逻辑的。将类的方法体进行检验分析,保证被校验的类在运行时不会做出危害虚拟机安全的事件
- 在JDK1.6后,为了节省验证时间,给方法体的code属性的属性表增加了StackMapTable的属性,描述了方法体中所有的基本快开始时本地变量表和操作栈应有的状态,在字节码验证期间,只需检查StackMapTable属性中的记录是否合法即可,将字节码验证的类型推导转变为类型检查。在JDK1.6这项优化可选,在JDK1.7唯一选择
4) 符号引用验证
可以看做是对类自身以外的信息进行匹配校验
3、 准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段(仅包括类变量—被static修饰的变量,初始值通常情况下是数据类型的零值)
4、 解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程
- 符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可。与虚拟机的实现内存无关
- 直接引用:可以是直接指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。与虚拟机的实现内存有关
5、初始化
初始化阶段是执行类构造器<clinit>()方法的过程
---有且只有5种必须立即对类进行“初始化”的情况:(主动引用)
1)遇到new,getstatic,putstatic,invokestatic这4条字节指令;
2)使用java.lang.reflact包的方法对类进行反射调用
3)初始化一个类时,若其父类未初始化,则需先初始化父类
4)虚拟机启动时,用户指定执行的父类(包含main()方法的那个类)
5)使用JDK1.7时,若java.lang.invoke.MethodHandle实例最后解析的结REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄
----被动引用:(举例)
1)通过子类引用父类的静态字段,不会导致子类初始化
2)通过数组定义来引用类,不会触发此类的初始化
3)常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发类的初始化
三、类加载器
1、三种类加载器:
- 启动类加载器:负责将存放在<java_home>\lib目录中的,或者被-Xbootclasspath参数指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中
- 扩展类加载器:负责实现<java_home>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库
- 应用程序类加载器(系统类加载器):负责加载用户类路径(classpath)上所制定的类库
2、 双亲委派模型:类加载器之间的层次关系称为类加载器的双亲委派模型,工作过程:当一个类加载器收到类加载请求,先委派给父类加载器,当父类加载器反馈无法完成时子加载器才会尝试自己加载,集中在java.lang.ClassLoader的loadClass()方法中
3、 破坏双亲委派模型
1)为了向前兼容
2)模型自身缺陷—例子:JNDI服务—解决:引用线程上下文类加载器
3)用户对程序动态性的追求—例子:热部署