03类的生命周期

类的生命周期包括:加载、连接(验证、准备、解析)、初始化、使用、卸载。

1.加载

1.加载阶段第一步是通过类加载器根据类的全限定名,通过不同渠道(最常见的是磁盘的字节码文件,就是javac编译后的class文件,第二常见的是Springboot在程序运行时动态代理生成的内容)以二进制流的方式获得字节码信息。程序员可以通过代码拓展的不同渠道(比如有些公司需要将你的类保存到数据库中持久化,就是通过把这里进行拓展实现的)

2.在完成类加载器的加载后,java虚拟机会将字节码中的信息保存到内存的方法区。生成一个InstanceKlass对象,保存类的所有信息。里面还包括实现特定功能,比如多态的信息。

3.同时,Java虚拟机还会在堆中生成一份和方法区中数据类似的java.lang.Class对象。(因为InstanceKlass对象是C++编写的,无法通过java代码获取到其对象,所以在堆中创建了这个Class对象,方便程序员通过java代码获取类的信息以及存储静态字段的数据。)

推荐使用 JDK自带的hsdb工具查看Java虚拟机内存信息。不同办法的启动方法不同,高版本(JDK17)的启动方法是到JDK的bin目录,通过cmd命令行,输入jhsdb hsdb来启动,低版本可自行到网上找启动方法。

2.连接

连接分为验证、准备、解析三个阶段。

2.1验证

负责验证内容是否满足《Java虚拟机规范》。这个阶段一般不需要程序员参与。

主要包含如下四部分,具体详见《Java虚拟机规范》:
1.文件格式验证,比如文件是否以0xCAFEBABE开头,主次版本号是否满足当前Java虚拟机版本要求。
2.元信息验证,例如类必须有父类(super不能为空)。
3.验证程序执行指令的语义,比如方法内的指令执行中跳转到不正确的位置。
4.符号引用验证,例如是否访问了其他类中private的方法等。

2.2准备

准备阶段为静态变量(static修饰的变量)分配内存并设置初始值。

准备阶段只会给静态变量赋初始值,而每一种基本数据类型和引用数据类型都有其初始值。

final修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值。

2.3解析

解析阶段主要将常量池中的符号引用替换成直接引用。

常量池中的符合引用就是使用编号来访问常量池中的内容。

直接引用不在使用编号,而是使用内存中地址进行访问具体的数据。

3.初始化阶段

初始化阶段会执行静态代码块中的代码,并为静态变量赋值。初始化阶段会执行字节码中的client部分的内容。clinit方法中的执行顺序与Java中编写的顺序是一致的。

上面的clinit文件中的方法如下:

以下几种方式会导致类的初始化:

  • 访问一个类的静态变量或者静态方法(如果变量是final修饰的并且等号后边是常量,不会触发初始化)
  • 调用Class.forName(String ClassName)方法
  • new一个类的对象时
  • 执行Main方法中的当前类

比如如下内容:

进入main函数,触发初始化,打印D,然后正常执行打印A,然后new了Test1,触发Test1的初始化,打印C,再打印B,第二次new,再次打印CB,最终是DACBCB。

需要注意clinit指令在特定情况下不会出现,比如:如下几种情况是不会进行初始化指令执行的。
1.无静态代码块且无静态变量赋值语句。
2.有静态变量的声明,但是没有赋值语句。
3.静态变量的定义使用final关键字,这类变量会在准备阶段直接进行初始化。

直接访问父类的静态变量,不会触发子类的初始化。子类的初始化clinit调用之前,会先调用父类的clinit初始化方法。

调用B02,会初始化,但是B02继承自A02,所以会先调用A02的初始化,再调用B02的初始化,最终a等于2。

4.使用和卸载

使用阶段不用细讲,就是在java代码运行中使用了这些类,卸载阶段会在后续的垃圾回收部分讲解。

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注