JVM-类的生命周期

类的生命周期概述

类的生命周期描述了一个类加载、使用、卸载的整个过程。整体可以分为:

  • 加载
  • 连接,其中又分为验证、准备、解析三个子阶段
  • 初始化
  • 使用
  • 卸载

 加载阶段

加载(Loading)阶段第一步是类加载器根据类的全限定名通过不同的渠道以二进制流的方式获取字节码信息,程序员可以使用Java代码拓展的不同的渠道。

  • 从本地磁盘上获取文件
  • 运行时通过动态代理生成,比如Spring框架
  • Applet技术通过网络获取字节码文件

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

 

 Java虚拟机同时会在堆上生成与方法区中数据类似的java.lang.Class对象,作用是在Java代码中去获取类的信息以及存储静态字段的数据(JDK8及之后)。

 连接阶段

连接阶段分为三个子阶段:

  • 验证,验证内容是否满足《Java虚拟机规范》。
  • 准备,给静态变量赋初值。
  • 解析,将常量池中的符号引用替换成指向内存的直接引用。

 

 验证

验证的主要目的是检测Java字节码文件是否遵守了《Java虚拟机规范》中的约束。这个阶段一般不需要程序员参与。主要包含如下四部分,具体详见《Java虚拟机规范》:

1、文件格式验证,比如文件是否以0xCAFEBABE开头,主次版本号是否满足当前Java虚拟机版本要求。

2、元信息验证,例如类必须有父类(super不能为空),Java默认所有类都继承了Object这个顶级父类。

3、验证程序执行指令的语义,比如方法内的指令执行中跳转到不正确的位置。

4、符号引用验证,例如是否访问了其他类中private的方法等。

 对版本号的验证,在JDK8的源码中如下:

编译文件的主版本号不能高于运行环境主版本号,如果主版本号相等,副版本号也不能超过。

准备 

准备阶段为静态变量(static)分配内存并设置初值,每一种基本数据类型和引用数据类型都有其初值。

数据类型

初始值

int

0

long

0L

short

0

char

‘\u0000’

byte

0

boolean

false

double

0.0

引用数据类型

null

 如下代码在准备阶段会为value分配内存并赋初值为0,在初始化阶段才会将值修改为1。

public class Student{

public static int value = 1;

}

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

在上面的例子中,变量加上final进行修饰,在准备阶段value值就直接变成1了,因为final修饰的变量后续不会发生值的变更。

 

 从字节码文件也可以看到,编译器已经确定了该字段指向了常量池中的常量2:

import java.io.IOException;

public class HsdbDemo {
    public static final int i = 2;

    public HsdbDemo() {
    }

    public static void main(String[] args) throws IOException, InstantiationException, IllegalAccessException {
        new HsdbDemo();
        System.out.println(2);
        System.in.read();
    }
}

 解析

解析阶段主要是将常量池中的符号引用替换为直接引用,符号引用就是在字节码文件中使用编号来访问常量池中的内容,直接引用就是指向具体的内存地址。

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

初始化阶段

初始化阶段会执行字节码文件中clinitclass init 类的初始化)方法的字节码指令,包含了静态代码块中的代码,并为静态变量赋值。

如下代码编译成字节码文件之后,会生成三个方法:

  • init方法,会在对象初始化时执行
  • main方法,主方法
  • clinit方法,类的初始化阶段执行
public class Demo1 {
    public static int value = 2;

    public Demo1() {
    }

    public static void main(String[] args) {
    }

    static {
        value = 1;
    }
}

继续来看clinit方法中的字节码指令:

1、iconst_1,将常量1放入操作数栈。此时栈中只有1这个数

 2、putstatic指令会将操作数栈上的数弹出来,并放入堆中静态变量的位置,字节码指令中#2指向了常量池中的静态变量value,在解析阶段会被替换成变量的地址。

 3、后两步操作类似,执行value=2,将堆上的value赋值为2。

如果将代码的位置互换:就会先执行静态代码块得初始化,再执行显式赋值的值

public class Demo1 {
    static {
        value = 2;
    }
   
    public static int value = 1;
   
    public static void main(String[] args) {

    }
}

因为字节码指令的位置也会发生变化,这样初始化结束之后,最终value的值就变成了1而不是2。

 触发类的初始化

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

1.访问一个类的静态变量或者静态方法,注意变量是final修饰的并且等号右边是常量不会触发初始化,因为此时可以直接从常量池中找到这个变量,不需要访问类信息。

2.调用Class.forName(String className)。

3.new一个该类的对象时。

4.执行Main方法的当前类。

 面试题1

public class Test1 {
    public static void main(String[] args) {
        System.out.println("A");
        new Test1();
        new Test1();
    }

    public Test1(){
        System.out.println("B");
    }

    {
        System.out.println("C");
    }

    static {
        System.out.println("D");
    }
}

分析步骤:

1、执行main方法之前,先执行clinit指令。执行静态代码块的初始化,指令会输出D

 2、执行main方法的字节码指令。指令会输出A

 3、创建两个对象,会执行两次对象初始化的指令。

 

这里会输出CB,源代码因为代码块的执行在构造器的前面执行,输出C这行,被放到了对象初始化的一开始来执行。所以最后的结果应该是DACBCB

 面试题2

public class Demo01 {
    public static void main(String[] args) {
        new B02();
        System.out.println(B02.a);
    }
}

class A02{
    static int a = 0;
    static {
        a = 1;
    }
}

class B02 extends A02{
    static {
        a = 2;
    }
}

分析步骤:

1、调用new创建对象,需要初始化B02,优先初始化父类。

2、执行A02的初始化代码,将a赋值为1。

3、B02初始化,将a赋值为2。

new B02();注释掉会怎么样?

分析步骤:

1、访问父类的静态变量,只初始化父类。

2、执行A02的初始化代码,将a赋值为1。

补充练习题

 数组的创建不会导致数组中元素的类进行初始化。

public class Test2 {
    public static void main(String[] args) {
        Test2_A[] arr = new Test2_A[10];

    }
}

class Test2_A {
    static {
        System.out.println("Test2 A的静态代码块运行");
    }
}

 通过查看字节码文件,我们发现只初始化了Object这个类

 final修饰的变量如果赋值的内容需要执行指令才能得出结果,会执行clinit方法进行初始化。

public class Test4 {
    public static void main(String[] args) {
        System.out.println(Test4_A.a);
    }
}

class Test4_A {
    public static final int a = Integer.valueOf(1);

    static {
        System.out.println("Test3 A的静态代码块运行");
    }
}

clinit不会执行的几种情况

1.无静态代码块且无静态变量赋值语句。

2.有静态变量的声明,但是没有赋值语句。

3.静态变量的定义使用final关键字,这类变量会在准备阶段直接进行初始化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/358775.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c++|类和对象(下)

一、再谈构造函数 1.1初始化列表 在上一章节中,对于类我们可以形象的比喻为房子的图纸,而真正对于类的初始化可以比喻为建造了一个实体房子,即创建对象,对于房子中的各个房间都有特定的位置构造,那么对于类中的成员变…

一张图搞清楚HTTP状态码

HTTP状态码的基本概念 在客户端和服务器连接交互的时候,一般是客户端先给服务器发送请求,然后服务器返回结果。客户端和服务器之间的交互非常频繁,涉及到很多种不同类型的操作,大多数的时候服务器能成功返回结果,有时…

DC-磁盘配额(23国赛真题)

2023全国职业院校技能大赛网络系统管理赛项–模块B:服务部署(WindowServer2022) 文章目录 DC-磁盘配额题目配置步骤验证查看DC2驱动器C:\的磁盘配额,限制磁盘空间,警告等级等配置 DC-磁盘配额 题目 在DC2驱动器C:\上…

全新魅思V20正规视频影视系统源码/APP+H5视频影视源码

全新魅思V20正规视频影视系统源码,APPH5视频影视源码。会员花费三千购入的,具体搭建教程放压缩包了! 有兴趣的下载自行研究吧,搭建一共要用到3个域名,可以拿二级域名搭建。

PMP重考流程与费用

很多参加PMP考试的考生都经历过辛勤的学习过程,特别是那些在毕业几年后才开始备考的人。对大多数人来说,PMP考试都是一项艰难的任务。尽管PMP考试的平均通过率超过90%,但仍然有些人无法在首次尝试中通过考试。那么,如果一次没有通…

Linux ---- Shell编程之正则表达式

一、正则表达式 ​ 由一类特殊字符及文本字符所编写的模式,其中有些字符(元字符)不表示字符字面意义,而表示控制或通配的功能,类似于增强版的通配符功能,但与通配符不同,通配符功能是用…

Apache POI 处理excel文件 记录用法

Apache POI 写excel public static void write() throws IOException {//再内存中创建了一个Excel文件XSSFWorkbook excel new XSSFWorkbook();//创建一个sheet页XSSFSheet sheet excel.createSheet("info");//这里创建行对象,这里的rownum 是从0开始的,类似于数…

Redis学习——高级篇①

Redis学习——高级篇① Redis7高级之单线程和多线程(一) 一、Redis单线程VS多线程1.Redis的单线程部分1.1 Redis为什么是单线程?1.2 Redis所谓的“单线程”1.3 Redis演进变化1.3.1 Redis 3.x 单线程时代性能很快的原因1.3.2…

Python处理图片生成天际线(2024.1.29)

1、天际线简介 天际线(SkyLine)顾名思义就是天空与地面的边界线,人站在不同的高度,会看到不同的景色和地平线,天空与地面建筑物分离的标记线,不得不说,每天抬头仰望天空,相信大家都可…

窥探向量乘矩阵的存内计算原理—基于向量乘矩阵的存内计算

在当今计算领域中,存内计算技术凭借其出色的向量乘矩阵操作效能引起了广泛关注。本文将深入研究基于向量乘矩阵的存内计算原理,并探讨几个引人注目的代表性工作,如DPE、ISAAC、PRIME等,它们在神经网络和图计算应用中表现出色&…

三维模型设计新纪元:3D开发工具HOOPS在机械加工行业的应用与优势

在当今快速发展的科技时代,机械加工行业正经历着巨大的变革,而HOOPS技术正是其中一项重要的创新。HOOPS技术不仅仅是一种用于处理和可视化计算机辅助设计(CAD)数据的工具,更是机械加工领域中提升效率、优化设计的利器。…

SI3933 15k-125kHZ低频唤醒开发技术资料

SI3933完美兼容:AS3933.PAN3501,GC3933Si3933 是一款三通道的低功耗 ASK 接收机,可用于检测 15kHz-150kHz 低频载波频率的数字信号,并产生唤醒信号。内部集成的校验器用于检测 16 位或 32 位曼彻斯特编码的唤醒向量,且支持两次重复…

超声波自动气象站是什么?

TH-CQX12超声波自动气象站是一种利用超声波技术进行气象观测和数据采集的自动化设备。它能够实时监测温度、湿度、风速、风向、气压、雨量等多种气象要素,并通过无线传输方式将数据发送到数据中心进行分析和处理。 与传统气象站相比,超声波自动气象站具有…

申万宏源:证券低时延交易系统全链路自主可控创新实践 |论坛实录

由中科驭数主办的第二届证券基金行业先进计算技术大会暨2024低时延技术创新实践论坛(上海站)在上海举行。会上各位嘉宾深入的分享,吸引了不少行业同仁对本次会议干货内容的关注。特此,中科驭数整理部分演讲者发言实录,…

ARM汇编 2.arm常用指令

MOV 赋值操作 寄存器 < 寄存器/存储器/立即数 MOV{条件}{S} 目的寄存器&#xff0c;源操作数 没有S时指令不更新 CPSR 中条件标志位的值 立即数&#xff1a;由0-255之间的数据循环右移偶数位生成。(移动规则不用掌握) #0xfff不是立即数&#xff0c;而0x80000001是立即数 …

Mysql-ReadView + MVCC-RR 与 RC

实验准备 创建脚本 CREATE TABLE user (id int(11) NOT NULL AUTO_INCREMENT,name varchar(16) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,age int(11) NULL DEFAULT NULL,addr varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,PRIMARY …

3款最好用的tron钱包解读:TronLink,Ledger,Bitget钱包

电子钱包是用户连接到区块链网络的重要媒介。除了接收和发送功能外&#xff0c;它还可用于传输虚拟货币。它也是使用分散应用程序&#xff08;DApp&#xff09;的必要工具&#xff01;无论您是想在ON上使用以太坊&#xff0c;EOS还是任何DApp&#xff0c;您都必须先拥有钱包。因…

vxe-table表格合并行和虚拟滚动冲突

项目一直用的vxe-table 2.0版本&#xff0c;支持表格的虚拟滚动&#xff0c;最近要做表格合并行功能&#xff0c;虚拟滚动便失效了&#xff0c;强行虚拟滚动&#xff0c;合并行会有错行现象。 vxe-table2.0给出的解释是&#xff1a;合并行不能和虚拟滚动一起使用。 目前找到两种…

华为配置小型网络WLAN 的基本业务示例

配置小型网络WLAN基本业务示例 组网图形 图1 配置小型网络WLAN基本业务组网图 小型WLAN网络简介配置注意事项组网需求数据规划配置思路操作步骤配置文件 小型WLAN网络简介 本文介绍的WLAN网络是指利用频率为2.4GHz或5GHz的射频信号作为传输介质的无线局域网&#xff0c;相对于有…

【css】设置渐变阴影

css的属性中没有直接设置渐变阴影的&#xff0c;但是可以通过伪元素去实现。 .box-wrap{width: 100%;display: grid;place-content: center; } .box {width: 150px;height: 150px;background: #eee;border: 1px solid #585252;position: relative;transform: translate(0);/* …