类加载的本质 将描述里的数据从class文件加载到内存 对数据进行校验 转换 解析和初始化 最终形成可被虚拟机直接使用的java使用类型
整个流程一句话概括:将类的.class文件中的二进制数据读取到内存中,将其放在方法区,然后再堆区创造一个j.l.Class对象 用来封装类在方法区的数据结构
五个步骤 加载 验证 准备 解析 初始化
一 加载
将外部class文件转化为二进制流 加载到java虚拟机 存储到方法区内
1通过类的全限定名,来获悉定义此类的二进制字节流
2 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3 在内存中生成一个代表这个类的Java.Lang.Class文件 作为方法区该类的各种数据的访问入口
数组类通过java虚拟机直接创建 不通过类加载器
二验证
确保加载进来的class文件包含的信息符合java虚拟机的要求 会进行4个阶段的验证
文件格式验证 验证.class文件字节流是否符合class文件的格式的规范,并且能够被当前版本的虚拟机处理
元数据验证 主要是对字节码描述的信息进行语义分析,以保证其描述的信息符合java语言规范的要求,比如说验证这个类是不是有父类,类中的字段方法是不是和父类冲突等等。
字节码验证 这是整个验证过程最复杂的阶段,主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在元数据验证阶段对数据类型做出验证后,这个阶段主要对类的方法做出分析,保证类的方法在运行时不会做出威海虚拟机安全的事。
符号引用验证 它是验证的最后一个阶段,发生在虚拟机将符号引用转化为直接引用的时候。主要是对类自身以外的信息进行校验。目的是确保解析动作能够完成。
三 准备
为类变量分配内存 设置类变量的初始值
为类的static变量在方法区中分配内存 将上述变量的初始值设为0(非开发者定义初始值) 实例变量不在该阶段分配内存 若类变量被定义为常量则直接赋予开发者定义的值
四 解析
将常量池内符号引用转为直接引用
符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,只要是能无歧义的定位到目标就好,就好比在班级中,老师可以用张三来代表你,也可以用你的学号来代表你,但无论任何方式这些都只是一个代号(符号),这个代号指向你(符号引用)
直接引用:直接引用是可以指向目标的指针、相对偏移量或者是一个能直接或间接定位到目标的句柄。和虚拟机实现的内存有关,不同的虚拟机直接引用一般不同。
解析对象包括类 接口 方法(类方法 接口方法 方法类型 方法句柄) 字段
实例变量不在该阶段分配内存 因为类方法和私有方法符合“编译器可知 运行期不可变”的 即不会被重写或者继承 所以适合在类加载过程进行解析 若类变量被定义为常量则直接赋予开发者定义的值
五 初始化
初始化类变量 静态语句块
生成类构造器<clinit>()方法 即合并所有类变量(static)和静态语句块 顺序为定义的顺序 实例变量又类的构造函数<init>()赋值 再执行<clinit>()方法
注 类构造器<clinit> 区别于类构造函数 <init> 不需要调用父类构造器 子类 clinit执行前 父类 clinit一定执行 即虚拟机执行的第一个clinit 是java.lang.Object
静态语句块只可赋值 不可访问
接口与类不同 执行子接口的 clinit 不需要执行父接口的 clinit
JVM初始化步骤
1、假如这个类还没有被加载和连接,则程序先加载并连接该类
2、假如该类的直接父类还没有被初始化,则先初始化其直接父类
3、假如类中有初始化语句,则系统依次执行这些初始化语句
类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种:
创建类的实例,也就是new的方式
访问某个类或接口的静态变量,或者对该静态变量赋值
调用类的静态方法
反射(如 Class.forName(“com.shengsiyuan.Test”))
初始化某个类的子类,则其父类也会被初始化
Java虚拟机启动时被标明为启动类的类( JavaTest),直接使用 java.exe命令来运行某个主类
Q 什么时候启动类加载器
其实,类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。
Q 从哪个地方去加载.class文件
1)本地磁盘
(2)网上加载.class文件(Applet)
(3)从数据库中
(4)压缩文件中(ZAR,jar等)
(5)从其他文件生成的(JSP应用)