字节码文件结构篇
java虚拟机规范——字节码文件官网:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.1
源代码经编译后会生成字节码文件,字节码文件是一种二进制的类型文件,它的内容是jvm指令。
字节码指令:
java虚拟机的指令又一个字节长度的、代表某种特定操作含义的操作码(opcode)以及跟随其后的零至多个代表此操作所需参数的操作数构成。
字节码文件整体结构:
ClassFile {
u4 magic; // 魔数
u2 minor_version; // Class文件小版本
u2 major_version; // Class文件大版本
u2 constant_pool_count; // 常量池长度
cp_info constant_pool[constant_pool_count-1]; // 常量池表
u2 access_flags; // 访问标志
u2 this_class; // 类索引
u2 super_class; // 父类索引
u2 interfaces_count; // 接口索引集合
u2 interfaces[interfaces_count];
u2 fields_count; // 字段集合
field_info fields[fields_count];
u2 methods_count; // 方法集合
method_info methods[methods_count];
u2 attributes_count; // 属性集合
attribute_info attributes[attributes_count];
}
1.魔数
magic 作为clss文件的开头,占四个字节,用于校验文件是否属于合法的class文件。
固定字符为:ca fe ba be
2.Class文件版本号
4字节的魔数之后,是4个字节的class文件版本。其中第5、6位是minor_version(副版本),第7、8位是major_version(主版本)。主版本从45开始(jdk1.1),此后每更新一个版本主版本号+1(如jdk1.8对应52);副版本号1.1时位3,此后版本都为0。
高版本虚拟机及可以运行由低版本编译器生成的class文件,但是低版本的虚拟机不能运行高版本编译的class文件。
3.常量池
常量池时Class文件中内容最丰富的区域之一,也是对于Class文件中的字段的方法解析也有着至关重要的作用。包括有2字节的常量池容量计数器和若干字节常量池表。在常量池表中主要存放字面量
和符号引用
。
常量池计数器
constant_pool_count 占2个字节,用于表示常量池中项的数量。但是,该计数器从1开始计数,比如:当constant_pool_count=1时,表示常量池中没有项;再如:当constant_pool_count=20时,表示常量池中的项有19个,索引为1-19(这里0被空出来)。
常量池表
constant_pool 中存放constant_pool_count-1个项。主要存放字面量
和符号引用
。
具体存放类型如下表所示:
Constant Type | 标识 | 描述 |
---|---|---|
CONSTANT_Class | 7 | 类或接口的符号引用 |
CONSTANT_Fieldref | 9 | 字段的符号引用 |
CONSTANT_Methodref | 10 | 类中方法的符号引用 |
CONSTANT_InterfaceMethodref | 11 | 接口中方法的符号引用 |
CONSTANT_String | 8 | 字符串类型字面量 |
CONSTANT_Integer | 3 | 整型字面量 |
CONSTANT_Float | 4 | 浮点型字面量 |
CONSTANT_Long | 5 | 长整型字面量 |
CONSTANT_Double | 6 | 双精度浮点型字面量 |
CONSTANT_NameAndType | 12 | 字段或方法的符号引用 |
CONSTANT_Utf8 | 1 | UTF-8编码的字符串 |
CONSTANT_MethodHandle | 15 | 表示方法句柄 |
CONSTANT_MethodType | 16 | 标志方法类型 |
CONSTANT_InvokeDynamic | 18 | 动态调用 |
解释:
- 字面量:
- 文本字符串:String str = “abcd";
- 声明为final的常量值:final int a = 5;
- 符号引用:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
注意描述符
的作用是用来描述数据的类型、方法参数列表(包括数量、类型及顺序)和返回值。具体类型和其标识符如下表所示:
字段类型术语 | 类型 | 解释 |
---|---|---|
B | byte | 基本数据类型byte |
C | char | 基本数据类型char |
D | double | 双精度浮点值 |
F | float | 单精度浮点值 |
I | int | 基本数据类型int |
J | long | 基本数据类型long |
L | reference | 类ClassName 的一个实例 |
S | short | 基本数据类型short |
Z | boolean | true 或者 false |
[ | reference | 一维数组 |
V | 代表void类型 |
符号引用:简单理解就是用一个符号来表示一个目标(这个目标可以是一个类、一个方法或是一个字段),而内存不需要已经加载了这个目标,因为这个目标已近被符号代表了。
直接引用:直接引用就是直接指向目标(可以用指针、句柄或者偏移量来指明),说明此时目标已经被加载到了内存中(否则没有内存可以被指向)。
常量池字节码文件解读
常量池16进制文件解读:
对应jclasslib中常量池解读:
4.访问标识
access_flags 常量池之后紧随着访问标识,占2个字节,用于识别一些类或接口层次的访问信息,包括:这个类是接口还是类;是否定义为oublic类型;是否定义为抽象类型;是否被声明为final的类等。
访问标识如下表所示:
标志名称 | 标志值 | 解释 |
---|---|---|
ACC_PUBLIC | 0x0001 | 宣布public ; 可以从其包外部访问。 |
ACC_FINAL | 0x0010 | 宣布final ; 不允许有子类。 |
ACC_SUPER | 0x0020 | 当被invokespecial指令调用时,特别对待超类方法。 |
ACC_INTERFACE | 0x0200 | 表示一个接口 |
ACC_ABSTRACT | 0x0400 | 表示abstract`; 不得实例化。 |
ACC_SYNTHETIC | 0x1000 | 非用户代码生成;源代码中不存在。 |
ACC_ANNOTATION | 0x2000 | 声明为注解类型。 |
ACC_ENUM | 0x4000 | 声明为enum 类型。 |
在class文件中的access_flags是由上表中的多个组合而成的。
5.索引
通过类索引、父类索引和接口索引集合表示类的继承关系。
类索引
this_class 为类索引,占2个字节。用于确定这个类的全限定名。
父类索引
super_class 为父类索引,占2个字节。该索引指向常量池中一个位置,用于表示当前类的全限定名。任何类除了Object类都有且只有父类,而Object类的所以为"0x00"。
接口索引集合
interfaces 为接口索引集合,有2个字节的interfaces_count和interfaces接口集合表示,因为一个类可以实现多个接口。而interfaces中的每个索引也是指向常量池中的一个CONSTANT_Class类型。
6.字段表集合
fields 用于描述接口中或类中声明的变量。包括类变量和实例变量。它指向常量池索引集合,描述每个字段的完整信息,比如字段的标识符、访问修饰符、是类变量还是成员变量、是否是常量等。
由fields_count 字段计数器2字节和fields 字段表集合组成。
fields 包含
- 2字节字段访问标识
- 2字节字段名称索引
- 2字节描述符索引(主要为字段的类型)
- 2字节字段属性计数器、字段属性表
字段访问标识:
标志名称 | 标识 | 解释 |
---|---|---|
ACC_PUBLIC | 0x0001 | 表示public ; 可以从其包外部访问。 |
ACC_PRIVATE | 0x0002 | 表示private ; 只能在定义类中使用。 |
ACC_PROTECTED | 0x0004 | 表示protected ; 可以在子类中访问。 |
ACC_STATIC | 0x0008 | 表示static 。 |
ACC_FINAL | 0x0010 | 表示final ; 永远不会直接分配给对象构造之后(JLS §17.5)。 |
ACC_VOLATILE | 0x0040 | 表示volatile ; 无法缓存。 |
ACC_TRANSIENT | 0x0080 | 表示transient ; 不由持久对象管理器写入或读取。 |
ACC_SYNTHETIC | 0x1000 | 表示合成;源代码中不存在。 |
ACC_ENUM | 0x4000 | 声明为 的元素enum 。 |
7.方法表集合
methods 指向常量池索引集合,它完整描述了每个方法的签名。
由methods_count 方法计数器和methods 方法表组成。
methods 包含:
- 2字节方法访问标识
- 2字节方法名索引
- 2字节描述符索引(主要为方法的参数信息及返回类型信息)
- 2字节属性计数器、方法属性表
方法访问标识:
标志名称 | 标识 | 解释 |
---|---|---|
ACC_PUBLIC | 0x0001 | 表示public ; 可以从其包外部访问。 |
ACC_PRIVATE | 0x0002 | 表示private ; 只能在定义类中访问。 |
ACC_PROTECTED | 0x0004 | 表示protected ; 可以在子类中访问。 |
ACC_STATIC | 0x0008 | 表示static 。 |
ACC_FINAL | 0x0010 | 表示final ; 不得覆盖(第5.4.5 节)。 |
ACC_SYNCHRONIZED | 0x0020 | 表示synchronized ; 调用由监视器使用包装。 |
ACC_BRIDGE | 0x0040 | 一种桥接方法,由编译器生成。 |
ACC_VARARGS | 0x0080 | 用可变数量的参数声明。 |
ACC_NATIVE | 0x0100 | 表示native ; 用 Java 以外的语言实现。 |
ACC_ABSTRACT | 0x0400 | 表示abstract ; 没有提供实现。 |
ACC_STRICT | 0x0800 | 表示strictfp ; 浮点模式是 FP-strict。 |
ACC_SYNTHETIC | 0x1000 | 申报合成;源代码中不存在。 |
8.属性表集合
方法标记和后面的字节表示属性表。包括attributes_count 属性计数器和 attributes 属性表集合。
在字段表集合和方法表集合中都有属性表集合。
属性表包含:
- 2字节属性名索引
- 4字节属性长度
- 字节属性表
9.javap指令
用法: javap
其中, 可能的选项包括:
–help -help -h -? 输出此帮助消息
-version 当前javap指令的版本信息,与class文件无关
-v -verbose 输出附加信息
-l 输出行号和本地变量表
-public 仅显示公共类和成员
-protected 显示受保护的/公共类和成员
-package 显示程序包/受保护的/公共类 和成员 (默认)
-p -private 显示所有类和成员
-c 对代码进行反汇编
-s 输出内部类型签名
-sysinfo 显示正在处理的类的系统信息(路径、大小、日期、SHA-256 散列)
-constants 显示最终常量
–module <模块>, -m <模块> 指定包含要反汇编的类的模块
-J 指定 VM 选项
–module-path <路径> 指定查找应用程序模块的位置
–system 指定查找系统模块的位置
–class-path <路径> 指定查找用户类文件的位置
-classpath <路径> 指定查找用户类文件的位置
-cp <路径> 指定查找用户类文件的位置
-bootclasspath <路径> 覆盖引导类文件的位置
–multi-release 指定要在多发行版 JAR 文件中使用的版本
GNU 样式的选项可使用 = (而非空白) 来分隔选项名称及其值。
每个类可由其文件名, URL 或其
全限定类名指定。示例:
path/to/MyClass.class
jar:file:///path/to/MyJar.jar!/mypkg/MyClass.class
java.lang.Object
字节码指令集篇
java虚拟机指令由一个字节长度的、代表某种特定含义的数字(称为操作码,Opcode)以及跟随其后的零至多个代表此操作所需参数(称为操作数,Operands)构成。
局部变量表和操作数栈中保存的都是数据,不是变量名
1.加载与存储指令
加载指令
局部变量入栈指令:
从局部变量表中取出对应索引
(n)的变量压入操作数栈中。
- xload_ x为i、l、f、d、a,0<=n<=3
- xload n
常量入栈指令:
const指令:
- iconst_:整型入栈,i=[-1,5] i标识压入栈的具体数据,下面同理。
- lconst_:长整型入栈,l=[0,1]
- fconst_:浮点型入栈,f=[0,2]
- dconst_:双精度入栈,d=[0,1]
- aconst_null:引用类型入栈,值为null
push指令:因为const的范围有限,所以使用push
- bipush:接收8位整型,[-128,127]
- sipuch:接收16位整型,[-32768,32767]
ldc指令:ldc指令是万能的,接收8位参数指向常量池中的索引,并将其压入栈中。
存储指令
将数据保存到局部变更表中。
- xstore_:将操作数栈顶的数据存储到局部变量表的索引为n的位置
- xstore n
2.算术运指令
用于对操作数栈上的两个值pop进行运算,并将结果重新压入栈。
- 加法指令:iadd、ladd、fadd、dadd
- 减法指令:isub、lsub、fsub、asub
- 乘法指令:imul…
- 除法指令:idiv…
- 求余指令:irem…
- 取反指令:ineg…
- 自增指令:iinc…
- 位运算指令
- 位移:ishl、ishr、iushr、lshl、lshr、lushr
- 或:ior、lor
- 与:iand、land
- 异或:ixor、loxr
- 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp :比较栈顶的两个数据的大小并将结果保存到操作数栈中。
3.类型转换指令
宽化类型转换
范围小的类型向范围大的类型转换。即:int -> long -> float -> double
指令:
- i2l、i2f、i2d
- l2f、l2d
- f2d
int、long转float,long转double可能会出现精度损失的问题(由于后面的范围大了而占用的空间不变,就会导致精度损失)。byte、short、char类型没有转换指令,视为int。
窄化类型转换
范围大的类型转化为范围效的,容易出现精度损失。
- i2b、i2s、i2c
- l2i
- f2i、f2l
- d2l、d2i、d2f
其他情况可以使用两个指令组合完成,如:long转byte -> l2i + i2b
注意:
- NaN转int或long结果为0;
- 浮点数的无穷大转int或long结果为int或long的MAX_VALUE;
- 当double值很小接近于0时且超过float的进度范围时,转化为float的值为0.0或-0.0;
- 当double值很大且超过float的进度范围时,转化为float的值为正负无穷大;
- double的NaN转Float的NaN,结果依然时NaN。
4.对象创建于访问指令
创建指令
-
创建类实例的指令:new
接受一个操作数,执行常量池的索引,标识要创建的类型,执行完成后将对象引用压入栈。
-
创建数组的指令:
- newarray:基本数据类型数组
- anewarray:引用类型数组
- multianewarray:多维数组
字段访问指令
访问类字段(static字段):
- getstatic 操作数:将操作数所指向的对象或值压入栈中
- putstatic 操作数: 将栈中的值或对象弹出赋值给操作数
访问实例字段(非static字段):
- getfield 操作数:同上
- putfield 操作数:
数组擦做指令
- xastore:将一个操作数栈中的值存储到数组指定索引中。需要三个栈顶值:值、索引、数组引用地址,将值保存到数组的响应索引位置。
- xaload:将一个数组元素加载到操作数栈。需要两个值:栈顶的数组索引、第二顺位的数组引用地址,将数组对应索引位置的值压入栈中。
- arraylength:弹出数组引用地址,压入数组长度。
x可以是b、c、s、i、l、f、d、a
类型检查指令
- instanceof:判断给定对象是否是某一个类的实例
- checkcast:检查类型强转是否可以进行
5…方法调用和返回
方法调用指令
- invokevirtual:调用对象的实例方法,根据对象的实际类型进行分派,称为动态分派;
- invokeinterface:调用接口方法,在运行时会调用具体实现类中的方法;
- invokespecial:调用一些需要特殊处理的实例方法:包括实例初始化方法(构造器)、私有化方法和父类方法,这些方法都不能重写,称为**静态类型绑定:**简单理解就是调用的方法是确定的;
- invokestatic:调用静态方法,也是静态类型绑定;
- invokedynamic:调用动态绑定的方法。
方法返回指令
- xreturn:将当前方法的操作数栈的栈顶元素弹出,并将其压入调用这个方法的操作数栈,x为i、l、f、d、a
- return:无具体返回值类型
6.操作数栈管理指令
- pop:弹出栈顶元素
- pop2:弹出2个元素(或弹出2个slot)
- dup
- dup:复制一个slot,置于栈顶
- dup2:复制两个slot置于栈顶
- duo_x1:复制一个栈顶slot,并将其插入到距栈顶2个slot的下面
- duo2_x1:复制两个栈顶slot,并将其插入到距栈顶3个slot的下面
- dup_x2:复制一个栈顶slot,并将其插入到距栈顶3个slot的下面
- dup2_x2:复制两个栈顶slot,并将其插入到距栈顶4个slot的下面
- swap:交换栈顶两个slot的位置
7.控制转移指令
比较指令
dcmpg、dcmpl、fcmpg、fcmpl、lcmp
上面的指令表示从栈顶弹出两个元素(top为B,top-1为A),比较A、B大小,若A>B将1压入栈,若A=B,将0压入栈;若A<B,将-1压入栈。cmpg于cmpl的区别在于NaN的比较,一般用不到。
条件跳转指令
在使用如下指令是,先要使用比较指令压入一个int的数。
- ifeq:当栈顶int类型数值等于0时跳转
- ifne:当栈顶int类型数值不等于0时跳转
- iflt:当栈顶int类型数值小于0时跳转
- ifle:当栈顶int类型数值小于等于0时跳转
- ifgt:当栈顶int类型数值大于0时跳转
- ifge:当栈顶int类型数值大于等于0时跳转
- ifnull:当栈顶int类型数值为null时跳转
- ifnonull:当栈顶int类型数值不为null时跳转
比较条件跳转指令
判断两个整型的大小决定是否跳转
- if_icmpge
- if_icmple
- …
多条件分支跳转指令
为switch-case设计的条件分支跳转。
- tableswitch:用于case值连续
- lookupswitch::用于case值不连续
在jdk7以后可以使用String,比较的是hash值,如果hash值相等在使用equals比较
无条件跳转指令
goto 操作数 跳转到操作数指定的偏移量的指令。
8.异常处理指令
抛出异常敕令
athrow:显示手动抛出异常"throw’"
异常处理
如果一个方法定义了一个try-catch或try-finally,就会创建一个异常表。异常表保存了异常处理信息。
出现异常后不会使用字节码指令处理异常,而是使用异常表处理,该异常会与异常表中的异常比较,如果一致则使用该异常(包含异常出现的为范围,跳转到的指令偏移量,异常类型等)。
9.同步控制指令
同步方法
是隐式的,无需通过字节码指令来控制。会给方法添加访问synchronized标识
同步代码块
- monitorenter:握锁
- monitorexit:释放锁
当一个方法执行monitorenter指令时,会有一个计数器,若计数器为0则任何同步代码块可以使用,使用后+1;若计数器为1且不是本线程持有,则不能进入。当在同步代码块中出现异常后,会使用monitorexit释放锁。
类加载过程篇
类的生命周期:
加载 >> 验证 >> 准备 >> 解析 >> 初始化 >> 使用 >> 卸载
1.加载
加载过程就是将java类的字节码文件加载到机器内存中,并在内存中构建出java类的原型——类模板对象。
主要有以下操作:
- 通过类的全类名,获取类i的二进制数据流。
- 解析类的二进制数据流为方法区内的数据结构(java类模型)。
- 创建java.lang.Class类的实例,表示该类型。作为方法区这个类的各种数据的访问入口。
二进制流可以是class文件、jar或zip归档数据包、存储在数据库中的二进制流、网络中传输的二进制流等。
类模型存放在方法区中。Class对象存储在堆空间中。
数组类本身并不是由类加载器负责创建,而是由jvm在运行时根据需要直接创建的,但是数组元素的类型任然需要依靠类加载器去加载创建。
2.链接
注意:在此环节中不涉及代码的执行。
验证
- 格式检查:魔数检查、版本检查、长度检查(事实上在加载阶段就已经做了这些工作)
- 语义检查:比如父类是否存在、final修饰的方法或类是否被重写或继承、是否存在不兼容方法等
- 字节码验证:跳转的指令是否存在、函数参数传递是否正确、变量赋值是否给了正确类型。
- 符号引用验证
准备
为类中的静态变量分配内存,并将其初始化为默认值。
常量在编译的时候就被分配了,在准备阶段会显式赋值。
解析
将类、接口、字段和方法的符号引用转换为直接引用。
3.初始化阶段
为类的静态变量赋予正确的初始值。
只有在初始化阶段才开始执行代码。在类初始化方法<clinit>()中执行。
在加载一个类之前,会先加载其父类;且父类的()会先于子类的()。
生成()方法的场景:
- 对于非静态变量,不管是否显式赋值,都不会生成;
- 对于静态变量,没有显示赋值,则不会生成;
- 对于静态变量且显式赋值,则会生成;
- 基本数据类型常量(static final)不会生成;
- 引用类型的常量和静态变量都在()方法中初始化;但是字面量的String常量在准备阶段初始化。
简单理解:非晶态的变量一定不在初始化阶段赋值;仅static 修饰的变量一定在初始化阶段赋值;static+ final 修饰的常量:1.如不涉及代码操作(如基本数据类型,String字面量)则在准备阶段赋值;2.如涉及代码操作(如需要new 对象,引用数据类型,随机数等)则在初始化阶段赋值。
()方法是带锁的线程安全的。但是可能会出现死锁。
类的主动使用
类的主动使用会执行()方法
- 创建一个类的实例的时候(new,反序列化等)
- 调用类的静态方法时
- 调用类、接口的静态变量字段
- 使用反射类的方法时
- 初始化子类时,先初始化其父类,但是接口不适用;子接口初始化不会初始化父接口
- 接口中有default方法时,实现类的初始化会导致接口的初始化(无论是否调用接口中的方法或变量)
- 虚拟机启动会先初始化包含main()方法的类
类的被动使用
类的被动调用不会执行()方法
- 当访问一个静态字段时,只有真正声明了这个字段的类才会被初始化。如:通过子类调用父类的静态字段时,父类会被初始化而子类不会被初始化。
- 通过数组定义类引用,不会触发初始化,即:定义的数组的类型的类不会被初始化。
- 引用常量或接口不会触发初始化。
- 调用ClassLoader类的loadClass()方法加载一个类不会初始化这个类。
4.类的使用
即对对象的静态变量、方法的使用,即类的实例化等。
5.类的卸载
类(类模板,存放在方法区中)何时被回收(即类不在被使用)?
- 该类的所有实例都已经被回收
- 加载该类的类加载器已经被回收
- 该类对应的java.lang.Class对象没有在任何地方被引用
引导类加载器、扩展类加载器、系统类加载器很难被卸载。
类的加载器篇
1.类加载器分类
BootStrap ClassLoader
使用C/C++语言实现,嵌套在JVM内存。不继承于ClassLoader。
用于加载核心类库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)。
只加载包名为java、javax、sun开头的类。
扩展类加载器 和 系统类加载器 的类的实例由引导类加载器加载的,所以引导类加载器为其二者的父类加载器。
扩展类加载器
Java语言编写,继承ClassLoader类。
主要加载jre/lib/ext目录下的类库。
系统类加载器
继承于ClassLoader类,父类加载器为扩展类加载器。
负责加载classpath下的类库。应用程序中的类加载器默认是系统类加载器。是用户自定义类加载器的默认父类加载器。
用户自定义类加载器
2.双亲委派机制
双亲委派机制的实现可以查看ClassLoader类中的loadClass()方法源代码。
作用:
- 避免重复加载,确保一个类的全局唯一性。(由于每个类加载器都有自己的命名空间,其中不会存放相同类)
- 保护程序安全,防止篡改核心API。(核心API会由于向上委派,所以始终由上层的类加载器加载jre中的类库,而不是用户自定义的在classpath中的类库)
弊端:顶层的cCassLoader无法访问底层的ClassLoader所加载的类。
破坏双亲委派模式情况
1.JDK1.2之前没有双亲委派机制
2.使用ContextClassLoader(线程上下文类加载器,默认为系统类加载器),当上层的类加载器要访问下层的类加载器加载的类的时候,就反向委托上下文类加载器去访问。
3.OSGI使用更复杂的网状结构。
3.自定义类加载器
为社么使用自定义类加载器?
- 隔离加载类
- 修改类加载的方式
- 扩展加载源
- 防止源码泄露
实现
- 继承ClassLoader
- 重写方法:
- 重写loadClass()
- 重写findClass():推荐使用,保留双亲委派机制,改动范围小。
/*
当创建一个MyClassLoader实例的时候,只需传入需要加载的类的文件的地址,
然后调用findClass()方法传入文件的名称即可
*/
public class MyClassLoader extends ClassLoader {
private String loadPath;
public MyClassLoader(String loadPath) {
this.loadPath = loadPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 获取字节码文件位置
String fileName = loadPath + name + ".class";
BufferedInputStream bis = null;
ByteArrayOutputStream baos = null;
try {
bis = new BufferedInputStream(new FileInputStream(fileName));
baos = new ByteArrayOutputStream();
int len;
byte[] data = new byte[1024];
// 读取文件并将其写出到内存
while ((len = bis.read(data)) != -1) {
baos.write(data, 0, len);
}
// 获取内存中的完整的字节数组数据
byte[] bytes = baos.toByteArray();
// 将字节数组转换为class实例
defineClass(null, bytes, 0, bytes.length);
}catch (Exception e){
e.printStackTrace();
}finally {
try {
if(baos != null){
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(bis != null){
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
4.JDK9变化
-
扩展类加载器被重命名为PlatformClassLoader,可通过ClassLoader.getPlatformClassLoader()获得。
-
系统类加载器、平台类加载器不在继承于URLClassLoader,转而如下:
AppClassLoader、PlatformClassLoader、BootClassLoader 继承于
BuiltinClassLoader 继承于
SecureClassLoader 继承于
ClassLoader
这里的BootClassLoader 就是 BootStrapClassLoader 但是有java类库的支持。
-
双亲委派机制变化:这里依然是三层模型向上委托;但是:由于模块化的区分,将系统类库分为了几个模块,分别由这三个加载器加载,所以在加载类的时候会先判断这个类是否是系统类库中的类同时又是否是归这个加载器加载,如果是则直接由该加载器加载。
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if(bis != null){
bis.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
## 4.JDK9变化
- 扩展类加载器被重命名为PlatformClassLoader,可通过ClassLoader.getPlatformClassLoader()获得。
- 系统类加载器、平台类加载器不在继承于URLClassLoader,转而如下:
AppClassLoader、PlatformClassLoader、BootClassLoader 继承于
BuiltinClassLoader 继承于
SecureClassLoader 继承于
ClassLoader
这里的BootClassLoader 就是 BootStrapClassLoader 但是有java类库的支持。
- 双亲委派机制变化:这里依然是三层模型向上委托;但是:由于模块化的区分,将系统类库分为了几个模块,分别由这三个加载器加载,所以在加载类的时候会先判断这个类是否是系统类库中的类同时又是否是归这个加载器加载,如果是则直接由该加载器加载。