类加载过程
类加载过程包含了加载、验证、准备、解析、初始化五个阶段。其中加载、验证、准备、初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)。需要注意的是,这几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
1. 加载
加载是类加载过程的第一个阶段,JVM需要完成以下几件事情:
- 通过一个类的全限定名来获取二进制字节流
- 将二进制字节流所代表的的静态存储结构转化为方法区的运行时数据结构
- 在Java堆中生成一个代表这个类的java.lang.Class对象,作为方法区中该运行时数据结构的访问入口
注意: 类的二进制字节流不仅可以从Class文件中获取,还可以从jar包、网络等其他途径获取。
2. 链接
2.1 验证
验证的目的是保证Class流的格式是正确的,主要分为文件格式验证、元数据验证、字节码验证、符号引用验证等。
②版本号是否合理
③......
②继承了final类?
③非抽象类实现了所有的抽象方法
④......
②栈数据类型和操作码数据参数吻合
③跳转指令指定到合理的位置
④......
②访问的方法或字段是否存在,且有足够的权限(public、private等描述符)
2.2 准备
准备阶段为类变量分配内存(在方法区中分配),并为类变量设置零值。
比如:
public static int v = 1;
在准备阶段,v会被设置为0,在初始化阶段的<clinit>中才会被设置为1。
对于static final修饰的常量,在准备阶段即会被赋上正确的值(即准备阶段v就会被设置为1),比如:
public static final v = 1;
2.3 解析
解析阶段是将符号引用替换为直接引用的过程。
符号引用:字符串,引用对象不一定被加载
直接引用:指针或地址偏移量,引用对象一定在内存中
3. 初始化
初始化实际上就是执行类构造器<clinit>的过程(注意与实例构造器<init>区分),主要是类变量的赋值,包括static变量赋值、static{}语句块执行等。
两个特性:
- 子类的<clinit>调用前保证父类的<clinit>执行完毕
- <clinit>是线程安全的
类加载器ClassLoader
ClassLoader是一个抽象类,它的实例负责读取Java字节码文件并装载到JVM中。在加载阶段,我们说类的二进制流可以从多个途径获取,实际上就是通过定制不同的ClassLoader实现的,而ClassLoader也仅仅负责类加载过程中的加载阶段。
/** * 载入并返回一个Class */ public Class<?> loadClass(String name) throws ClassNotFoundException /** * 通过二进制流,定义一个Class对象 */ protected final Class<?> defineClass(byte[] b, int off, int len) /** * loadClass会回调该方法 * * 自定义ClassLoader时推荐重写该方法,不会破坏双亲委派模型 */ protected Class<?> findClass(String name) throws ClassNotFoundException /** * 在当前类加载器中寻找已经加载的类 */ protected final Class<?> findLoadedClass(String name)
类加载器主要分为以下几类:
- BootStrap ClassLoader(启动类加载器)
- Ext ClassLoader(扩展类加载器)
- App ClassLoader(应用类加载器 / 系统类加载器)
- Custom ClassLoader(自定义类加载器)
每个类加载器都有一个Parent作为父亲,结构如下图:
这种结构关系称为类加载器的双亲委派模型。我们把每一层上面的类加载器叫做当前类加载器的父加载器,但它们之间的父子关系并不是通过继承关系来实现的,而是使用组合关系来复用类加载器中的代码。
注意:扩展类加载器与启动类加载器不存在父子关系,即Launcher#ExtClassLoader类中持有的parent属性为null。
双亲委派模式的工作流程如图所示:当使用某个类时,从当前类加载器开始自底向上检查类是否已经加载,如果某个类加载器已经加载了该类,则直接返回Class对象;如果直到启动类加载器也无法找到该类,则自顶向下尝试加载类,任意类加载器加载成功则直接返回。如果直到当前类加载器尝试加载但仍然失败时,抛出ClassNotFoundException异常。
public static Class<?> checkAndLoadMain(boolean var0, int var1, String var2) { initOutput(var0); String var3 = null; switch(var1) { case 1: var3 = var2; break; case 2: var3 = getMainClassFromJar(var2); break; default: throw new InternalError("" + var1 + ": Unknown launch mode"); } var3 = var3.replace('/', '.'); Class var4 = null; try { // scloader为App ClassLoader实例,调用scloader.loadClass方法跳转到ClassLoader抽象类中 ① var4 = scloader.loadClass(var3); } catch (ClassNotFoundException | NoClassDefFoundError var8) { if (System.getProperty("os.name", "").contains("OS X") && Normalizer.isNormalized(var3, Form.NFD)) { try { var4 = scloader.loadClass(Normalizer.normalize(var3, Form.NFC)); } catch (ClassNotFoundException | NoClassDefFoundError var7) { abort(var8, "java.launcher.cls.error1", var3); } } else { abort(var8, "java.launcher.cls.error1", var3); } } appClass = var4; if (!var4.equals(LauncherHelper.FXHelper.class) && !LauncherHelper.FXHelper.doesExtendFXApplication(var4)) { validateMainClass(var4); return var4; } else { LauncherHelper.FXHelper.setFXLaunchParameters(var2, var1); return LauncherHelper.FXHelper.class; } }
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded // 在当前类加载器中查找是否已经加载 ② Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { // 当前类加载器还未加载时,如果父加载器存在,则委托父加载器加载(递归)③ if (parent != null) { c = parent.loadClass(name, false); } else { // 启动类加载器尝试加载该类 ④ c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } // 如果启动类加载器仍然无法加载,则当前类加载器尝试加载 ⑤ if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { resolveClass(c); } return c; } }
假设场景为应用程序new一个对象Student,但Student类未加载,具体步骤:
- App ClassLoader加载Student类,对应①
- App ClassLoader查找是否加载了Student类,对应②
- App ClassLoader未加载,委托其父加载器(Ext ClassLoader)进行加载,对应③
- Ext ClassLoader查找是否加载了Student类,对应②
- Ext ClassLoader未加载,其parent为null,委托BootStrap ClassLoader进行加载,对应④
- BootStrap ClassLoader没有加载到,则Ext ClassLoader尝试加载,对应⑤
- Ext ClassLoader没有加载到,App ClassLoader尝试加载,对应⑤
如何验证自顶向下加载
package com.example.demo; // 实际路径为D:\WorkSpaces\demo\src\main\java\com\example\demo public class Test { public static void main(String[] args) { Student student = new Student(); student.sayHello(); } }
package com.example.demo; // 实际路径为D:\WorkSpaces\demo\src\main\java\com\example\demo public class Student { public void sayHello() { System.out.println("App ClassLoader"); } }
package com.example.demo; // 实际路径为D:\temp\com\example\demo,包名保持一致,并单独编译成class文件 public class Student { public void sayHello() { System.out.println("BootStrap ClassLoader"); } }
不带任何参数,执行Test#main()方法,打印如下:
App ClassLoader
添加JVM参数 -Xbootclasspath/a:D:/temp,修改BootStrap ClassLoader加载路径,打印如下:
BootStrap ClassLoader
双亲委派模型有何弊端?
双亲委派模型存在一个弊端:顶层ClassLoader无法加载底层ClassLoader的类。比如,Java框架(核心包rt.java)如何加载应用的类?举个例子,javax.xml.parsers包中定义了xml解析的类接口Service Provider Interface SPI 位于rt.jar ,即接口在启动ClassLoader中,而SPI的实现类,在App ClassLoader。
JDK为我们提供了一个上下文加载器的概念,它只是一个角色,任何类加载器都可以被设置为上下文加载器。其基本思路是在顶层ClassLoader中传入底层ClassLoader的实例,用以解决顶层ClassLoader无法访问底层ClassLoader的类。
static private Class getProviderClass(String className, ClassLoader cl, boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException { try { if (cl == null) { if (useBSClsLoader) { return Class.forName(className, true, FactoryFinder.class.getClassLoader()); } else { // 获取上下文ClassLoader cl = ss.getContextClassLoader(); if (cl == null) { throw new ClassNotFoundException(); } else { // 使用上下文ClassLoader return cl.loadClass(className); } } } else { return cl.loadClass(className); } } catch (ClassNotFoundException e1) { if (doFallback) { // Use current class loader - should always be bootstrap CL return Class.forName(className, true, FactoryFinder.class.getClassLoader()); } ......
打破双亲委派模型,自定义类加载器,实现自底向上加载
import java.net.URL; import java.net.URLClassLoader; public class MyClassLoader extends URLClassLoader { public MyClassLoader(URL[] urls) { super(urls); } // 打破双亲模式,保证自己的类会被自己的classloader加载 @Override protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { Class c = findLoadedClass(name); if (c == null) { try { // 先使用当前类加载器查找 c = findClass(name); } catch (Exception e) { } } // 若当前类加载器查找不到,使用父加载器查找 if (c == null) { c = super.loadClass(name, resolve); } return c; } }
参考
- 【深入Java虚拟机】之四:类加载机制
- 《深入JVM内核—原理、诊断与优化》系列课程 - 葛一鸣老师
相关推荐
### Java 类加载机制详解 Java 类加载机制是Java运行时环境的一个核心组成部分,它负责将编译后的Java字节码加载到JVM中,并确保Java应用程序能够正确地运行。类加载机制不仅涉及到类的加载、验证、准备、解析和...
### 深入研究Java类加载机制 #### 一、Java类加载机制概述 Java类加载机制是Java程序运行的第一步,它对于理解Java虚拟机(JVM)的行为至关重要。类加载过程涉及到类的加载、链接(验证、准备、解析)、初始化等...
Java 类加载机制是Java平台的核心特性之一,它负责将类的字节码加载到Java虚拟机(JVM)中并转换为运行时的类对象。理解这一机制对于优化应用程序性能和解决类相关的错误至关重要。 首先,类加载的过程分为三个主要...
JAVA 类加载机制是Java平台核心特性之一,它关乎到程序的运行时环境和代码的动态加载。理解这一机制有助于开发者解决与对象创建、配置问题、应用程序发布等相关的问题。以下是关于JAVA 类加载机制的详细分析: 首先...
Java 类加载机制原理与实现 Java 类加载机制是 Java 虚拟机(JVM)的一部分,负责将编译后的 Java 字节码文件加载到 JVM 中,以便执行 Java 程序。类加载机制是 JVM 的核心组件之一,对 Java 程序的执行和安全性起...
### JAVA类加载机制与动态代理 #### 一、类加载机制 ##### 1.1 类加载的时机 类加载机制负责将描述类的数据从`.class`文件加载到内存,并进行必要的校验、转换解析和初始化,使之成为可以被Java虚拟机直接使用的...
Java类加载机制是Java平台核心特性之一,它负责将类的.class文件加载到JVM(Java虚拟机)中,使得程序能够运行。本篇主要基于“译 Java类加载机制(一、二)”的博客内容,深入探讨Java的类加载过程、类加载器以及...
Java 类加载机制是Java运行时环境中的核心组成部分,它负责将`.class`文件中的字节码转换为运行时的Class对象。在这个过程中,类加载器扮演着至关重要的角色。本篇文章将深入探讨Java的双亲模型类加载机制,以及如何...
Java类加载机制是Java程序运行的关键部分,它使得程序能够在运行时动态加载和执行。类加载涉及多个步骤,包括加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)和初始化...
Java类加载机制详解 Java类加载机制是Java虚拟机(JVM)中的一种机制,负责将类从字节码文件加载到内存中,并将其转换为可执行的类对象。在Java中,类加载机制是通过ClassLoader来完成的,该机制在JDK 1.2以后变得...
Java类加载机制是Java平台的一个重要特性,它负责将类的字节码文件加载到JVM中,并在内存中创建对应的类对象。类加载机制涉及类加载顺序、类加载器的体系结构、类加载过程以及双亲委派模型等核心概念。架构师或高级...
Java类加载机制是Java运行时环境中的核心组成部分,它负责将编译后的字节码(.class文件)加载到Java虚拟机(JVM)中,使得程序能够执行。这一过程涉及多个步骤,包括加载、验证、准备、解析和初始化。理解类加载...
### Java 类加载机制详解 #### 一、引言 Java 类加载机制是Java虚拟机(JVM)中的一个重要组成部分,它负责将编译后的字节码文件(.class)加载到内存中,形成Class对象,以便于Java程序能够识别并使用这些类。深入...
Java类加载机制是Java虚拟机(JVM)的核心特性,它负责在程序运行时找到并加载所需的类。在E-learning平台的功能模块更新中,这一机制起着至关重要的作用,因为它允许平台在不影响其他功能模块正常运行的情况下动态...
该文件是JVM中关于类加载机制的知识整理的思维导图,包括类加载机制概述、类加载的生命周期、加载时机、加载过程、类加载、类的初始化和实例化等几个大方面进行了讲解,其中类加载中还对JVM三种预定义类加载器进行了...
详解JAVA类加载机制 JAVA类加载机制是JAVA虚拟机(JVM)的一部分,它负责将.class文件加载到内存中,并对其进行校验、转换、解析和初始化,以形成可以被JVM直接使用的JAVA类型。下面将详细介绍JAVA类加载机制的相关...