当前位置:  首页>> 技术小册>> 深入拆解 Java 虚拟机

03 | Java虚拟机是如何加载Java类的?

在深入探讨Java虚拟机(JVM)如何加载Java类之前,我们需要先理解Java类加载机制的基本概念和重要性。Java的跨平台特性得益于其“一次编写,到处运行”的理念,而实现这一理念的核心便是Java虚拟机及其类加载机制。类加载机制是JVM中负责将类的二进制数据(.class文件)加载到内存中,并转换成JVM内部数据结构表示(如运行时数据结构、方法区中的类数据等),然后生成对应的Class对象以供程序使用的过程。这一过程涉及多个阶段,包括加载、链接(验证、准备、解析)、初始化等,本章将重点讲解类加载的详细过程。

一、类加载的时机

在JVM中,类的加载并非一蹴而就,也不是在程序启动时一次性完成的。类的加载时机遵循JVM规范,主要包括以下几种情况:

  1. 显式加载:通过Class.forName()方法显式加载某个类。
  2. 隐式加载:当创建类的实例、访问类的静态变量或静态方法时,如果该类尚未被加载到JVM中,则会触发类的加载。
  3. 初始化子类时:如果一个类还没有被加载和初始化,但它的子类被加载和初始化了,那么父类也会被加载和初始化。
  4. 使用反射API:通过反射API(如Class.forName()的变体)访问类时,会触发类的加载。
  5. 动态代理:在动态代理创建时,如果代理类引用的接口所对应的类还没有被加载,则会加载这些类。

二、类加载的过程

Java虚拟机将类的加载过程分为五个阶段:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)和初始化(Initialization)。虽然这五个阶段是按顺序进行的,但某些阶段(如解析)可能会延迟到真正使用到时才进行。

2.1 加载(Loading)

加载是类加载过程的第一个阶段,它负责完成以下任务:

  • 通过一个类的全限定名(包括包名和类名)来获取定义此类的二进制字节流。
  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

加载阶段可以通过自定义类加载器(ClassLoader)来实现,这是Java动态加载类的基础。类加载器只负责类的加载,至于类的链接和初始化则由JVM负责。

2.2 验证(Verification)

验证阶段确保被加载的类的正确性,防止对JVM造成危害。验证包括以下几个方面的检查:

  • 文件格式验证:检查字节流是否符合Class文件格式规范。
  • 元数据验证:对类的元数据信息进行语义校验,确保不存在不符合Java语言规范的元数据。
  • 字节码验证:通过数据流分析和控制流分析,确保程序语义是合法的、符合逻辑的。
  • 符号引用验证:在解析阶段对符号引用进行验证,确保解析动作能正常执行。
2.3 准备(Preparation)

准备阶段为类变量(即被static修饰的变量,不包括实例变量)分配内存并设置初始值。这里的初始值是指数据类型默认的零值(如int为0,boolean为false),而不是类变量在代码中显式指定的初始值。这些显式指定的初始值将在初始化阶段被赋值。

2.4 解析(Resolution)

解析是将类、接口、字段和方法的符号引用转换为直接引用的过程。符号引用以字符串形式表示,如com.example.MyClass,而直接引用则是可以直接定位到目标的方法区中的内存地址、偏移量或句柄等。解析主要发生在类或接口、字段、类方法、接口方法等的引用上。

2.5 初始化(Initialization)

初始化阶段是类加载过程的最后一步,也是执行类构造器<clinit>()方法的阶段。<clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态代码块(static block)中的语句合并而成的。<clinit>()方法具有以下特点:

  • 它是由编译器自动生成的,没有方法体。
  • 它是在类被加载到JVM中并开始执行任何类的成员之前被调用的。
  • 它在多线程环境下被正确地同步,确保一个类的<clinit>()方法在多线程环境中被正确地加锁和同步,以防止多个线程同时执行<clinit>()方法。
  • <clinit>()方法对于类或接口来说不是必须的,如果一个类中没有静态代码块和静态变量的显式赋值操作,那么编译器可以不为这个类生成<clinit>()方法。

三、类加载器

类加载器是JVM中实现类的加载机制的核心组件,它负责将类的二进制数据加载到JVM中,并转换成JVM能够识别的数据结构。Java中的类加载器遵循双亲委派模型(Parents Delegation Model),这是一种类加载的层次结构,每个类加载器都有一个父类加载器,除了根加载器(Bootstrap ClassLoader)外。

  • 启动类加载器(Bootstrap ClassLoader):负责加载JVM核心库,如rt.jar中的类,由C++实现,不是java.lang.ClassLoader的子类。
  • 扩展类加载器(Extension ClassLoader):负责加载JDK扩展目录(jre/lib/ext)中的类库。
  • 系统类加载器(System ClassLoader):也称为应用类加载器,它负责加载用户类路径(classpath)上的类库。

当一个类需要被加载时,类加载器会首先尝试将加载任务委托给它的父类加载器,如果父类加载器无法完成加载,则子类加载器才会尝试自己加载。这种机制确保了Java平台的核心类库的安全性,防止了类名冲突等问题。

四、总结

Java虚拟机通过类加载机制实现了类的动态加载,这是Java语言灵活性和可扩展性的重要基础。类加载过程包括加载、验证、准备、解析和初始化五个阶段,每个阶段都有其特定的任务和目的。类加载器则是实现类加载机制的核心组件,它遵循双亲委派模型,确保了类加载的安全性和一致性。理解Java虚拟机的类加载机制,对于深入掌握Java语言特性、进行高效的Java开发具有重要意义。


该分类下的相关小册推荐: