对类的生命周期以及类的静态代码块、构造代码块、构造函数、普通代码块的执行顺序的讨论

类的生命周期

首先,我们应该清楚java的代码不是直接在所在操作系统运行的,而是在java虚拟机中运行后,最终在所在
操作系统执行。所以,Java程序从源文件创建到程序运行要经过两大步骤:
1、源文件由编译器编译成字节码(.class)  
2、字节码由java虚拟机解释运行。

类的生命周期:
加载->链接(验证+准备+解析)->初始化(使用前的准备)->使用->卸载
运行过程图:
在这里插入图片描述

1.加载

首先,java源程序代码(.java文件)被编译为字节码文件(.class)后,类加载器将.class文件读取到二进制流并解析。将里面的元数据(类型、常量等)载入到方法区,在java堆中生成对应的java.lang.Class对象。

(方法区:方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已经被虚拟机加载的类信息/常量//静态信息/即时编译器编译后的代码等数据.虽然Java虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却又一个别名叫做Non-Heap(非堆),目的应该是与Java堆区分开来.)
在这里插入图片描述
①类型信息
类型的全限定名
超类的全限定名
直接超接口的全限定名
类型标志(该类是类类型还是接口类型)
类的访问描述符(public、private、default、abstract、final、static)
②类型的常量池
存放该类型所用到的常量的有序集合,包括直接常量(如字符串、整数、浮点数的常量)和对其他类型、字段、方法的符号引用。
③字段信息(该类声明的所有字段)
字段修饰符(public、protect、private、default)
字段的类型
字段名称
④方法信息
方法信息中包含类的所有方法,每个方法包含以下信息:
方法修饰符
方法返回类型
方法名
方法参数个数、类型、顺序等
方法字节码
操作数栈和该方法在栈帧中的局部变量区大小
异常表
⑤类变量(静态变量)
指该类所有对象共享的变量,即使没有任何实例对象时,也可以访问的类变量。它们与类进行绑定。
⑥指向类加载器的引用
每一个被JVM加载的类型,都保存这个类加载器的引用,类加载器动态链接时会用到。
⑦指向Class实例的引用
类加载的过程中,虚拟机会创建该类型的Class实例,方法区中必须保存对该对象的引用。通过Class.forName(String className)来查找获得该实例的引用,然后创建该类的对象。
⑧方法表
为了提高访问效率,JVM可能会对每个装载的非抽象类,都创建一个数组,数组的每个元素是实例可能调用的方法的直接引用,包括父类中继承过来的方法。

2.连接

连接分为3步:
①验证
对文件格式、版本号、元数据、字节码等进行验证。
②准备
为类的静态变量分配内存,并将其初始化为默认值。
比如字符串的默认值为空串,数字类型默认为0。
静态常量直接设置指定值。
③解析
将符号引用替换为直接引用,就是将类的内存地址找到,建立直接引用关系。

3.初始化

主要包括执行类构造方法、static变量赋值语句,staic{}语句块。如果一个子类进行初始化,那么它会事先初始化其父类,保证父类在子类之前被初始化。所以,在java中初始化一个类,那么必然是先初始化java.lang.Object,因为所有的java类都继承自java.lang.Object。

类初始化时间

①创建类的实例(new)
②访问某个类或接口的静态变量,或者对该静态变量赋值
③调用类的静态方法
④反射(Class.forName(“com.lyj.load”))(我也不懂)
⑤初始化一个类的子类(会首先初始化子类的父类)
⑥JVM启动时标明的启动类,即文件名和类名相同的那个类

类的初始化顺序

①如果这个类还没有被加载和链接,那先进行加载和链接
②假如这个类存在直接父类,并且这个类还没有被初始化
那就初始化直接的父类(不适用于接口)
③假如类中存在初始化语句(如static变量和static块),那就依次执行这些初始化语句。
总结类的初始化顺序:
(静态变量、静态代码块)–>(普通变量、普通代码块)–> 构造器

一个类只初始化一次

4.使用

①创建实例
②访问类或接口的静态变量
③调用类的静态方法
等等…

5.卸载

用例子解析静态代码块、构造代码块、构造函数、普通代码块的执行顺序

public class Employee {
private String name = "lisi";
{
	System.err.println("初始化1");
	name = "wangwu";
	age = 20;
}
private int age = 30;
static {
	System.err.println("静态初始化1");
	id = 40;
	Employee employee = new Employee("liuliu", 50);
	System.err.println(employee);
}
private static int id = 30;
{
	System.err.println("初始化2");
	name = "zhaosi";
	age = 40;
}
static {
	System.err.println("静态初始化2");
	id = 80;
}

public static int getId() {
	return id;
}

public String getName() {
	return name;
}

public void setName(String name) {
	this.name = name;
}

public Employee(String name, int age) {
	super();
	System.out.println("有參構造器");
	this.name = name;
	this.age = age;
}

public Employee() {
	super();
	System.out.println("無參構造器");
	// TODO Auto-generated constructor stub
}

@Override
public String toString() {
	return "Employee [name=" + name + ", age=" + age + "]";
}
}

public class TestDefault {
public static void main(String[] args) {
	Employee tom = new Employee("tom", 20);
	System.out.println(tom);
	System.out.println(tom.getId());
	Employee jerry = new Employee("jerry", 19);
	System.out.println(jerry);
	System.out.println(jerry.getId());
}
}

分析

加载:

.java文件被编译为.class文件,.class文件读取至二进制流。并将元数据映射到方法区。

连接

验证
准备:为类的静态变量分配内存,并初始化为默认值。(为private static int id分配默认值0; )
解析

初始化

因为主类创建了Employee类的实例(Employee tom = new Employee(“tom”, 20);),所以将初始化Emlpoyee类。
首先,初始化静态变量和静态代码块。
静态变量初始化:id=30;
静态代码块(按顺序)
①输出"静态初始化1"
②将id赋值为30;
③创建Employee实例(Employee employee = new Employee(“liuliu”, 50);),所以要跳转到实例employee的初始化步骤
{
①静态变量初始化:id=30;
②静态代码块:
因为tom实例处于Employee类的静态代码块初始化中,静态代码块只初始化一次,所以employee实例不执行静态代码块。
③普通变量初始化:
name=“lisi”;
age=30;
④普通代码块:

{
	System.err.println("初始化1");
	name = "wangwu";
	age = 20;
}

输出"初始化1"
将name赋值为"wangwu";
将age赋值为20

{
	System.err.println("初始化2");
	name = "zhaosi";
	age = 40;
}

输出"初始化2"
将name赋值为"zhaosi";
将age赋值为40
⑤构造器

public Employee(String name, int age) {
	super();
	System.out.println("有參構造器");
	this.name = name;
	this.age = age;
}

输出"有参构造器"
将name赋值为"liuliu";
将age赋值为50
}
④输出employee内容:Employee [name=liuliu,age=50]
⑤输出"静态初始化2"
⑥将静态变量赋值为80;
普通变量初始化
name=“lisi”;
age=30;
普通代码块(和employee实例相同)
输出"初始化1"
将name赋值为"wangwu";
将age赋值为20
输出"初始化2"
将name赋值为"zhaosi";
将age赋值为40
构造器
输出"有参构造器"
将name赋值为"tom";
将age赋值为20;

接下来继续执行主函数后续代码

    System.out.println(tom);
	System.out.println(tom.getId());
	Employee jerry = new Employee("jerry", 19);
	System.out.println(jerry);
	System.out.println(jerry.getId());

①输出tom内容:Employee [name=liuliu,age=50]
②执行tom方法返回静态变量id(id此时的值为80)
③后续代码再创建实例与创建tom实例相同,但是因为静态变量、静态代码块已经初始化过了,所以jerry实例初始化不再执行静态变量赋值和静态代码块。

参考文章:https://blog.csdn.net/u012556994/article/details/81271764

https://blog.csdn.net/u013412772/article/details/81051465

猜你喜欢

转载自blog.csdn.net/Jonsnow1457/article/details/89291348