理一下,java 编译后的字节码文件,我们已经熟悉了 字节码文件长什么样,字节码文件中有哪些内容,那么下一步就是使用类加载器 把字节码文件加载到JVM中
类的生命周期
类的生命周期是 JVM类加载的基础。
加载
所谓加载,就是 根据类的全限定名(包名+类名)以二进制流的方式 将字节码文件信息 加载到 JVM中。
注意这里的源头不固定:就是说 我要加载的 字节码文件内容,但是这个内容是哪里来的不一定 可以从磁盘上来 有可能从网络上来,也有可能在JVM内部动态生成(动态代理技术,总之源头是很灵活的。
然后这些字节码文件内容 加载到了JVM中,问题来了 JVM将它们放在哪里?
这里引入一个虚拟概念叫 ”方法区“ 这些加载进来的 类 数据被放在一个虚拟空间 方法区里面
最后,加载进来之后,还有最后一步:
在JVM 堆中生成一个代表这个类的 java.lang.class对象,作为方法区这个类的各种数据(基础信息 常量池 字段 熟悉 方法 等)的方法入口
所以这里大家可以回想一下 为什么我们在使用发射的时候 可以通过 java类的java.lang.class对象 来获取类的名称 方法 属性等等, 就是因为在类加载过程中 JVM在堆上面创建了这个对象
验证
作为 连接 的第一阶段:
这个阶段就是各种校验 你加载进来的 字节码文件是不是符合各种java规范,这个阶段不需要程序员操心
准备
准备阶段 为 类变量(static 静态变量) 赋零值。
这里要注意 是赋一个零值,而不是赋值。(赋值要等到初始化阶段才开始)
解析
解析这一步是 将常量池里面的符号引用 转换为 JVM内存中的直接引用。
符号引用是常量池中人为设计的符号,通过符号去找到常量池中的某个值, 内存引用 就是直接通过内存地址找到这个值
初始化
经过了验证和解析 到了初始化,这个阶段和程序员就很紧密了:
静态变量赋值: 在初始化阶段,虚拟机会按照代码中的顺序为类的静态变量赋初值。这些静态变量可以是基本数据类型或引用类型。如果静态变量被声明为final,并且它的值是在编译时可以确定的常量,那么这个值将会被直接放入常量池,而不进行初始化。
静态代码块: 如果类中包含静态代码块,它会在静态变量赋值之后执行。静态代码块中的代码会按照在类中的声明顺序执行。这通常用于在类加载时进行一些初始化操作,例如静态资源的加载、静态变量的复杂初始化等。
实例变量赋值和实例代码块: 在初始化阶段,如果有实例变量被赋予初始值,这些实例变量的赋值操作会在执行构造函数之前进行。同时,如果有实例代码块(非静态代码块),它们也会在构造函数执行之前按照在类中的声明顺序执行。这一步骤是在类的初始化和对象的初始化之间的。
构造函数: 最后,执行构造函数。在初始化阶段,会调用对象的构造函数来完成对象的初始化。构造函数中的代码将在实例变量赋值和实例代码块执行之后执行。
需要注意的是,这些步骤并不是一成不变的,具体的初始化顺序还会受到访问权限、继承关系、static关键字的影响。例如,父类的初始化先于子类,子类的静态代码块和静态变量初始化先于实例变量和实例代码块。
总体而言,初始化顺序可以归纳为:静态变量赋值和静态代码块 -> 父类的初始化 -> 实例变量赋值和实例代码块 -> 子类的初始化 -> 构造函数。
这就是类的加载过程生命周期,后面还有类的使用和卸载 这个放到后面说