java 类加载机制
- 方式:内容来自《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》
类的生命周期
类从被加载到虚拟机内存开始直到卸载为止;类的生命周期包括:加载、验证、准备、解析、初始化、使用、卸载这几个过程;
我们通常说的class load的过程包括:加载、验证、准备、解析、初始化 这几个阶段;
加载
加载阶段主要完成3件事情:
- 通过类的全限定名称获取此类的二进制流
- 通过字节流的静态存储结构转化为方法区的运行时数据结构
- 在内存中生存一个java.lang.Class对象;作为方法区的各种数据的访问入口
验证
验证加载的类的字节流是否合法;主要是为了避免虚拟机收到攻击;大致分为4个阶段;
- 文件格式验证:魔数、版本号、常量的类型、索引及编码规范等
- 元数据验证:是否有父类、是否继承了final类、是否是抽象类、是否实现了所以的父类方法、字段方法是否冲突
- 字节码验证:字节码指令是否符合预期;(如入栈是int出栈也应该是int)、跳转范围是否合理、类型转化是否合理等
- 符号引用验证:引用是否有对应类,是否存在符合方法描述的方法、字段,要求访问的字段、方法是否可访问等
准备
进行内存分配,对static变量赋值;注意以下两种写法;
public static int aa = 123; //准备后为0
public static fianl int bb = 123; //准备后为123
第一种情况实际上是吧赋值放到了 方法内;所以此处并不赋值;常见的初始值情况如下:
- int:0
- boolean:false
- long:0L
- float:0.0f
- short:(short)0
- double:0.0d
- char:’\u0000’
- reference:null
- byte:(byte)0
解析
将符号引用转化为直接引用;
符号引用不要求虚拟机去加载;只要能找到定义即可;
直接引用可以理解为指向目标的指针;有了直接引用;对象必已存在;
几个和解析有关的异常:
- java.lang.IllegalAccessError:被引用对象(类、接口、方法)解析成功;但是引用方没有权限访问;
- java.lang.NoSuchFieldError:解析字段符号引用时;无法在定义类中、定义类的实现接口、类中找到定义时抛出;
- java.lang.NoSuchMethodError:解析类引用时;无法在定义类中、定义类的实现接口、类中找到定义时抛出;
初始化
执行方法;方法是编译器收集类中的静态变量赋值或者是statis区域后自动生成的;
类加载器
被不同类加载器加载的类哪怕拥有相同的全限定名称;也会被认为是两个不同的类;在java中;类加载器使用双亲委派模型;
被成为双亲委派是因为在java虚拟机的角度上;只有两种加载器;
一是Bootsrap ClassLoader,使用C++实现;是虚拟机一部分;另一种是其他的类加载器;
但是我们更习惯与细分,如下(图片来自网络):
- Bootsrap ClassLoader:加载/lib下和Xbootclasspath指定的类;在java中得到此加载器的classload会返回null
- Extension Classloader:扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包
- System Classloader:系统(也称为应用)类加载器;负责加载用户的类;一般来说用户默认的加载器就是这个;
如图所示,双亲委派的模型的工作流程为;如果一个类加载器收到了加载请求;首先会尝试将加载任务委托给自己的父类完成;只有当父类加载器无法完成加载请求时才会自己加载;这样可以避免用户自己定义的类污染jvm环境;