👽System.out.println(“👋🏼嗨,大家好,我是代码不会敲的小符,双非大四,Java实习中…”);
📚System.out.println(“🎈如果文章中有错误的地方,恳请大家指正!共同进步,共同成长✊”);
🌟System.out.println(“💡如果文章对您有所帮助,希望您可以三连支持一下博主噢🔥”);
🌈System.out.println("🚀正在完成计划中:接下来的三个月里,对梦想的追逐 ");
文章
- 类的生命周期
- 加载阶段
- 连接阶段
- 验证:验证内容是否满足《Java虚拟机规范》
- 准备:给静态变量赋初始值
- 解析:将常量池中的符号引用替换成指向内存的直接引用
- 初始化阶段
- 总结回答
类的生命周期
描述了一个类加载、使用、卸载的整个过程
生命周期: 加载、连接、初始化、使用、卸载
- 连接:验证、准备、解析
加载阶段
- 加载阶段:通过类加载器把字节码信息(类的信息)加载到内存中,Java虚拟机在方法区和堆区各保存一个对象
- 第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息。程序员可以使用Java代码拓展的不同的渠道
- 本地文件:磁盘上的字节码文件
- 动态代理生成:程序使用动态代理生成
- 通过网络传输的类:早期的Applet技术使用
- 类加载器在加载完类之后,Java虚拟机会将字节码中的信息保存到方法区中。
- 生成一个InstanceKlass对象,保存类的所有信息,里边还包含实现特定功能比如多态的信息。
- 同时Java虚拟机还会再堆中生成一份与方法区中数据类似的java.lang.Class对象
- 作用是在Java代码中去获取类的信息以及存储静态字段的数据(JDK8之后)
思考1:为什么需要在方法区和堆区都要创建一个对象?
- InstanceKlass对象是使用C++编写的对象,Java代码一般不能直接操作
- 堆区中的Java.lang.Class是使用Java包装后的对象,Java程序员可以获取到
- 字段是少于InstanceKlass对象的字段的(控制开发者访问数据的范围,保证安全性)
思考2:在类的加载阶段怎么查看内存中的对象?
- 使用jdk自带的hsdb工具查看Java虚拟机内存信息。工具位于JDK安装目录lib文件夹下的sa-jdi.jar中。
- 启动命令:java -cp sa-jdi.jar sun.jvm.hotspot.HSDB
- jps 是 Java 虚拟机自带的一个小工具,用于列出当前系统上所有正在运行的 Java 进程的主类名和它们各自的 Java 虚拟机参数。可以在命令行中直接运行 jps 命令来查看当前系统上正在运行的 Java 进程列表。
连接阶段
验证:验证内容是否满足《Java虚拟机规范》
- 文件格式验证
- 格式是否正确:文件以0xcafebabe开头
- 主次版本号是否满足当前Java虚拟机的要求【主版本号不能高于运行环境主版本号;如果主版本号相同,副版本号也不能超过】
- 元信息验证
- 比如:super不能为空,一定有父类Object
- 验证程序执行指令的语义
- 比如:方法内的指令执行中跳转到不正确或不存在的位置
- 符号引用验证
- 比如:是否访问了其他类中private的方法等
准备:给静态变量赋初始值
- 为静态变量分配内存并设置初始值,每一种基本类型和引用类型都有初始值
数据类型 | 初始值 | 数据类型 | 初始值 |
---|---|---|---|
int | 0 | byte | 0 |
— | — | — | — |
long | 0L | boolean | false |
— | — | — | — |
short | 0 | double | 0.0 |
— | — | — | — |
char | ‘\u0000’ | 引用数据类型 | null |
注意: final修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值
解析:将常量池中的符号引用替换成指向内存的直接引用
- 符号引用:在字节码文件中使用编号来访问常量池中的内容,并不是直接引用
- 直接引用:在字节码文件中不再使用编号,而是使用内存中地址进行访问具体的数据
初始化阶段
初始化阶段会执行静态代码块中的代码,并为静态变量赋值
初始化阶段会执行字节码文件中clinit部分的字节码指令
- clinit方法中的执行顺序与Java中编写的顺序一致
添加Java虚拟机参数 -XX:+TraceClassLoading 开启功能打印出加载并初始化的类
怎样会触发类的初始化操作?
- 访问一个类的静态变量或者静态方法
- 注意:静态变量是final修饰的并且等号右边是常量不会触发初始化(在连接的准备阶段就会被赋值-初始化)
- 调用Class.forName(String className)
- new 一个该类的对象时
- 执行Main方法的当前类
clinit指令在特定情况下不会出现
- 没有静态代码块且没有静态变量赋值语句
- 有静态变量的声明,但没有赋值语句(初始化目的是为了给静态变量赋值)
- 静态变量是final修饰的并且等号右边是常量(在连接的准备阶段就会被赋值-初始化)
注意:
- 访问父类的静态变量。只初始化父类
- 数组的创建不会导致数组中的类进行初始化:new的是数组
- final修饰的变量右边不是一个单纯的常量,如果需要执行指令才能得出结果,则会执行clinit方法进行初始化
总结回答
类的生命周期有加载、连接、初始化、使用、卸载
加载阶段是通过类加载器把字节码信息加载到内存中,转换成内部可识别的信息,存放在方法区和堆上。字节码信息可以通过本地文件、网络传输和动态代理生成
连接阶段又被分为验证、准备和解析。
验证:是否符和Java虚拟机规范,文件格式、版本号、指令信息等等
准备:是为静态变量分配内存赋初始值
解析:是将常量池中的符号引用替换成指向内存的直接引用
初始化阶段会执行静态代码块并为静态变量赋值
使用阶段是程序可以使用类来创建对象、调用方法
卸载的话就是如果一个类不再被使用,且没有任何引用指向它,那么这个类就可能会被垃圾回收,释放内存空间