JVM类加载
[toc]
JVM类加载
1. 类文件结构
1.1 Class文件结构
Class文件是二进制文件, 有严格的规范. Class文件所有内容分2种: 无符号数, 表.
- 无符号数: 表示Class文件中的值, 没有任何类型, 但有不同长度, u1, u2, u4, u8代表1/2/4/8字节的无符号数
- 表: 多个无符号数或者其他表作为数据项构成的复合数据类型
Class文件具体由以下构成:
魔数:
Class头4个字节称为魔数, 代表这事class文件类型, 16进制表示为"CAFE BABE"
版本信息:
魔数后4个字节代表版本信息, 5-6字节代表此版本, 6-7代表主版本号,高版本JDK能向下兼容低版本Class文件, 但不能运行更高版本Class文件
java8就是:
00 00 00 34 // 00 00 代表次版本, 00 34代表主版本(3*16+4=52, 对应jdk8)
常量池:
存储种类型常量:
- 字面值常量: 程序中定义的字符串, 被final修饰的值
- 符号引用: 定义的各种名字: 类,接口的全限定名, 字段的名称, 描述符, 方法的名字, 描述符.
常量池特点: - 常量池的常量数量不固定, 每个常量池开头放置一个u2类型的无符号数,存储当前常量池的值
- 每个常量都是一个表, 表第一位是一个u1类型的标志位(tag), 代表当前常量属于哪种常量类型
常量类型:
类型 | tag | 内部结构 | 描述 | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
CONSTANT_utf8_info | 1 |
| UTF-8编码字符串 | ||||||||||||||||
CONSTANT_Integer_info | 3 |
| 整形字面量 | ||||||||||||||||
CONSTANT_Float_info | 4 |
| 浮点型字面量 | ||||||||||||||||
CONSTANT_Long_info | 5 |
| 长整型字面量 | ||||||||||||||||
CONSTANT_Double_info | 6 |
| 双精度浮点型字面量 | ||||||||||||||||
CONSTANT_Class_info | 7 |
| 类或者接口的符号引用 | ||||||||||||||||
CONSTANT_String_info | 8 |
| 字符串字面量 | ||||||||||||||||
CONSTANT_Fieldref_info | 9 |
| 字段的符号引用 | ||||||||||||||||
CONSTANT_Methodref_info | 10 |
| 类中方法的符号引用 | ||||||||||||||||
CONSTANT_InterfaceMethodref_info | 11 |
| 接口中方法的符号引用 | ||||||||||||||||
CONSTANT_NameAndType_info | 12 |
| 字段或者方法的符号引用 | ||||||||||||||||
CONSTANT_MethodHandle_info | 15 |
| 方法句柄 | ||||||||||||||||
CONSTANT_MethodType_info | 16 |
| 标识方法类型 | ||||||||||||||||
CONSTANT_InvokeDynamic_info | 18 |
| 动态方法调用点 |
访问标识:
常量池结束后,紧接的2个字节代表访问标志, 识别一些类或者接口层次的访问信息, 如: Class是类还是接口, 是否定义public类型, 是否被abstract/final修饰
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否声明为final类型 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令新语意, 这个必须为真 |
ACC_INTERFACE | 0x0200 | 表示这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为抽象类, 抽象类或接口必须为真 |
ACC_SYNTHETIC | 0x1000 | 非用户生成代码 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
类索引, 父类索引, 接口索引集合:
类索引: u2类型数据, 确定这个类的全限定名
父类索引: u2类型数据, 确定父类的全限定名(由于java不支持多重继承, 所以父类只有一个), 除了java.lang.Object, 其他java类都有父类(除了Object, 其他类父类索引都不能为0)
接口索引: u2类型集合, 集合第一项u2代表接口集合容量, 后面就是接口的名字索引. java支持实现多个接口. 所以接口是集合.
字段表集合:
接口或者类中涉及的成员变量, 包括实例变量与类变量, 但不包括方法中局部变量.
不会出现父类(或接口)中继承而来的字段, 但有可能出现java代码中不存在的字段, 如: 内部类中为了保持对外部类的访问性,自动添加指向外部实例的字段
字段表结构:
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
u2 | access_flags | 1 | 字段的访问标志 |
u2 | name_index | 1 | 字段名字索引 |
u2 | descrption_index | 1 | 描述符,描述字段数据类型. 基本类型用大写字母, 对象类型用"L"加对象类型全限定名表示 |
u2 | attributes_count | 1 | 属性表集合的长度 |
u2 | attribute | attributes_count | 属性表集合, 用于存放属性的额外信息, 如属性值 |
字段的访问标志access_flags说明:
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否public |
ACC_PRIVATE | 0x0002 | 字段是否private |
ACC_PROTECTED | 0x0004 | 字段是否protected |
ACC_STATIC | 0x0008 | 字段是否static |
ACC_FINAL | 0x0010 | 字段是否final |
ACC_VOLATILE | 0x0040 | 字段是否volatile |
ACC_TRANSIENT | 0x0080 | 字段是否transient |
ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自动生成 |
ACC_ENUM | 0x4000 | 字段是否enum |
字段的对象类型标识符descrption_index说明:
标志字符 | 含义 |
---|---|
B | 基础类型byte |
C | 基础类型char |
D | 基础类型double |
F | 基础类型float |
I | 基础类型int |
J | 基础类型long |
S | 基础类型short |
Z | 基础类型boolean |
V | 特殊类型void |
L | 对象类型, 如Ljava/lang/Object |
方法表集合:
方法表结构:
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
u2 | access_flags | 1 | 方法的访问标志 |
u2 | name_index | 1 | 方法名字索引 |
u2 | descrption_index | 1 | 描述符,描述方法返回数据类型. 基本类型用大写字母, 对象类型用"L"加对象类型全限定名表示 |
u2 | attributes_count | 1 | 属性表集合的长度 |
u2 | attribute | attributes_count | 属性表集合, 用于存放属性的额外信息, 如属性值 |
方法的访问标志access_flags说明:
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 字段是否public |
ACC_PRIVATE | 0x0002 | 字段是否private |
ACC_PROTECTED | 0x0004 | 字段是否protected |
ACC_STATIC | 0x0008 | 字段是否static |
ACC_FINAL | 0x0010 | 字段是否final |
ACC_SYNCHRONIZED | 0x0020 | 字段是否sychronized |
ACC_BRIAGE | 0x0040 | 字段是否编译器生成桥接方法, jdk1.5为了解决子类继承(实现)父类(接口)的一个泛型方法,并在子类中指定了泛型类型 |
ACC_VARARGS | 0x0080 | 字段是否接收不定参数 |
ACC_NATIVE | 0x0100 | 字段是否native |
ACC_ABSTRACT | 0x0400 | 字段是否abstract |
ACC_STRICTFP | 0x0800 | 字段是否strictfp(strictfp关键字!) |
ACC_SYNTHETIC | 0x1000 | 字段是否由编译器自动生成 |
方法的对象类型标识符descrption_index说明:
标志字符 | 含义 |
---|---|
B | 基础类型byte |
C | 基础类型char |
D | 基础类型double |
F | 基础类型float |
I | 基础类型int |
J | 基础类型long |
S | 基础类型short |
Z | 基础类型boolean |
V | 特殊类型void |
L | 对象类型, 如Ljava/lang/Object |
属性表集合:
Class文件, 字段表, 方法表都可以携带自己的属性表, 用于描述某些场景专有信息. 不要求各属性由严格顺序,只有不与已有属性重名即可
每个属性, 它的名称需要从常量池引用CONSTANT_utf8_info常量来标识, 属性表结构:
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u2 | attribute_length | 1 |
u1 | info | attribute_length |
属性表:
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | java编译成的字节码指令 |
ConstantValue | 方法表 | final关键字定义的常量值 |
Deprecated | 类,方法表,字段表 | 被声明为Deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出异常 |
EnclosingMethod | 类文件 | 类为局部类或者匿名类是,才有此属性,标识此类所在外部方法 |
InnerClasses | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | java源码行号与字节码指令对应关系 |
LocalVariableTable | Code属性 | 局部变量描述 |
StackMapTable | Code属性 | JDK1.6新增属性,供新的类型检查验证器检查和处理目标方法的局部变量和操作数栈所需类型是否一致 |
Signature | 类,方法表,字段表 | JDK1.5新增属性, 支持泛型情况下方法签名 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | JDK1.6新增属性,存储额外调试信息(记录非java语言辨析,却需要字节码允许并进行调试的标准机制) |
Synthetic | 类,方法表,字段表 | 方法或者字段为编译器自动生成 |
LocalVariableTypeTable | 类 | JDK1.5新增属性, 使用特征前面代理描述符, 引入泛型后描述泛型参数化类型面添加 |
RuntimeVisibleAnnotation | 类,方法表,字段表 | JDK1.5新增属性, 动态注解提供支持, 哪些注解是运行时 |
RuntimeInvisibleAnnotation | 类,方法表,字段表 | JDK1.5新增属性, 动态注解提供支持, 哪些注解是非运行时 |
RuntimeVisibleParameterAnnotation | 方法表 | JDK1.5新增属性, 动态注解提供支持, 哪些注解是运行时, 作用对象为方法参数 |
RuntimeInvisibleParameterAnnotation | 方法表 | JDK1.5新增属性, 动态注解提供支持, 哪些注解是非运行时, 作用对象为方法参数 |
AnnotationDefault | 方法表 | JDK1.5新增属性,记录注解元素默认值 |
BootstrapMethod | 方法表 | JDK1.7新增属性,保存invokedynamic指令引用的引导方法限定符 |
Code
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attribute_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引,常量值固定为"code", 代表该属性的属性名称 |
| u4 | attribute_length | 1 | 属性值长度 |
| u2 | max_stack | 1 | 操作数栈(Operand Stacks)深度的最大值, 任意时刻,操作数栈都不能超过这个最大值, 虚拟机根据这个值分配栈帧 |
| u2 | max_locals | 1 | 局部变量表所需要的存储空间, 单位是slot |
| u4 | code_length | 1 | 字节码长度 |
| u1 | code | 1 | 编译后生成的字节码指令 |
| u2 | exception_table_length | 1 | 异常表长度 |
| exception_info | exception_table | 1 | 异常表 |
| u2 | attributes_count | 1 | 属性字节数 |
| attribute_info | attributes | 1 | 属性 |
异常表(当字节码在start_pc行到end_pc行之间(try的范围)出现类型为catch_type的异常或者其子类的异常, 则跳转到handle_pc行继续处理):
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | start_pc | 1 | 开始行号 |
| u2 | end_pc | 1 | 结束行号 |
| u2 | handler_pc | 1 | 异常处置行号 |
| u2 | catch_type | 1 | 异常类, 指向CONSTANT_Class_info型常量的索引 |Exceptions属性
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attributes_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 |
| u4 | attributes_length | 1 | 属性值长度 |
| u2 | number_of_exceptions | 1 | 受检异常类型长度 |
| u2 | exception_index_table | number_of_exceptions | 受检异常数组, 指向常量池CONSTANT_Class_info常量索引 |LineNumberTable属性
标识源码行号与字节码行号关系, 可以在javac设置-g:none或者-g:lines选项取消或者要求生成此项信息. 如果取消, 则堆栈不会打印出错行号, 调试时无法安装源码设置断点.
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attributes_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 |
| u4 | attributes_length | 1 | 属性值长度 |
| u2 | line_number_table_length | 1 | 行号表长度 |
| line_number_info | line_number_table | line_number_table_length | 行号表 |
行号表是一个数量为line_number_table_length, 类型为line_number_info的集合,
line_number_info包含u2: start_pc : 1(字节码行号)和u2: line_number : 1(源码行号)的数据项LocalVariableTable属性
描述栈帧局部变量表与java源码中的关系, 可以在javac设置-g:none或者-g:lines选项取消或者要求生成此项信息. 如果取消, 其他引入方法时, 所以参数名称消失, IDE用args1等代替原有参数名
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attributes_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 |
| u4 | attributes_length | 1 | 属性值长度 |
| u2 | local_variable_table_length | 1 | 行号表长度 |
| local_variable_info | local_variable_table | local_variable_table_length | 行号表 |
行号表是一个数量为line_number_table_length, 类型为local_variable_info的集合
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | start_pc | 1 | 局部变量的生命周期开始的字节码偏移量 |
| u2 | length | 1 | 局部变量在生命周期开始的字节码的作用范围覆盖长度 |
| u2 | name_index | 1 | 指向常量池中CONSTANT_Utf8_info型常量的索引,代表局部变量的名称 |
| u2 | descriptor_index | 1 | 指向常量池中CONSTANT_Utf8_info型常量的索引,代表局部变量的描述符 |
| u2 | index | local_variable_table_length | 这个局部变量在栈帧局部变量表中Slot的位置 |SourceFile属性
用来记录生成Class文件的源码,可以在javac设置-g:none或者-g:lines选项取消或者要求生成此项信息. 如果不生成这项信息, 当抛出异常, 堆栈不会显示出错行代码所属的文件名
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attributes_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 |
| u4 | attributes_length | 1 | 属性值长度 |
| u2 | sourcefile_index | 1 | 指向常量池中CONSTANT_Utf8_info型常量的索引,常量值是源码文件名 |ConstantValue属性
通知虚拟机自动为静态变量赋值.只有被static修饰的变量才可以使用这个属性.InnerClasses属性
记录内部类与宿主类之间的关系
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attributes_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 |
| u4 | attributes_length | 1 | 属性值长度 |
| u2 | number_of_classes | 1 | 多少个内部类信息 |
| inner_classes_info | inner_classes | number_of_classes | 内部类信息 |
inner_classes为内部类信息数组
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | inner_class_info_index | 1 | 指向常量池中CONSTANT_Class_info类型常量的索引,代表内部类的符号引用 |
| u2 | outer_class_info_index | 1 | 指向常量池中CONSTANT_Class_info类型常量的索引,代表宿主类的符号引用 |
| u2 | inner_name_index | 1 | 指向常量池中CONSTANT_Utf8_info型常量的索引,代表内部类的名称,如果是匿名内部类,那么这项值为0 |
| u4 | inner_class_access_flags | number_of_classes | 内部类的访问标志 |
匿名内内部访问标志:
| 标志名称 | 标志值 | 含义 |
| ------ | ------ | ---- |
| ACC_PUBLIC | 0x0001 | 字段是否public |
| ACC_PRIVATE | 0x0002 | 字段是否private |
| ACC_PROTECTED | 0x0004 | 字段是否protected |
| ACC_STATIC | 0x0008 | 字段是否static |
| ACC_FINAL | 0x0010 | 字段是否final |
| ACC_INTERFACE | 0x0200 | 表示这是一个接口 |
| ACC_ABSTRACT | 0x0400 | 是否为抽象类, 抽象类或接口必须为真 |
| ACC_SYNTHETIC | 0x1000 | 非用户生成代码 |
| ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
| ACC_ENUM | 0x4000 | 标识这是一个枚举 |Deprecated及Synthetic属性
Deprecated及Synthetic属性都属于标志类型的布尔属性, 只有是否的区别, Deprecated属性用于表示某个类、字段或方法, 有@deprecated注释进行设置.
Synthetic属性代表此字段或方法不是由java源码直接产生,而是由编译器自行添加的.
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attributes_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 |
| u4 | attributes_length | 1 | 属性值长度 |StackMapTable属性
这个属性会在虚拟机的类加载的字节码验证阶段被新类型检查验证器(Type Checker)使用,StackMapTable属性中包含零至多个栈映射帧(Stack Map Frames),每个栈帧射帧都显式或隐式的代表了一个字节码的偏移量,用于表示执行到该字节码时局部变量表和操作数栈的验证类型.类型检查器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定指令是否符合逻辑的约束.
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attributes_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 |
| u4 | attributes_length | 1 | 属性值长度 |
| u4 | number_of_entries | 1 | 栈映射帧长度 |
| stack_map_frame | stack_map_frame_entries | number_of_entries | 栈映射帧 |Signature属性
在任何类,接口,初始化方法或成员的泛型简明中如果包含了类型变量或参数化类型,则Signature属性会为它记录泛型签名信息
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attributes_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 |
| u4 | attributes_length | 1 | 属性值长度 |
| u2 | signature_index | 1 | 必须是一个对常量池的有效索引. 常量池在该索引未知的项必须是CONSTANT_Utf8_info结构;表示类签名,方法类型签名或字段类型签名. 如果当前的Signature属性是类文件的属性,则表示是类签名,如果是方法表的属性则表示是方法类型签名.如果是字段表属性则说明是字段类型签名 |BootstrapMethods属性
用于保存invokedynamic指令引用的引导方法限定符. 如果某个类文件的常量池中曾经出现过CONSTANT_InvokeDynamic_info类型的常量,那么这个类文件的属性表中必须存在一个明确的BootstrapMethods属性,另外,即使CONSTANT_InvokeDynamic_info类型的常量在常量池中出现过多次,最多也只能有一个BootstrapMethods属性
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | attributes_name_index | 1 | 指向CONSTANT_Utf8_info型常量的索引 |
| u4 | attributes_length | 1 | 属性值长度 |
| u2 | num_bootstrap_methods | 1 | bootstrap_method个数 |
| bootstrap_method | bootstrap_methods | num_bootstrap_methods | bootstrap_method数组 |
bootstrap_method结构:
| 类型 | 名称 | 数量 | 说明 |
| ----- | ---- | ---- | ---- |
| u2 | bootstrap_method_ref | 1 | bootstrap_method管理地址 |
| u2 | num_bootstrap_arguments | 1 | 参数数量 |
| u2 | bootstrap_arguments | num_bootstrap_arguments | 参数地址 |
2. 类的加载过程
2.1 类的生命周期
@startuml
:加载;
partition 链接 {
:验证;
:准备;
:解析;
}
:初始化;
:使用;
:卸载;
@enduml
加载
主要完成3件事:
- 根据全类名获取定义此类的二进制字节流
- 将字节流所代表的静态存储结构转换为方法区的运行时数据结构
- 内存生成一个代表该类的Class对象,作为访问方法区这些数据的入口
非数组类加载阶段可以自定义类加载器区控制字节流的获取方式(重写一个类加载器的loadClass()方法)
数组类型是通过Java虚拟机直接创建的
加载和连接阶段部分内容是交叉进行的, 加载阶段暂未结束,连接阶段就可能已开始. 但2个阶段开始时间仍然保存固定的先后顺序
验证
验证安装如下顺序执行
文件格式验证: 验证字节流是否符合规范: 以OxCAFEBABE开头, 主次版本是否支持,常量池常量是否有不支持的类型,常量索引值指向不存在的常量,CONSTANT_Utf8_info常量是否有不符合UTF8编码的数据等等
元数据验证: 对字节码描述信息进行语义分析: 类是否有父类(除Object外都有父类), 类是否继承不允许继承的类(final类)等等
字节码验证: 最复杂阶段, 通过数据流和控制流分析, 确认程序语义合法,符合逻辑. 保证方法运行时不会出现危害虚拟机的事件
符号引用验证: 确保解析动作能正确执行.
准备
为类变量分配内存以及设置初始值
- 只内存分配类变量(即静态变量, static修饰变量), 不包括实例变量(对象实例化使随对象一块分配再java堆中)
- 类变量放到方法区中; JDK1.8后, 元空间实现了方法区, 所以这些数据放到堆外元空间中了
- 初始化值通常情况下是设置数据类型的零值, 如
public static int value=111
, 那此时设置值为0(初始化阶段才会赋值). 但是如果是final类型public static final int value=111
, 那么这时是设置为111的
解析
常量池内的符号引用替换为直接引用的过程, 针对类/接口, 方法, 类方法, 接口方法, 方法类型, 方法句柄, 调用限定符7类符号引用进行.
程序实际运行, 需要明确直到方法所属位置. 而java虚拟机为每个类定义了一个方法表来存放类中所有方法, 当调用方法时, 只要知道这个方法在方法表中的偏移量就可以直接调用该方法了. 解析过程就是将常量池内符号引用转换为直接引用, 就可以得到类,或者字段,方法中的内存指针或偏移量
初始化
执行<clinit>()
(编译之后自动生成)方法的过程, 类加载最后一步. 带锁的线程安全
初始阶段, 虚拟机规范了只有5种情况, 必须对类进行初始化:
- 遇到
new
(初始化类, 即创建一个类的实例对象),getstatic
(访问类的静态变量(未修饰final的static变量), 非静态常量),putstatic
(给静态变量赋值),invokestatic
(静态方法调用) - 使用反射调用
Class.forname()
,newInstance()
等, 如果类未初始化, 则触发初始化 - 初始化类, 如果父类未初始化, 则触发父类初始化
- 虚拟机启动时, main所属类, 虚拟机会先初始化这个类
- 当使用JDK1.7动态语言支持是, 如果
MethodHandle
和VarHandle
实例解析结果为REF_getStatic, REF_putStatic, REF_invokeStatic的方法句柄时, 并且方法句柄对应类还未初始化时, 必须先使用findStaticVarHandle
初始化调用类 - 当接口中定义的
default
方法, 如果实现类发生变化, 那这个接口要在之前初始化
接口初始化时, 不要求父接口全部完成初始, 当真正用到父接口时才会初始化
卸载
该类Class对象被GC, 需要满足三个要求
- 该类所有实例对象被GC
- 没有其他地方引用
- 该类类加载器的实例已被GC
所以JDK自带的BoostrapClassLoader
,ExtClassLoader
, AppClassLoader
负责加载JDK提供类, 所以肯定不会被回收, 而自定义类加载器的实例被回收, 自定义加载器加载的类是可以被卸载掉的
2.2 类加载器
2.2.1 加载方式
任意类, 由加载它的类加载器和类本身来确定在java虚拟机中的唯一性, 每个类加载器, 都有一个独立的类名称空间.
判断2个类是否"相等", 只有在同一类加载器加载的前提下才有意义. 否则即使2个类来源与同一class文件, 被一个虚拟机加载, 只要类加载器不同, 2个类必定不等(Class对象的equals, inInstance()方法返回结果, instanceof判定等)
- 隐式加载: 程序遇到new生成对象时, 隐式调用类加载器加载对应类到JVM中
- 显示加载: 调用Class.forname()等方法, 显示加载类
java类的加载是动态的, 不是一次性将所有类都加载后运行, 而是保证程序运行基础类完全加载到JVM中, 其他类需要时才加载, 节省内存开销
2.2.1 类加载器种类
- 启动类加载器(BootstrapClassLoader): 最顶层加载类, C++实现, 加载
%JAVA_HOME%/lib
核心类库或者被-Xbootclasspath
参数指定的路径中的且能够被虚拟机识别的类库(按文件名识别, 名称不符合的类库即使在目录中也不会被加载), 无法被java程序直接引用 - 扩展加载器(ExtensionClassLoader): 扩展加载类, java实现, 加载
%JRE_HOME/lib/ext%
目录下jar包和类, 或者被java.ext.dirs
系统变量所指定的路径下的jar包,开发者可以直接使用扩展类加载器 - 应用程序加载器(ApplicationClassLoader): 根据java应用类路径(CLASSPATH)来加载java类, 一般来说如果应用程序中没有自定义类加载器, java应用类都有它来加载, 可以通过
ClassLoader.getSystemClassLoader()
来获取 - 自定义加载器(CustomClassLoader): 继承java.lang.ClassLoader实现
2.2.1 双亲委派模型
每个类都有对象的它的类加载器. 系统中ClassLoader协同工作时会默认使用双亲委派模型
- 类加载时, 系统会先判断类是否被加载过. 被加载过则会直接返回, 否则尝试加载
- 加载时, 首先会把该请求委派给父类加载器的loadClass()处理.
- 最终所有请求都应该传送到顶层的启动类加载器BootstrapClassLoader中
- 当父类加载器无法处理时, 字节才来处理
- 当父类加载器为null时, 会使用启动类加载器BootstrapClassLoader作为父类加载器
双亲委派好处
保证java程序的稳定运行, 可以避免类的重复加载(JVM区分不同类不仅根据类名, 相同类文件被不同类加载器加载产生不同的类). 保证了java核心API不被篡改(如果多个类去加载Object类, 会产生多个不同Object类).
不想使用双亲委派模型: 不想打破双亲委派模型, 那么重写findClass()方法即可, 无法被父类加载器加载的方法最终会通过这个发方法加载; 如果想打破双亲委派模型, 则重写loadClass()方法.