类加载过程
一个类从被加载到虚拟机内存开始,到卸载出内存为止,一共需要经历加载、验证、准备、解析、初始化、使用和卸载,其中加载、验证、准备、解析、初始化又被称为类加载过程,而验证、准备、解析又被称为连接。
何时必须初始化
对于初始化阶段,《JAVA虚拟机规范》有严格规定,(目前为止 jdk 12之前,之后不清楚)有且仅有下面6种情况:
- 遇到new、getStatic、putStatic、invokeStatic、 这四个字节码指令时
场景:
1)使用new关键字实例化对象
2)读取或设置一个类的静态字段(被final修饰、已在编译期把结果放到常量池的静态字段除外)的时候
3)调用一个类的静态方法时- 对类进行反射调用时
- 初始化一个类时,如果他的父类还没有初始化,则先初始化父类 ;
- 虚拟机启动时,执行拥有main()方法的主类时
- jdk7加入的动态语言支持时,有REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄
- jdk8中接口定义了default方法,如果有接口的实现类需要初始化,需要先对接口初始化
详谈类加载过程
- 加载:是类加载的第一个阶段,需要完成三件事
a. 通过一个类的全限名来获取此类的二进制字节流
b. 将字节流所代表的静态存储结构转为方法运行时数据结构
c. 在内存中生成这个类的Class对象,作为方法区这个类的数据访问入口- 验证:确保Class文件的字节流中包含的信息符合《Java虚拟机规范》的全部约束,运行时不会危害虚拟机自身的安全,大致分为四种:
- 文件格式验证:验证字节流是否符合Class文件格式规范,是否能被当前虚拟机处理
比如:是否以0xCAFEBABE开头;主次版本号是否在当前虚拟机接受范围之内等- 元数据验证:对字节码描述的信息进行语义分析,以保证其描述符合《Java语言规范》
比如:该类是否有父类;该类的父类是否继承了不被允许的类,如果不是抽象类,是否实现了父类的方法- 字节码验证:通过字节流分析和控制流分析,确定程序语义是否合法、符合逻辑
- 符号引用验证:验证该类是否缺少或者禁止访问它依赖的外部资源
- 准备:正式为类中定义的变量分配内存并设置初始零值
- 解析:是Java虚拟机将常量池内的符号引用替换为直接引用的过程
- 类或接口的解析
- 字段解析
- 方法解析
- 接口方法解析
- 初始化:会根据程序员通过程序编码制定的主观计划去初始化类变量和其他资源,就是执行<clinit>方法的过程。
- <cliinit>方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块合并产生的
- 执行顺序由语句在源文件中的顺序决定
- 静态语句块可以访问到定义在它之前的变量,但是对于定义在它之后的变量,之前的静态语句块可以赋值,但是不能访问
- 与构造方法不同,不需要显示调用父类的<clinit>方法,Java虚拟机会保证父类的<clinit>方法在此之前执行,这意味着父类的静态语句块优先于子类执行
- 接口中不能使用静态语句块,但仍然有变量初始化赋值操作,但与类中不同,不需要先执行父接口的 <clinit>方法
- java虚拟机会保证一个类的 <clinit>方法的线程安全
类加载器
- 对于任意一个类,都必须由它的类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的名称空间。
- 比较两个类是否相等,只有在这两个类是由同一个类加载器所加载的前提下才有意义
- 相等指:equals()、isAssignableFrom()、isInstance()、instanceOf等各种情况
启动类加载器 :主要负责加载<JAVA_HOME>\lib目录,或者 -Xbootclasspath参数指定路径中的jar包
扩展类加载器 :主要负责加载\lib\ext目录,或者被java.ext.dirs系统变量所指定的目录中的jar
系统类加载器 :主要负责加载用户类路径所有类库
双亲委派模型
- 要求除启动类加载器外,其余所有类加载器都应有自己的父类加载器
- 如果一个类加载器收到类加载请求,他首先不会自己去加载,而是把这个请求委派给父类加载器去加载,每层如此,只有当父类加载器反馈自己无法完成时,才会尝试自己去加载
优点:
- Java中的类随着类加载器一起具备了一种带有优先级的层次关系
双亲委派模型被破坏的场景
1 JNDI、JDBC等,基础类需要回调用户代码时
2 代码热替换、模块热部署等
模块化
jdk9开始,支持模块化,模块化不仅仅只是代码的容器,还包括以下几点:
- 依赖其他模块的列表
- 导出的包列表,其他模块可以访问的
- 开放的包列表,其他模块可以反射访问的
- 使用的服务列表
- 提供服务的实现列表
如何兼容以前的代码
- jar文件在类路径下的访问规则:所有类路径下的jar文件和其他资源,都被视为自动打包在匿名模块 里,该模块几乎是没有任何限制的,可以看到和使用类路径下所有的包,jdk系统模块中所有的导出包,以及模块路径上所有模块的导出包
- 模块在模块路径的访问规则:模块路径下的具名模块,只能访问到依赖中定义的模块和包,对传统目录是无法访问的
- jar文件在模块路径的访问规则:如果把传统jar文件放到模块路径中,它就会变成一个自动模块,可以访问所有模块的导出包,也会导出自己所有的包。
模块化后对类加载器的影响
- 没有了扩展类加载器,出现了平台类加载器
- 启动类加载器、平台类加载器、应用程序类加载器都继承BuiltinClassLoader
- 双亲委派模型发生改变,平台和应用程序类加载器在收到类加载请求,在委派给父类之前,先要判断该类是否归属于某一个模块,如果是,则需要优先委派给负责该模块的类加载器进行加载