本文源代码在Github。

本文仅为个人笔记,不应作为权威参考。

原文

在前一篇文章初步了解ClassLoader里提到了委托模型(又称双亲委派模型),解释了ClassLoader hierarchy(层级)处理类加载的过程。那么class文件是如何变成Class对象的呢?

Class的加载过程

Class加载分为这几步:

  1. 创建和加载(Creation and Loading)
  2. 链接(Linking)

    1. 验证(Verification)
    2. 准备(Preparation)
    3. 解析(Resolution),此步骤可选
  3. 初始化(Initialization)

注: 前面说了数组类是虚拟机直接创建的,以上过程不适用于数组类。

创建和加载(Creation and Loading)

何时会触发一个类的加载?

Java Language Specification - 12.1.1. Load the Class Test:

The initial attempt to execute the method main of class Test discovers that the class Test is not loaded - that is, that the Java Virtual Machine does not currently contain a binary representation for this class. The Java Virtual Machine then uses a class loader to attempt to find such a binary representation.

也就是说,当要用到一个类,JVM发现还没有包含这个类的二进制形式(字节)时,就会使用ClassLoader尝试查找这个类的二进制形式。

我们知道ClassLoader委托模型,也就是说实际触发加载的ClassLoader和真正加载的ClassLoader可能不是同一个,JVM将它们称之为initiating loaderdefining loader(Java Virtual Machine Specification - 5.3. Creation and Loading):

A class loader L may create C by defining it directly or by delegating to another class loader. If L creates C directly, we say that L defines C or, equivalently, that L is the defining loader of C.

When one class loader delegates to another class loader, the loader that initiates the loading is not necessarily the same loader that completes the loading and defines the class. If L creates C, either by defining it directly or by delegation, we say that L initiates loading of C or, equivalently, that L is an initiating loader of C.

那么当A类使用B类的时候,B类使用的是哪个ClassLoader呢?

Java Virtual Machine Specification - 5.3. Creation and Loading:

The Java Virtual Machine uses one of three procedures to create class or interface C denoted by N:

  • If N denotes a nonarray class or an interface, one of the two following methods is used to load and thereby create C:

    • If D was defined by the bootstrap class loader, then the bootstrap class loader initiates loading of C (§5.3.1).
    • If D was defined by a user-defined class loader, then that same user-defined class loader initiates loading of C (§5.3.2).
  • Otherwise N denotes an array class. An array class is created directly by the Java Virtual Machine (§5.3.3), not by a class loader. However, the defining class loader of D is used in the process of creating array class C.

注:上文的C和D都是类,N则是C的名字。

也就说如果D用到C,且C还没有被加载,且C不是数组,那么:

  1. 如果D的defining loader是bootstrap class loader,那么C的initiating loader就是bootstrap class loader。
  2. 如果D的defining loader是自定义的class loader X,那么C的initiating loader就是X。

再总结一下就是:如果D用到C,且C还没有被加载,且C不是数组,那么C的initiating loader就是D的defining loader。

用下面的代码观察一下:

// 把这个项目打包然后放到/tmp目录下
public class CreationAndLoading {public static void main(String[] args) throws Exception {// ucl1的parent是bootstrap class loaderURLClassLoader ucl1 = new NamedURLClassLoader("user-defined 1", new URL[] { new URL("file:///tmp/classloader.jar") }, null);// ucl1是ucl2的parentURLClassLoader ucl2 = new NamedURLClassLoader("user-defined 2", new URL[0], ucl1);Class<?> fooClass2 = ucl2.loadClass("me.chanjar.javarelearn.classloader.Foo");fooClass2.newInstance();}
}public class Foo {public Foo() {System.out.println("Foo's classLoader: " + Foo.class.getClassLoader());System.out.println("Bar's classLoader: " + Bar.class.getClassLoader());}
}public class NamedURLClassLoader extends URLClassLoader {private String name;public NamedURLClassLoader(String name, URL[] urls, ClassLoader parent) {super(urls, parent);this.name = name;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {System.out.println("ClassLoader: " + this.name + " findClass(" + name + ")");return super.findClass(name);}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {System.out.println("ClassLoader: " + this.name + " loadClass(" + name + ")");return super.loadClass(name);}@Overridepublic String toString() {return name;}
}

运行结果是:

ClassLoader: user-defined 2 loadClass(me.chanjar.javarelearn.classloader.Foo)
ClassLoader: user-defined 1 findClass(me.chanjar.javarelearn.classloader.Foo)
ClassLoader: user-defined 1 loadClass(java.lang.Object)
ClassLoader: user-defined 1 loadClass(java.lang.System)
ClassLoader: user-defined 1 loadClass(java.lang.StringBuilder)
ClassLoader: user-defined 1 loadClass(java.lang.Class)
ClassLoader: user-defined 1 loadClass(java.io.PrintStream)
Foo's classLoader: user-defined 1
ClassLoader: user-defined 1 loadClass(me.chanjar.javarelearn.classloader.Bar)
ClassLoader: user-defined 1 findClass(me.chanjar.javarelearn.classloader.Bar)
Bar's classLoader: user-defined 1

可以注意到Foo的initiating loader是user-defined 2,但是defining loader是user-defined 1。而Bar的initiating loader与defining loader则直接是user-defined 1,绕过了user-defined 2。观察结果符合预期。

链接

验证(Verification)

验证类的二进制形式在结构上是否正确。

准备(Preparation)

为类创建静态字段,并且为这些静态字段初始化默认值。

解析(Resolution)

JVM在运行时会为每个类维护一个run-time constant pool,run-time constant pool构建自类的二进制形式里的constant_pool表。run-time constant pool里的所有引用一开始都是符号引用(symbolic reference)(见Java Virutal Machine Specification - 5.1. The Run-Time Constant Pool)。符号引用就是并非真正引用(即引用内存地址),只是指向了一个名字而已(就是字符串)。解析阶段做的事情就是将符号引用转变成实际引用)。

Java Virutal Machine Specification - 5.4. Linking:

This specification allows an implementation flexibility as to when linking activities (and, because of recursion, loading) take place, provided that all of the following properties are maintained:

  1. A class or interface is completely loaded before it is linked.
  2. A class or interface is completely verified and prepared before it is initialized.

也就是说仅要求:

  1. 一个类在被链接之前得是完全加载的。
  2. 一个类在被初始化之前得是被完全验证和准备的。

所以对于解析的时机JVM Spec没有作出太多规定,只说了以下JVM指令在执行之前需要解析符号引用:_anewarray_, checkcast_, _getfield_, _getstatic_, _instanceof_, _invokedynamic_, _invokeinterface_, _invokespecial_, _invokestatic_, _invokevirtual_, _ldc_, _ldc_w_, _multianewarray_, _new_, _putfieldputstatic

看不懂没关系,大致意思就是,用到字段、用到方法、用到静态方法、new类等时候需要解析符号引用。

初始化

如果直接赋值的静态字段被 final 所修饰,并且它的类型是基本类型或字符串时,那么该字段便会被 Java 编译器标记成常量值(ConstantValue),其初始化直接由 Java 虚拟机完成。除此之外的直接赋值操作,以及所有静态代码块中的代码,则会被 Java 编译器置于同一方法中,并把它命为 <clinit>class init)。

JVM 规范枚举了下述类的初始化时机是:

  1. 当虚拟机启动时,初始化用户指定的主类;
  2. new 某个类的时候
  3. 调用某类的静态方法时
  4. 访问某类的静态字段时
  5. 子类初始化会触发父类初始化
  6. 用反射API对某个类进行调用时
  7. 一个接口定义了default方法(原文是non-abstract、non-static方法),某个实现了这个接口的类被初始化,那么这个接口也会被初始化
  8. 初次调用 MethodHandle 实例时

注意:这里没有提到new 数组的情况,所以new 数组的时候不会初始化类。

同时类的初始化过程是线程安全的,下面是一个利用上述时机4和线程安全特性做的延迟加载的Singleton的例子:

public class Singleton {private Singleton() {}private static class LazyHolder {static final Singleton INSTANCE = new Singleton();}public static Singleton getInstance() {return LazyHolder.INSTANCE;}
}

这种做法被称为Initialization-on-demand holder idiom。

类加载常见异常

ClassNotFoundException

Java Virutal Machine Specification - 5.3.1. Loading Using the Bootstrap Class Loader:

If no purported representation of C is found, loading throws an instance of ClassNotFoundException.

Java Virutal Machine Specification - 5.3.2. Loading Using a User-defined Class Loader:

When the loadClass method of the class loader L is invoked with the name N of a class or interface C to be loaded, L must perform one of the following two operations in order to load C:

  1. The class loader L can create an array of bytes representing C as the bytes of a ClassFile structure (§4.1); it then must invoke the method defineClass of class ClassLoader. Invoking defineClass causes the Java Virtual Machine to derive a class or interface denoted by N using L from the array of bytes using the algorithm found in §5.3.5.
  2. The class loader L can delegate the loading of C to some other class loader L'. This is accomplished by passing the argument N directly or indirectly to an invocation of a method on L' (typically the loadClass method). The result of the invocation is C.

In either (1) or (2), if the class loader L is unable to load a class or interface denoted by N for any reason, it must throw an instance of ClassNotFoundException.

所以,ClassNotFoundException发生在【加载阶段】:

  1. 如果用的是bootstrap class loader,则当找不到其该类的二进制形式时抛出ClassNotFoundException
  2. 如果用的是用户自定义class loader,不管是自己创建二进制(这里包括从文件读取或者内存中创建),还是代理给其他class loader,只要出现无法加载的情况,都要抛出ClassNotFoundException

NoClassDefFoundError

Java Virtual Machine Specification - 5.3. Creation and Loading

If the Java Virtual Machine ever attempts to load a class C during verification (§5.4.1) or resolution (§5.4.3) (but not initialization (§5.5)), and the class loader that is used to initiate loading of C throws an instance of ClassNotFoundException, then the Java Virtual Machine must throw an instance of NoClassDefFoundError whose cause is the instance of ClassNotFoundException.

(A subtlety here is that recursive class loading to load superclasses is performed as part of resolution (§5.3.5, step 3). Therefore, a ClassNotFoundException that results from a class loader failing to load a superclass must be wrapped in a NoClassDefFoundError.)

Java Virtual Machine Specification - 5.3.5. Deriving a Class from a class File Representation

Otherwise, if the purported representation does not actually represent a class named N, loading throws an instance of NoClassDefFoundError or an instance of one of its subclasses.

Java Virtual Machine Specification - 5.5. Initialization

If the Class object for C is in an erroneous state, then initialization is not possible. Release LC and throw a NoClassDefFoundError.

所以,NoClassDefFoundError发生在:

  1. 【加载阶段】,因其他类的【验证】or【解析】触发对C类的【加载】,此时发生了ClassNotFoundException,那么就要抛出NoClassDefFoundError,cause 是ClassNotFoundException
  2. 【加载阶段】,在【解析】superclass的过程中发生的ClassNotFoundException也必须包在NoClassDefFoundError里。
  3. 【加载阶段】,发现找到的二进制里的类名和要找的类名不一致时,抛出NoClassDefFoundError
  4. 【初始化阶段】,如果C类的Class对象处于错误状态,那么抛出NoClassDefFoundError

追踪类的加载

可以在JVM启动时添加-verbose:class来打印类加载过程。

参考资料

  • Java Language Specification - Chapter 12. Execution
  • Java Virtual Machine Specification - Chapter 5. Loading, Linking, and Initializing
  • 极客时间 - 深入拆解Java虚拟机 - 03 Java虚拟机是如何加载Java类的?(专栏文章,需付费购买)
  • CS-Note 类加载机制
  • 深入理解JVM(八)——类加载的时机
  • 深入理解JVM(九)——类加载的过程

广告

ClassLoader(二)- 加载过程相关推荐

  1. 深入理解Java虚拟机二(类加载器和类的加载过程)

    类加载器子系统作用 类加载器子系统负责从文件系统或者网络中加载Class文件,class文件在文件开头有特定的文件标识. ClassLoader只负责class文件的加载,至于它是否可以运行,则由Ex ...

  2. Java类的加载过程详解 面试高频!!!值得收藏!!!

    受多种情况的影响,又开始看JVM 方面的知识. 1.Java 实在过于内卷,没法不往深了学. 2.面试题问的多,被迫学习. 3.纯粹的好奇. 很喜欢一句话: 八小时内谋生活,八小时外谋发展. 望别日与 ...

  3. JVM学习笔记之-类加载子系统,类的加载与类的加载过程,双亲委派机制

    一 类加载器与类加载过程 类加载子系统作用 类加载器子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识. ClassLoader只负责class文件的加载,至于 ...

  4. 中yeti不能加载_第二十章_类的加载过程详解

    类的加载过程详解 概述 在 Java 中数据类型分为基本数据类型和引用数据类型.基本数据类型由虚拟机预先定义,引用数据类型则需要进行类的加载 按照 Java 虚拟机规范,从 Class 文件到加载到内 ...

  5. jvm类加载过程_JVM类生命周期概述:加载时机与加载过程

    作者:菜鸟小于 https://www.cnblogs.com/Young111/p/11359700.html 一个.java文件在编译后会形成相应的一个或多个Class文件,这些Class文件中描 ...

  6. JVM——类的加载过程

    附一张图方便理解,一个类的执行过程 类的加载过程,简明的来说 类装饰器就是寻找类的字节码文件并构造出类在JVM内部表示的对象组件.在Java中,类装载器把一个类装入JVM中,要经过以下步骤: 装载:查 ...

  7. Trembling ! Java类的加载过程详解(加载验证准备解析初始化使用卸载)

    [1]类的生命周期 一个类从加载进内存到卸载出内存为止,一共经历7个阶段: 加载->验证->准备->解析->初始化->使用->卸载 其中,类加载包括5个阶段: 加载 ...

  8. 类的加载过程(生命周期)

    一.概述 生命周期的7个阶段: 从使用过程看: 最后会在方法区,存在类的模版,之后就可以使用这个类了. 二.过程1:Loading阶段(加载) 所谓加载,就是将字节码文件加载到机器内存中,并在内存中构 ...

  9. 类加载器及其加载过程

    一.内存结构概述 内存结构简图: 内存结构详细图: 中文: 英文: 二.类加载器与加载过程 类加载器子系统作用 图解: 描述: 1.类加载器子系统负责从文件系统或者网络中加载class文件,class ...

最新文章

  1. PowerShell的开源实现
  2. 雷林鹏分享:Redis 管道技术
  3. PostgreSQL在何处处理 sql查询之五十二
  4. Spring Boot 配置文件中的花样,看这一篇足矣!
  5. 波形捕捉:(1)枚举捕捉设备
  6. Android Binder ProcessState IPCThreadState相关介绍
  7. Python中操作MySQL/Oracle
  8. 机器学习(五)——SVM(3)
  9. 《SAS编程与数据挖掘商业案例》学习笔记之九
  10. 如何搭建一个 Data Guard 环境
  11. iOS内存泄漏的常见情况
  12. Java(4)---Java 对象和类
  13. 从零开始学_JavaScript_系列(22)——dojo(9)(表单、JsonRest的post方法,widget的使用思路)...
  14. 自带的jvm监控不准_如何实时监控 Flink 集群和作业?
  15. c++ 模板类实现堆栈实验报告_5分钟学会C/C++多线程编程进程和线程
  16. sftp访问_实时数据处理探索:接收、处理、访问
  17. 阿里云国际版CDN 安全保护指南:管理篡改、攻击和内容
  18. 智课雅思词汇---十一、spect是什么意思
  19. 德怀特·艾森豪威尔(1890-1969)美国第34任总统,陆军五星上将。
  20. Vue实现表格的分页打印和导出Excel功能

热门文章

  1. linux下启动jconsole报 java.lang.UnsatisfiedLinkError
  2. .Net Base64编码
  3. [转载] 晓说——第18期:古代科举考试那些事——招生
  4. jquery操作select option使用小结
  5. 8 基于管道的持久化存储 scrapy
  6. Leetcode 350 两个数的交集2
  7. 云创大会来了!全行业关心的热点都在这里
  8. ODOO权限管理,在两个方面设置权限
  9. boost::asio::streambuf 基本用法和注意事项
  10. 【Nginx入门系列】第六章 Nginx+tomcat集群负载均衡部署