什么是类加载?
虚拟机把描述类的信息从字节码文件加载到JVM的内存中,并对数据进行验证,准备和解析,最终是虚拟机可以直接使用
特点:
java的动态扩展就依赖于运行时期可以被动态加载和动态连接
生命周期
加载过程七个部分:加载,验证,准备,解析,初始化,使用和卸载
类加载时机
- 使用new关键字实例化对象,getstatic,putstatic(读取后者设置一个类的静态字段的时候,除了final修饰)
- invokestatic,如果类没有进行实例化,则需要触发其初始化)
- 使用java.lang.reflect包的方法进行反射调用的时候
- 如果父类还没有进行初始化,触发其初始化
- 启动虚拟机时,用户需要指定一个需要执行的主类
- 当使用动态语言支持时
通过子类引用父类的静态字段,不会导致子类初始化(使用的是SuperClass.value)
通过数组定义来引用类,不会触发此类的实例化
如果value是一个常量,
类加载过程
加载
- 通过一个类的全限定名来获取此类的二进制字节流
- 将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构。
- 在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。
连接-验证
-
文件格式验证
第一阶段要验证二进制字节流是否符合Class文件格式的规范,确保能被虚拟机处理。主要包括以下验证点:
是否以魔数 0xCAFEBABE 开头。(每个Class文件的头4个字节称为魔数,是一个16进制的固定值,它的作用就是确保这个Class文件能被虚拟机接受)
主、次版本号是否在当前虚拟机的处理范围中(紧接着魔数后面的第5,6字节代表次版本号,第7,8字节代表主版本号)。
常量池中的常量是否有不被支持的常量类型(依据常量的tag值)。
等等,还有其他很多验证,不再一一说明。这一阶段的验证主要是针对二进制字节流进行的,验证完成之后,字节流会进入内存中的方法区进行存储。所以后面的三个验证阶段不再直接操作二进制字节流。 -
元数据验证
第二阶段是对字节码描述的信息进行语义分析,保证其描述的信息符合Java语言规范。主要包括以下验证点:
这个类是否有父类(除了Object类,所有类都应该有父类)。
这个类是否继承了不允许被继承的类(被final修饰的类不可被继承)。
是否实现了其父类或接口要求实现的所有方法。
类中的字段、方法是否与父类产生矛盾(如覆盖了父类的final字段,或者重写、重载不符合规范)。 -
字节码验证
第三阶段主要是对类的方法体进行验证,确保程序语义是合法的、符合逻辑的。
保证数据的定义和使用相匹配,如定义int类型数据,使用时不能以long型操作。
保证跳转指令不会跳转到方法体以外的字节码指令上。
保证方法体中的类型转换是有效的。如可以把子类对象赋值给父类引用,但是父类不可以直接赋值给子类(必须强转)或其他不相干的类型。 -
符号引用验证
-
后一个阶段的验证发生在符号引用转换为直接引用的时候。实际的转换动作,发生在后面的解析阶段。主要对类自身以外的信息(常量池中的各种符号引用)进行匹配性的校验。
验证阶段是非常重要但是非必要的一个阶段。如果确保代码对程序运行期没有影响,则可以通过 -Xverify:node 参数关闭大部分的验证,以缩短类加载的总时间。
连接-准备
为类变量分配内存并设置类变量初始值
各数据类型的初始默认值如下:
数据类型 | 默认值 |
---|---|
short | (short)0 |
int | 0 |
long | 0L |
float | 0.0f |
double | 0.0d |
char | ‘u0000’ |
byte | byte(0) |
boolean | false |
reference | null |
连接-解析
将常量池内的符号引用替换为直接引用
符号引用是用一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义的定位到目标即可(前面JVM的模型中,也提到了符号引用,它存在于常量池中,包括类和接口的全限定名、字段的名称和描述符、方法的名称和描述符)。看概念可能比较抽象,可以理解为它就是一个代号,就像你有一个大名,同时也有一个小名,但是不管怎么叫指代的都是你本人。
直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。
解析动作主要针对类或接口、字段、类方法、接口方法、方法属性、方法句柄、调用点限定符7类符号引用。此处分别介绍一下前四种的解析过程。
uuv
初始化
类变量和static代码块中的变量赋初值
类构造器
关于类的初始化顺序
掌握了类的加载机制之后,我们再来理解Java类的初始化顺序就非常简单了,如果一个子类继承了父类,在实例化子类的时候,整个顺序如下:
父类静态块.
子类静态块.
父类成员变量初始化.
父类构造块.
父类构造函数.
子类成员变量初始化.
子类构造块.
子类构造函数.
类加载器
类加载器
java8
Bootstrap ClassLoader | JAVA_HOME/jre/lib | 无法直接访问 |
---|---|---|
Extension ClassLoader | JAVA_HOME/jre/lib/ext | 上级为Bootstrap,显示为null |
Aplication ClassLoader | classpath | 上级为Extention |
自定义加载器 | 自定义 | 上级为Application |
什么时候使用自定义加载器
想加载非classpath随意路径中的类文件
都是通过接口来使用实现,希望解耦时,常用在框架设计
这些类希望予以隔离,不同应用的同名类都可以加载,不冲突,常用于Tomcat容器
步骤:
1.继承ClassLoader类
2.要遵从双亲委派机制,重写findClass方法
3.读取类文件的字节码
4.调用父类的defineClass方法来加载类
5.使用者调用类加载器的loadClass方法
6.什么是双亲委派模型?
如果一个类加载器收到了类加载的请求,它首先不会区加载这个类,而是把请求委派给父类加载器去完成。所有的请求都会传送到顶层的启动类加载器中,只有当父加载器无法完成加载请求时(没找到所需的类),子加载器才会尝试加载这类