目录
类加载就是当字节码文件有了过后,进行下一步工作,不管是创建对象,还是干其他事情,都是有一个类加载器,把字节码加载到堆,并生成一个Class对象,同时在方法区生成类的二进制数据(元数据)。
基本说明
举例说明:
静态加载,编译的时候,也就是还没运行的时候,加载相关的类,如果没有就会报错,这种静态加载,依赖性是比较强的。
case "2" 里会去加载Person类,但是因为这里用的是反射,反射是动态加载,因为没有执行到这里来,不会真的去加载Person类
而case "1" 的加载类是静态加载,依赖性很强,在编译的时候就要把它加载进去,看他符不符合语法的规范,如果不符合,直接报错,或者Dog类写的有问题也会报错。
因为new Dog()是静态加载,因此必须编写Dog类
而Person类是动态加载,所以没有编写Person类也不会报错,只有当动态加载该类时,才会报错。
这时候我们在里面编写一个Dog类
这时候就不会进行报错了。
那么什么时候会导致动态加载报错?只有我们输入2的时候,走到这段代码的时候,它会发现根本没有Person,这个时候就会报出异常。
所以动态加载可以认为是一种延时加载,静态加载在编译的时候就已经在加载了。
这是我们输入2的时候,才走到这段代码,会发现没有Person,这时候就会报错。解决这种动态报错,就是我们需要写一个Person类
那么这时候找到Person类,用反射机制创建一个Person对象,然后这个Method对象用反射机制调用也是OK的
总结:
总的来说,静态加载就是在编译的时候就会加载相关的类,它会检查你的类是否正确,各方面会去检测。
而动态加载是执行到这一段的时候才会加载,如果运行的时候没有用到这个类,即使不存在也不会报错,这样就降低了一定的依赖性。
类加载过程流程图
第一步是先写源代码,源代码编译就得到字节码文件,字节码文件运行,运行的时候就会把字节码文件进行类加载,这里分成三个阶段。
第一个就是加载(Loading)
加载完的第二个阶段就是进入连接(Linking),连接里面又分成验证,准备,解析。
验证阶段主要是对文件进行安全的校验,比如说文件格式是否正确,再比如说元数据是否正确,字节码等是否正确,符号引用是否OK。
准备阶段主要是对静态变量进行分配内存,默认初始化,而且是针对静态变量而言。解析阶段主要的功能是虚拟机会将常量池中的符号引用替换为直接引用。
下一步才会进行初始化,到这个阶段,才会真正的执行在类中去定义的Java代码,比如说静态代码块等。
当我们加载完成以后,在内存中会产生2个重要部分。一个是方法区,会把字节码,以二进制的形式保存起来。这个就是元数据,真正定义的方法,访问权限,变量详细信息都是在这里。同时在堆区生成字节码对应的数据结构,就是类的Class对象,这里面就是数据结构,是数据的访问入口。
类加载各阶段完成的任务
加载阶段
加载和连接阶段其实是由JVM机来控制的,我们是无法控制的,初始化是我们程序员可以指定的。
连接阶段-验证
连接阶段-准备
ClassLoad02.java
class A{
//属性-成员变量-字段
//分析类加载的链接阶段-准备 属性是如何处理的
/**
* 1. n1是个实例属性,不是静态变量,因此在准备阶段是不会分配内存的
* 2. n2是个静态变量,这里就会分配内存,n2是默认初始化,这个默认初始化是0,还没到20,他在初始化这个阶段才会真正执行代码,但是内存确实分配了
* 3. n3是个static final 是个常量,它和静态变量就不一样了,一但赋值就不变,所以n3在准备阶段就是30了,不是0.
*/
public int n1 = 10;
public static int n2 = 20;
public static final int n3 = 30;
}
A类里面写了3个属性,n1,n2,n3。
那么这3个属性在类的准备阶段是怎么处理的呢?
n1是个实例属性,不是静态变量,因此在准备阶段是不会分配内存的
n2是个静态变量,这里就会分配内存,n2是默认初始化,这个默认初始化是0,还没到20,他在初始化这个阶段才会真正执行代码,但是内存确实分配了
n3是个static final 是个常量,它和静态变量就不一样了,一但赋值就不变,所以n3在准备阶段就是30了,不是0。
连接阶段-解析
Initialization(初始化)
package com.godairo.reflection.classload_;
/**
* @author GodAiro
* Creation Time: 2022/7/18 10:34.
* 演示类加载-初始化
*/
@SuppressWarnings({"all"})
public class ClassLoad3 {
public static void main(String[] args) {
/**
* 分析
* 1. 加载B类,,并生成B类的class对象
* 2. 链接 num=0
* 3. 初始化阶段
* 依次收集类中的所有静态变量的赋值动作和静态代码块中的语句,按顺序收集,并进行合并
* clinit(){
* System.out.println("B 静态代码块被执行");
* num=300;
* num=100;
* }
* 合并后:num=100,num=300就没什么意义了。
*/
System.out.println(B.num); //这里需要注意的是 我们是B.num,没有去new一个B对象,所以构造器是不会执行的
}
}
class B{
static{
System.out.println("B 静态代码块被执行");
num=300;
}
static int num=100;
public B(){
System.out.println("B() 构造器被执行");
}
}
看代码中注释
这是new一个B出来
System.out.println("=======new B()之后=======");
B b = new B();
System.out.println(B.num);
构造器也一起执行了。