JVM-类的生命周期

news2024/11/15 21:40:52

类的生命周期概述

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

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

 加载阶段

加载(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关键字,这类变量会在准备阶段直接进行初始化。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入玩转Playwright:高级操作解析与实践

playwright高级操作 iframe切换 ​ 很多时候,网页可能是网页嵌套网页,就是存在不止一个html标签,这时候我们的selenium或者playwright一般来说定位不到,为什么呢? ​ 因为默认是定位到第一个标准的html标签内部。 …

费一凡:土木博士的自我救赎之道 | 提升之路系列(五)

导读 为了发挥清华大学多学科优势,搭建跨学科交叉融合平台,创新跨学科交叉培养模式,培养具有大数据思维和应用创新的“π”型人才,由清华大学研究生院、清华大学大数据研究中心及相关院系共同设计组织的“清华大学大数据能力提升项…

五、防御保护---防火墙出口选路篇

五、防御保护---防火墙智能选路篇 一、就近选路二、策略路由选路1.策略路由的概念1.1匹配条件(通过ACL定义)1.2动作 三、智能选路 --- 全局路由策略1.基于链路带宽的负载分担2.基于链路质量进行负载分担3.基于链路权重进行负载分担4.基于链路优先级的主备…

股票市场

(一)股票市场 顾名思义,就是买卖股票的场所。就是为了撮合想发展但缺钱的企业与有钱但想投资的投资者。 股票市场按照交易场所,可分为场内市场和场外市场: 场内市场是指证券交易所, 场外市场就是证券交易…

比Filebeat更强大的日志收集工具-Fluent bit的http插件实战

文章目录 1.前言2. fluent bit http插件配置以及参数详解3. Http 接口服务3.1 开发Http 接口服务3.2 重启fluent bit向http web服务发送数据 1.前言 Fluent Bit 的 HTTP 插件提供了一种灵活而通用的机制,可用于将日志数据 从各种环境中传输到指定的远程服务器&#…

Python算法题集_滑动窗口最大值

本文为Python算法题集之一的代码示例 题目239:滑动窗口最大值 说明:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗…

常见的网络安全威胁和防护方法

随着数字化转型和新兴技术在各行业广泛应用,网络安全威胁对现代企业的业务运营和生产活动也产生了日益深远的影响。常见的网络安全威胁通常有以下几种: 1. 钓鱼攻击 攻击者伪装成合法的实体(如银行、电子邮件提供商、社交媒体平台等&#xf…

C++实现通讯录管理系统

目录 1、系统需求 2、创建项目 2.1 创建项目 3、菜单功能 4、退出功能 5、添加联系人 5.1 设计联系人结构体 5.2 设计通讯录结构体 5.3 main函数中创建通讯录 5.4 封装联系人函数 5.5 测试添加联系人功能 6、显示联系人 6.1 封装显示联系人函数 7、删除联系人 7.1…

获取依赖aar包的两种方式-在android studio里引入 如:glide

背景:我需要获取aar依赖到内网开发,内网几乎代表没网。 一、 如何需要获取依赖aar包 方式一:在官方的github中下载,耗时不建议 要从开发者网站、GitHub 存储库或其他来源获取 ‘com.github.bumptech.glide:glide:4.12.0’ AAR 包&#xff…

MySQL:MVCC原理详解

MySQL是允许多用户同时操作数据库的,那么就会出现多个事务的并发场景。那么再并发场景会出现很多问题:脏读、不可重复读、幻读的问题。 而解决这些问题所用到的方法就是:MVCC 多版本并发控制。而这个MVCC的实现是基于read_view、undoLog 如…

大规模机器学习(Large Scale Machine Learning)

1.大型数据集的学习 案例: 如果我们有一个低方差的模型,增加数据集的规模可以帮助你获得更好的结果。我们应该怎样应对一个有 100 万条记录的训练集? 以线性回归模型为例,每一次梯度下降迭代,我们都需要计算训练集的误…

古建筑电气火灾的防控与管理

摘要:我国古建筑多为砖木结构,当发生火灾事故时具有蔓延快、扑救难的特点,而火灾对古建筑的损害性很大,电气火灾事故在我国火灾事故中比重居高不下。本文通过对古建筑电气火灾成因进行分析,有针对性地提出了古建筑电气火灾防控对策…

日志之Loki详细讲解

文章目录 1 Loki1.1 引言1.2 Loki工作方式1.2.1 日志解析格式1.2.2 日志搜集架构模式1.2.3 Loki部署模式 1.3 服务端部署1.3.1 AllInOne部署模式1.3.1.1 k8s部署1.3.1.2 创建configmap1.3.1.3 创建持久化存储1.3.1.4 创建应用1.3.1.5 验证部署结果 1.3.2 裸机部署 1.4 Promtail…

炒黄金 vs 炒股:探寻投资路线的差异和各自的优势

在当前不景气的股市,人们越来越关注分散投资的方式,以期降低风险并稳定资产。炒黄金成为了一个备受关注的投资选择,与传统炒股相比,它到底有什么区别呢?本文将从多个维度深入分析这两种投资方式的差异以及各自的优势。…

微信开发者工具 git 拉取 failed invalid authentication scheme

微信开发者工具 git 拉取 failed invalid authentication scheme 拉取代码时报错,无效身份认证 解决方案: 1.检查git地址是否正常 2.检查git用户名密码是否正确

ElementUI组件:Button 按钮

button按钮 点击下载learnelementuispringboot项目源码 效果图 el-button.vue页面效果图 项目里el-button.vue代码 <script> export default {name: "el_button",// 注意这里的名称不能和 router inex.js里的name一样methods: {sendMsg() {// alert(1)xthi…

(2024,双流编码器,文本引导的风格迁移,调制,FFT 和低频滤波)FreeStyle:使用扩散模型进行文本引导风格迁移

FreeStyle: Free Lunch for Text-guided Style Transfer using Diffusion Models 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 方法 3.1 LDM 3.2 FreeStyle 的模型结构…

数据结构——并查集

1.并查集的定义 并查集其实也是一种树形结构&#xff0c;在使用中通常用森林的方式来表示 并查集的逻辑结构其实就是集合 并查集一般可以通过双亲写法&#xff08;顺序结构&#xff09;来完成&#xff0c;即通过一个数组存储父亲结点的下标 int s[10005]; int main() {for(…

共享的IP隔一段时间就变?用这种方法可以不需要知道电脑IP

前言 一般来说,电脑接入路由器之后,IP是由路由器自动分配的(DHCP),但如果隔一段时间不开机连接路由器,或者更换了别的网卡进行连接,自动分配的IP就会更改。 比如你手机连接着电脑的共享IP:192.168.1.10,但过段时间之后,电脑的IP突然变成了192.168.1.11,那么你的所有…

UDP/TCP协议特点

1.前置知识 定义应用层协议 1.确定客户端和服务端要传递哪些信息 2.约定传输格式 网络上传输的一般是二进制数据/字符串 结构化数据转二进制/字符串 称为序列化 反之称之为反序列化 下面就是传输层了 在TCP/IP协议中,我们以 目的端口,目的IP 源端口 源IP 协议号这样一个五…