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

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

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

相关文章

【JavaScript】fetch

fetch Response Headers ajax&axios&fetch的关系: ajax:ajax 是一种基于原生 JavaScript 的异步请求技术。它使用 XMLHttpRequest 对象来发送请求和接收响应。 axios:axios 是一个基于 Promise 的 HTTP 客户端,可以在浏览器和 Node…

STM32——中断系统和外部中断EXTI

一、中断 1.1中断系统 中断系统是管理和执行中断的逻辑结构; 1.2中断 系统在执行主程序过程中,出现了特定的触发条件(触发源),系统停止执行当前程序,转而去执行中断程序,执行完毕后&#xf…

什么是原型链?如何继承?

原型: 每个对象都可以有一个原型_proto_,这个原型还可以有它自己的原型,以此类推,形成一个原型链。查找特定属性的时候,我们先去这个对象里去找,如果没有的话就去它的原型对象里面去,如果还是没…

OllyDebug的使用方法. IDA Pro分析程序的控制流图,可以找到不同的函数入口点. 在汇编代码中定位特定函数可能是一个耗时且复杂的过程

实战 “OllyDbg” 是一个流行的Windows平台上的汇编级调试器,用于调试和分析二进制程序,尤其是用于逆向工程目的。使用OllyDbg的基本步骤如下: 安装和打开OllyDbg:首先,您需要在您的计算机上安装OllyDbg。完成安装后&…

HCIA学习作业五

拓扑图: PC端 PC1>ipconfig PC2>ipconfig PC3>ipconfig PC4>ipconfig PC>ping PC1>ping 192.168.1.125 PC1>ping 192.168.1.254 PC1>ping 192.168.1.253 PC2>ping 192.168.1.125 PC2>ping 192.168.1.253 PC3>ping 192.168.1.126…

java程序员怎么完善自己各个方面的能力?

java程序员怎么完善自己各个方面的能力? 在开始前我分享下我的经历,刚入行时遇到一个好公司和师父,给了我机会,两年时间从3k薪资涨到18k的, 我师父给了一些java学习方法和资料,让我不断提升自己,感谢帮助…

Python计算机二级/Python期末考试 刷题(一)

收集了一些经典Python计算机二级和Python期末考试题库 整理不易,大家点赞收藏支持一下 祝大家计算机二级和期末考试都高分过 目录 一、填空 二、选择 三、程序设计 一、填空 1.序列元素的编号称为索引,索引值从【1】开始,访问序列元素时将…

vue 使用 v-viewer 用于图片浏览的Vue组件,支持旋转、缩放、翻转等操作,基于viewer.js。

作者连接 npm: npm install v-viewerlegacy viewerjs main.js 引入: // 引入Viewer插件 import VueViewer, { directive as viewerDirective } from v-viewer; // 引入Viewer插件的图片预览器的样式 import viewerjs/dist/viewer.css; // 使用Viewer图片…

Pull模式和Push模式

Pull模式是一种消息消费模式,其中客户端主动从服务端拉取数据。 优点:客户端可以根据自己的消费能力来消费数据,不存在消息堆积的情况。 缺点:消息处理可能不及时,可能存在大量无效请求,客户端需要考虑拉取…

python对图片或文件的操作

一. base64 与图片的相互转换 1. base64 转图片 import base64 from io import BytesIO from PIL import Image# base64 编码的图像数据(示例) base64_data "iVBn9DHASKJDjDsdSADSf8lgg"# 将 base64 编码的字符串解码为二进制数据 binary_d…

不同的强化学习模型适配与金融二级市场的功能性建议

DQN ES DDPG A2C TD3 SAC QMIX MADDPG PPO CQL IMPALA 哪个模型适合进行股票操作 在考虑使用哪种模型进行股票操作时,需要考虑模型的特点、适用场景以及实现复杂度等因素。以下是对您列出的几种强化学习模型的简要概述,以帮助您做出选择: DQ…

【C++】类与对象(二)特殊成员函数

前言 类与对象(二) 文章目录 一、特殊成员函数二、构造函数三、析构函数四、拷贝构造函数五、拷贝赋值运算符 一、特殊成员函数 如果在类的声明中未显式提供某个成员函数的定义,编译器会自动生成一个默认实现。 这包括默认构造函数、默认析构…

Android studio打包apk比较大

1.遇到的问题 在集成linphone打包时发现有118m,为什么如此之大额。用studio打开后发现都是c不同的pu架构。 2.解决办法 增加ndk配置,不选配置那么多的cpu结构,根据自己需要调整。 defaultConfig { applicationId "com.matt.linphoneca…

备战蓝桥杯---数据结构与STL应用(基础3)

今天我们主要介绍的是pair,string,set,map pair:我们可以把它当作一个结构体&#xff1a; void solve(){pair<int int> a;//创建amake_pair(1,2);//添加元素cout<<a.first<<endl<<a.second<<endl;}//输出 当然&#xff0c;它也可以嵌套&#…

51单片机点灯

51单片机点灯 1.点亮LED灯 #include "reg52.h"sbit ledOne P3^7;void main() {//灯亮&#xff0c;给一个P3.7低电平ledOne 0; }给LED1对应标号的P3^7一个低电平&#xff0c;就能点亮LED灯2.LED灯闪烁 #include "reg52.h"sbit ledOne P3^7;void Delay…

ai电销机器人的优势

随着电销机器人的不断深入研究&#xff0c;电销机器人得到了很大的发展&#xff0c;逐渐走入了许多公司中。 机器人不像人一样会闹情绪会累&#xff0c;可以在客户开发上一如既往的开发客户&#xff0c;使用真心录音的方式能让客户听着像和人沟通一样&#xff0c;完成一些流程…

2024年美赛美国大学生数学建模竞赛D题思路解析+代码+论文

下文包含&#xff1a;2024年美国大学生数学建模竞赛&#xff08;美赛&#xff09;A- F题思路解析、选题建议、代码可视化及如何准备数学建模竞赛&#xff08;2号发&#xff09; C君将会第一时间发布选题建议、所有题目的思路解析、相关代码、参考文献、参考论文等多项资料&…

c++ scanf解释

scanf是C中的输入函数&#xff0c;用于从标准输入流中读取数据并将其存储到给定的变量中。它的功能类似于cin&#xff0c;但在某些情况下更方便。 scanf函数的使用格式为&#xff1a; scanf("格式控制字符串", 参数列表); 其中&#xff0c;格式控制字符串指定了输…

python笔记10

1、继承 继承是面向对象编程中的一个重要概念&#xff0c;它允许一个类&#xff08;子类&#xff09;继承另一个类&#xff08;父类&#xff09;的属性和方法。通过继承&#xff0c;子类可以重用父类的代码&#xff0c;并且有机会添加新的属性和方法&#xff0c;或者重写父类的…

使用PowerBI 基于Adventure Works案例分析

Adventure Works案例分析 前言 数据时代来临&#xff0c;但一个人要顺应时代的发展是真理。 数据分析的核心要素 那数分到底是什么&#xff1f; 显然DT 并不等同于 IT&#xff0c;我们需要的不仅仅是更快的服务器、更多的数据、更好用的工具。这些都是重要的组成部分&…