面试官:说一下类加载的过程

开发 前端
「理解和方法的作用还是很有必要的,因为经常有些面试题问静态代码块,构造代码块,构造函数的执行顺序。」

[[413663]]

加载

当我们要使用一个类的时候,要通过ClassLoader将类加载到内存中

「类加载阶段主要完成如下三件事情」

  • 通过全类名,获取类的二进制流
  • 解析类的二进制流为方法区内的数据结构
  • 创建一个java.lang.Class类的实例,表示该类型,作为方法区这个类的访问入口

「通过全类名,获取类的二进制流的方式有很多种」

  1. 从zip压缩包中获取
  2. 从网络中获取
  3. 运行时计算生成,如动态代理技术
  4. ...

「对于非数组类型的加载阶段,即可以使用Java虚拟机内置的类加载器去完成,也可以使用用户自定义的类加载器去完成」

链接

「链接这个阶段主要分为3个部分,验证,准备,解析」

验证

「验证阶段主要是确保Class文件的格式正确,运行时不会危害虚拟机的安全」

验证阶段的规则很多,但大致分为如下4个阶段

「具体详细的内容,我就不详细解释了,可以看《深入理解Java虚拟机》,本篇文章偏向于做一个总结,把握类加载的一个整体流程,而不对细节进行阐述」

准备

「准备阶段主要是为类的静态变量分配内存,并将其初始化为默认值」

常见的数据类型的默认值如下

数据类型 默认值
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d
boolean false
char '\u0000'
reference null

「如果类静态变量的字段属性表中存在ConstantValue属性,则直接执行赋值语句」

那么什么情况下类静态变量的字段属性表中存在ConstantValue属性呢?

  1. 类静态变量为基本数据类型,并且被final修饰
  2. 类静态变量为String类型,被final修饰,并且以字面量的形式赋值

为了方便查看Class文件的字节码,我在IDEA中下载了一个插件jclasslib Bytecode viewer,非常方便。用如下代码通过字节码的形式验证一下

  1. public class Person { 
  2.  
  3.     private static int age = 10; 
  4.     private static final int length = 160; 
  5.     private static final String name = "name"
  6.     private static final String loc = new String("loc"); 

「所以length和name属性在准备阶段就会赋值为ConstantValue指定的值」

「那么age和loc属性会在哪个阶段赋值呢?是在初始化阶段,后面会详细介绍哈」

解析

「将类,接口,字段和方法的符号引用(在常量池中)转为直接引用」符号引用:用一组符号来描述所引用的目标 直接引用;直接指向指向目标的指针

加入我写了一个如下的类

  1. public class Student { 
  2.  
  3.     private String name
  4.     private int age; 
  5.  
  6.     public String getName() { 
  7.         return this.name
  8.     } 

以字段为例,name和age对应的对象并不是直接指向内存地址,而是用字符串来进行描述(即符号引用)。解析阶段就是将这些描述转为直接指向目标的指针(即直接引用)

初始化

「执行类静态成员变量赋值语句和静态代码块中的语句」

我们把上面的Student代码改成如下形式

  1. public class Student { 
  2.  
  3.     private String name
  4.     private int age = 10; 
  5.     private static int gender = 1; 
  6.  
  7.     { 
  8.         System.out.println("构造代码块"); 
  9.     } 
  10.  
  11.     static { 
  12.         System.out.println("静态代码块"); 
  13.     } 
  14.  
  15.     public Student() { 
  16.         System.out.println("构造函数"); 
  17.     } 
  18.  
  19.     public String getName() { 
  20.         return this.name
  21.     } 

可以看到字节码中包含了3个方法,getName方法我们知道,<init>和<clinit>方法里面执行了哪些逻辑

从字节码的角度分析一波

「<init>方法」

从字节码可以看到方法的主要逻辑为

  • 调用父类的方法
  • 非静态成员变量赋值
  • 执行构造代码块
  • 执行构造函数

「<clinit>方法」

从字节码可以看到方法的主要逻辑为

  1. 执行静态变量的赋值语句
  2. 执行静态代码块中的语句
  3. 需要注意的一点是,「Java虚拟机会保证子类的方法执行前,父类的方法已经执行完毕」

「理解方法的作用还是很有必要的,因为经常有些面试题问静态代码块,构造代码块,构造函数的执行顺序。」

我这里就直接总结一下结论,大家可以写demo验证一下

「没有继承情况的执行顺序」

  1. 静态代码块和静态成员变量,执行顺序由编写顺序决定(只会执行一次哈)
  2. 构造代码块和非静态成员变量,执行顺序由编写顺序决定
  3. 构造函数

「有继承情况的执行顺序」

  1. 父类的静态(静态代码块,静态成员变量),子类的静态(静态代码块,静态成员变量)(只会执行一次哈)
  2. 父类的非静态(构造代码块,非静态成员变量),父类的构造函数
  3. 子类的非静态(构造代码块,非静态成员变量),子类的构造函数

卸载

垃圾收集不仅发生在堆中,方法区上也会发生。但是对方法区的类型数据回收的条件比较苛刻

以下图为例,想回收方法区中的Simple类

  1. 需要保证堆中的Sample类及其子类都已经被回收
  2. 加载Sample类的MyClassLoader已经被回收
  3. Sample类对应的Class对象已经被回收

可以看到对方法区的类型数据回收的条件比较苛刻,但是收效甚微,所以有些垃圾收集器不会对方法区的类型数据进行回收

总结

类加载过程

 

变量的赋值过程

本文转载自微信公众号「Java识堂」,可以通过以下二维码关注。转载本文请联系Java识堂公众号。

 

责任编辑:武晓燕 来源: Java识堂
相关推荐

2023-09-12 14:56:13

MyBatis缓存机制

2022-06-06 15:33:20

线程Java释放锁

2023-02-18 13:34:14

Nacos健康检查机制

2022-06-07 12:03:33

Java内存模型

2023-02-08 08:32:41

轮询锁

2024-01-29 10:08:11

零拷贝Zero-copyCPU 拷贝

2023-12-29 13:45:00

2020-07-30 07:58:36

加密算法

2024-02-21 16:42:00

2024-02-27 15:23:48

RedLock算法Redis

2023-01-30 15:39:40

GETHTTP

2021-06-02 11:25:18

线程池Java代码

2021-11-27 08:13:13

Final 面试

2023-11-29 08:00:53

JavaTreeMap底层

2024-02-20 08:13:35

类加载引用Class

2021-11-08 15:59:01

MyBatis关联开发

2021-08-28 09:06:11

Dubbo架构服务

2023-03-08 07:46:53

面试官优化结构体

2020-11-02 07:02:10

加载链接初始化

2021-08-13 07:23:15

架构秒杀系统
点赞
收藏

51CTO技术栈公众号