1. 类的生命周期

类的生命周期整体为:加载、连接、初始化、使用、卸载。其中连接阶段分为:验证、准备、解析。如下图所示:

Pasted image 20241224185809

1.1 加载阶段

  1. 首先,[[#2. 类加载器|类加载器]]根据类的全限定名通过不同的渠道(从磁盘、网络等)以二进制流的方式获取字节码信息;
  2. 然后JVM将字节码中的信息保存到内存的方法区中。在方法区生成一个InstanceKlass对象,保存类的所有信息;
  3. 并且在堆中保存一份与方法区中类似的java.lang.Class对象,用于在Java代码中获取类信息。

1.2 连接阶段

如上图所示,连接阶段包含验证、准备、解析三个阶段。

1.2.1 验证

这个阶段主要目的是检测字节码文件是否遵循《Java虚拟机规范》中的约束。主要包含一下几点:

  1. 文件格式验证,如:魔数的验证,注册版本号是否满足当前虚拟机的要求等;
  2. 元信息验证,如:所有类必须有父类,即super不能为空;
  3. 验证程序执行指令的语义;
  4. 符号引用验证,如:是否访问其他类中的private方法等。

1.2.2 准备

一般情况下,此阶段将会为静态变量分配内存并设置初值。当静态变量为final修饰的话,准备阶段将直接按代码中的值来赋初值。

注意:
以上所说的初值,并非代码中的初始值。而是各个数据类型的初值,如所有数值型类型初值都为0,布尔类型初值为false,引用类型初值为null。为了防止记错,也可以称其为零值

1.2.3 解析

解析阶段主要工作是将常量池中的符号引用替换为直接引用

如下图,在解析之前,字节码文件中的就是用如 #29的编号来定位常量池中的内容。解析阶段的工作就是将这些编号转化为直接指向内存中的具体地址。

analyze_phase

1.3 初始化阶段

初始化阶段时类加载到内存过程中的最后一个阶段,它的任务时执行静态代码块中的代码,并为静态变量赋初值。此时的初值才是真正的代码中的初始值,也就是将主导权交给Java程序。

换一种表达方式就是,执行类构造器<clinit>块中的指令的过程。<clinit>是由编译器自动收集所有类变量的赋值动作和静态代码块中的语句合并产生的。

clinit

1.4 类的卸载

判定一个类是否可以被卸载,必须要同时满足一下三个条件:

  1. 此类所有实例对象都已经被回收,在堆中不存在任何该类的实例对象以及子类对象;
  2. 加载该类的类加载器已经被回收;
  3. 该类对应的java.lang.Class对象没有任何地方被引用。

2. 类加载器

2.1 什么是类加载器?

Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节
流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动
作的代码被称为“类加载器”(Class Loader)。

以上摘自周志明的《深入理解Java虚拟机》。简而言之,就是将类通过二进制字节流的方式加载到虚拟机中的组件。

2.2 有哪些类加载器?

类加载器总体分为四类,分别是:启动类加载器、扩展类加载器/平台类加载器、应用程序加载器、自定义加载器。

  • 启动类加载器(Bootstrap ClassLoader):由Hotspot虚拟机提供的类加载器,JDK9之前是由C++编写,JDK9之后由Java编写。默认加载$JAVA_HOME/jre/lib下的类文件,如rt.jar、tools.ja、resources.jar等。
  • 扩展/平台类加载器(Extension Class Loader/Platform Class Loader):是JDK中提供的使用Java编写的类加载器。默认加$JAVA_HOME/jre/ext下的类文件。(JDK9之后由拓展累加载器改名为平台类加载器)
  • 应用程序类加载器(Application Class Loader):由JDK提供使用Java编写的类加载器。默认加载应用程序classpath下的类。
  • 自定义类加载器:继承ClassLoader的子类,可以从各种来源加载类信息。

2.3 自定义类加载器

一般情况下,继承ClassLoader类,重写findClass()方法即可。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class DiyClassLoader extends ClassLoader{  

public static final String MY_CLASS_HOME = "/Users/yang/tmp/";
public static final String CLASS_SUFFIX = ".class";

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path = MY_CLASS_HOME + name + CLASS_SUFFIX;
byte[] bytes = FileUtil.readBytes(path);
return defineClass(name, bytes, 0, bytes.length);
}

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
DiyClassLoader diyClassLoader = new DiyClassLoader();
Class<?> aClass = diyClassLoader.loadClass("com.yang.Demo8");

Object o = aClass.getConstructor().newInstance();
System.out.println(o.getClass().getClassLoader());
System.out.println(o);
}
}

原理与细节见后续的 [[关于双亲委派]]一文。