JVM - 类加载器

article/2025/9/18 8:46:18

# 类加载器及类加载器执行过程

JDK版本:1.8

# 1、类加载器子系统

下图为类加载子系统:

类加载子系统

  • 类加载子系统负责从文件系统或者网络中加载class文件,class文件在文件开头有特定的文件标识(CA FE BA BE)。
  • Classloader只负责class文件的加载,至于它是否可以运行,则由Execution Engine决定。
  • 记载的类信息存放在jvm内存中的一块名为Method Area的内存空间中。除了类的信息外,方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量(这部分常量信息是class文件中常量池部分的内存映射)。

# 2、类加载器Classloader的角色

类加载器的角色

  • class file源码文件存在于本地硬盘上,这一理解为一个模板。而这个模板在执行的时候是需要加载到JVM内存中根据这个文件实例化出n个一摸一样的实例。加载class file的方式是二进制流。
  • class file源码被加载到JVM中,被称为DNA元数据模板,存放在JVM内的Method Area中。
  • .class文件--> JVM --> 最终成为DNA元数据模板。这个过程需要一个运输工具(类装载器Classloader)完成。

# 3、类加载的过程

Java类加载的过程(宏观):

类的加载过程

# 3.1、加载(Loading)阶段

加载(Loading)阶段分为3步:

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

加载.class文件的方式:

  • 从本地系统中直接加载。
  • 通过网络获取,典型场景:Web Applet
  • zip压缩包中读取,成为日后jarwar格式的基础。
  • 运行时计算生成,使用最多的是:动态代理技术。
  • 由其他文件生成,典型场景:JSP应用。
  • 从专有数据库中提取.class文件。
  • 从加密文件中获取,典型的防止Class文件被反编译的保护措施。

# 3.2、链接(Linkging)阶段

链接(Linking)阶段分为3个步骤:

  • 验证(Verification):

    • 1、确保被加载类的Class文件的二进制字节流中包含的信息符合当前虚拟机的要求。保证被加载类的正确性,不会危害虚拟机自身安全。
    • 2、主要包括四种验证方式:文件格式验证、元数据验证、字节码验证、符号引用验证。
  • 准备(Preparation):

    • 1、为类变量分配内存并设置该类变量的默认初始值,即零值。注意,这里只是分配内存并为其分配默认值,不同类型的默认值也不同。这里并没有对类变量进行实际的具体赋值。
    • 2、这里不包含被final修饰的static变量(这已经不能被称为变量,此时已经是常量),因为final修饰的常量在编译的时候就会分配内存。准备阶段会进行显示初始化,即对其进行具体的赋值。
    • 3、这里不会为实例变量分配初始化(此时对象还未进行创建),类变量会分配在方法区中,而实例变量则是会随着对象一起分配到堆内存中。
  • 解析(Resolution):

    • 1、将常量池内的符号引用转换为直接引用的过程。
    • 2、解析操作往往是会伴随着JVM在执行完初始化后再执行。
    • 3、符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义在《Java虚拟机规范》Class文件格式中。直接引用就是直接指向目标的指针,相对偏移量或一个间接定位到目标的句柄。
    • 4、解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等。分别对应常量池中的CONSTANT_Class_infoCONSTANT_Fieldref_infoCONSTANT_Methodref_info等。
public class HelloLinking {/*** Preparation 阶段只是为其赋初始值0* Initialization 阶段将3赋值给a*/private static int a = 3;public static void main(String[] args) {System.out.println(a);}
}

# 3.3、初始化(Initialization)阶段

  • 初始化阶段就是执行类构造器方法<clinit>()的过程。
  • 此方法不需要定义,是javac编译器自动收集类中的所有类变量的赋值动作和静态代码块中的语句合并而来。如果类中没有变量需要赋值,是不会出现<clinit>()方法的。
  • 构造器方法中指令按语句在源文件中出现的顺序执行。
  • <clinit>()不同于类的构造器。(关联:构造器是虚拟机视角下的<init>())。
  • 若该类具有父类,JVM会保证子类的<clinit>()执行前,父类的<clinit>()已经执行完毕。
  • 虚拟机必须保证一个类的<clinit>()方法在多线程下被同步加锁。
public class HelloClassLoader {public static void main(String[] args) {Runnable runnable = () -> {System.out.println(getCurrentThreadName() + "开始");new DeadThread();System.out.println(getCurrentThreadName() + "结束");};Thread a = new Thread(runnable, "A线程");Thread b = new Thread(runnable, "B线程");a.start();b.start();}private static class DeadThread {static {System.out.println(getCurrentThreadName() + "正在初始化 [DeadThread] 类");}}private static String getCurrentThreadName() {return Thread.currentThread().getName();}}

最终只会有一个线程获取到锁并初始化DeadThread类。运行程序将会看到只会有一个线程初始化DeadThread类。

B线程开始
A线程开始
B线程正在初始化 [DeadThread] 类
B线程结束
A线程结束Process finished with exit code 0

所以一个类的<clinit>()方法只会被一个线程调用一次。


# 4、类加载器分类

JVM支持两种类型的类加载器 。分别为引导类加载器Bootstrap ClassLoader和自定义类加载器User-Defined ClassLoader

从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但在Java虚拟机规范中却没有这么定义,而是将所有派生于抽象类ClassLoader的类加载器都划分为自定义类加载器

无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个:

  • 引导类加载器(BootStrap Class Loader)
  • 扩展类加载器(Extension Class Loader)
  • 系统类加载器(System Class Loader)

类加载器

它们之间的关系是包含关系,并不是上下层或者子父类继承。

可以看到Java中的扩展类加载器ExtClassLoader,它是Launcher中的一个静态内部类。同时也可以获取到AppClassLoader,其也是定义在Launcher类中的一个静态内部类。而BootStrap Class Loader是获取不到的,它是由CC++进行编写的。

使用代码获取加载器:

public class CustomClassLoader {public static void main(String[] args) {// 获取系统类加载器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();// sun.misc.Launcher$AppClassLoader@18b4aac2System.out.println("systemClassLoader = " + systemClassLoader);// 获取其上层即扩展类加载器ClassLoader extensionClassLoader = systemClassLoader.getParent();// sun.misc.Launcher$ExtClassLoader@1b6d3586System.out.println("extensionClassLoader = " + extensionClassLoader);// 试图获取 BootStrap Class LoaderClassLoader bootStrapClassLoader = extensionClassLoader.getParent();// nullSystem.out.println("bootStrapClassLoader = " + bootStrapClassLoader);// 获取用户自定义类使用的类加载器ClassLoader classLoader = CustomClassLoader.class.getClassLoader();// sun.misc.Launcher$AppClassLoader@18b4aac2System.out.println("classLoader = " + classLoader);ClassLoader stringClassLoader = String.class.getClassLoader();// nullSystem.out.println("stringClassLoader = " + stringClassLoader);}}

输出结果:

systemClassLoader = sun.misc.Launcher$AppClassLoader@18b4aac2
extensionClassLoader = sun.misc.Launcher$ExtClassLoader@1b6d3586
bootStrapClassLoader = null
classLoader = sun.misc.Launcher$AppClassLoader@18b4aac2
stringClassLoader = nullProcess finished with exit code 0

对于开发者自定义的类,默认使用的是系统类加载器进行加载。

对于Java的核心类库都是使用的是引导类加载器进行加载。


# 5、虚拟机自带的类加载器

# 1、启动类加载器(引导类加载器,Bootstrap Class Loader)

  • BootStrap类加载使用C/C++语言实现的,嵌套在JVM内部。
  • BootStrap类加载用来加载Java的核心库(JAVA_HOME/jre/lib/rt.jarresources.jarsun.boot.class.path路径下的内容),用于提供JVM自身需要的类。
  • BootStrap类加载并不继承自java.lang.ClassLoader,没有父加载器。
  • BootStrap类加载器加载扩展类和应用程序类加载器,并指定为他们的父类加载器
  • 出于安全考虑,Bootstrap启动类加载器只加载包名为javajavaxsun等开头的类。

2、扩展类加载器(Extension Class Loader)

  • Java语言编写,由sun.misc.Launcher$ExtClassLoader实现。
  • 派生于ClassLoader类。
  • 父类加载器为启动类加载器。
  • java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

# 3、应用类加载器(系统类加载器,App Class Loader)

  • Java语言编写,由sun.misc.Launchers$AppClassLoader实现。
  • 派生于ClassLoader类。
  • 父类加载器为扩展类加载器。
  • 它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库。
  • 该类加载是程序中默认的类加载器,一般来说,Java应用的类都是由它来完成加载。
  • 通过ClassLoader#getSystemclassLoader() 方法可以获取到该类加载器。
public class TestClassLoader {private static final String SEMICOLON = ";";private static final String EXT_URL = "java.ext.dirs";public static void main(String[] args) {URL[] urLs = Launcher.getBootstrapClassPath().getURLs();Arrays.stream(urLs).forEach(System.out::println);System.out.println("\n");// 从上面的路劲中随便挑选一个类, 查看其类加载器 :ClassLoader proxyClassClassLoader = Proxy.class.getClassLoader();// null --> BootStrap Class LoaderSystem.out.println("proxyClassClassLoader = " + proxyClassClassLoader);System.out.println("\n");System.out.println("===========extension class loader spilt line===========");// 寻找 jre/lib/ext 目录下的 class 获取 ClassLoaderClassLoader ecKeyFactoryClassLoader = ECKeyFactory.class.getClassLoader();// sun.misc.Launcher$ExtClassLoader@7cca494bSystem.out.println("ecKeyFactoryClassLoader = " + ecKeyFactoryClassLoader);System.out.println("\n");String extensionUrl = System.getProperty(EXT_URL);Arrays.stream(extensionUrl.split(SEMICOLON)).forEach(System.out::println);// 获取自定义类的 ClassLoaderClassLoader classLoader = CustomClassLoader.class.getClassLoader();// sun.misc.Launcher$AppClassLoader@18b4aac2System.out.println("classLoader = " + classLoader);}}

# 6、用户自定义类加载器

Java的日常应用程序开发中,类的加载几乎是由BootStrap Class LoaderExtension Class LoaderApp Class Loader类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式。 为什么要自定义类加载器?

  • 隔离加载类.。

  • 修改类加载的方式。

  • 扩展加载源。

  • 防止源码泄漏。

用户自定义类加载器实现步骤:

  • 开发人员可以通过继承抽象类java.lang.ClassLoader类的方式,实现自己的类加载器,以满足一些特殊的需求。
  • JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadClass() 方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadclass() 方法,而是建议把自定义的类加载逻辑写在findClass()方法中。
  • 在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLClassLoader类,可以避免自己去编写findClass()方法及其获取字节码流的方式,使自定义类加载器编写更加优雅简洁。

自定义一个CustomClassLoader类加载器派生于ClassLoader

public class CustomClassLoader extends ClassLoader {private String classPath;public CustomClassLoader(String classPath) {this.classPath = classPath;}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] classFromCustomPath = getClassFromCustomPath();if (classFromCustomPath == null) {throw new FileNotFoundException();} else {return super.defineClass(name, classFromCustomPath, 0, classFromCustomPath.length);}} catch (FileNotFoundException e) {e.printStackTrace();}throw new ClassNotFoundException();}/*** 以二进制流的方式将指定的 class 文件读取到系统中来* 如果指定路劲的字节码进行了加密, 则需要在此方法中进行解密操作, 解密之后将其还远为字节数组** @return byte[]*/private byte[] getClassFromCustomPath() {if (this.classPath == null || "".equals(this.classPath)) {return null;}File file = new File(this.classPath);if (file.exists()) {try (FileInputStream fileInputStream = new FileInputStream(file);ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream()) {byte[] buffer = new byte[1024];int size;while ((size = fileInputStream.read(buffer)) != -1) {byteArrayOutputStream.write(buffer, 0, size);}return byteArrayOutputStream.toByteArray();} catch (IOException e) {e.printStackTrace();}}return null;}}

创建一个Log类使用自定义类加载器进行加载:

public class Log {public static void main(String[] args) {System.out.println("Log class load by Custom Class Loader Success!");}}

运行Log类中的main方法,运行完成之后在out文件目录下找到该类的class文件即Log.class文件。

创建一个TestCustomClassLoader类用于测试自定义类加载器:

public class TestCustomClassLoader {// Log 类的全类名private static final String ALL_PACKAGE_NAME = "com.kapcb.ccc.jvm.classload.Log";// Log 类的 class 文件路径private static final String LOG_CLASS_PATH = "D:/DevelopTools/IDEA/IDEA-workspace/Java-Kapcb/out/production/Java-Kapcb/com/kapcb/ccc/jvm/classload/Log.class";public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {CustomClassLoader customClassLoader = new CustomClassLoader(LOG_CLASS_PATH);Class<?> LogClass = customClassLoader.loadClass(ALL_PACKAGE_NAME);ClassLoader classLoader = LogClass.getClassLoader();System.out.println("Log 类的类加载器是 : [ " + classLoader + " ]");// 获取 Log 类中的 main 方法Method mainMethod = LogClass.getDeclaredMethod("main", String[].class);// 实例化 Log 类Object object = LogClass.newInstance();// 随便传入一个参数String[] arg = new String[]{"ad"};// 反射激活 Log 类中的 main 方法mainMethod.invoke(object, (Object) arg);}}

启动测试类的main方法输出结果:

Log 类的类加载器是 : [ sun.misc.Launcher$AppClassLoader@18b4aac2 ]
Log class load by Custom Class Loader Success!Process finished with exit code 0

# 7、ClassLoader的常用API

ClassLoader类是一个抽象类,其后所有的类加载器都继承自ClassLoader(不包括启动类加载器),其常用API有以下几个方法:

方法名称描述
getParent()返回当前类加载器的超类加载器
loadClass(String name)加载名称为name的类(这里的name是全类名),返回结果为java.lang.Class类的实例
findClass(String name)查找名称为name的类(这里的name是全类名),返回结果为java.lang.Class类的实例
findLoadedClass(String name)查找类名为name的已经被加载过的类,返回结果为java.lang.Class类的实例
defineClass(String name, byte[] b, int off, int len)将字节数组b中的内容转换为一个Java类,返回结果为java.lang.Class类的实例
resolveClass(Class<?> cla)链接指定的一个Java

sun.misc.Launcher是一个java虚拟机的入口应用:

classLoader继承图

获取ClassLoader的途径:

方式一:获取当前类的ClassLoader

clazz.getClassLoader();

方式二:获取当前线程上下文的ClassLoader

Thread.currentThread().getContextClassLoader();

方式三:获取系统的ClassLoader

ClassLoader.getSystemClassLoader();

方式四:获取调用者的ClassLoader

DriverManager.getCallerClassLoader();

GitHub源码地址:https://github.com/kapbc/Java-Kapcb/tree/master/src/main/java/com/kapcb/ccc/jvm

备注:此文为笔者学习JVM的笔记,鉴于本人技术有限,文中难免出现一些错误,感谢大家批评指正。


http://chatgpt.dhexx.cn/article/w3PLHYZq.shtml

相关文章

java的类加载器以及如何自定义类加载器

ClassLoader作用 类加载流程的"加载"阶段是由类加载器完成的。 类加载器结构 结构&#xff1a;BootstrapClassLoader&#xff08;祖父&#xff09;–>ExtClassLoader&#xff08;爷爷&#xff09;–>AppClassLoader(也称为SystemClassLoader)&#xff08;爸…

类加载器(ClassLoader)

一、类加载器&#xff08;ClassLoader&#xff09; 1.1 什么是类加载器 Java的类加载器是Java虚拟机&#xff08;JVM&#xff09;的重要组成部分&#xff0c;它的主要作用是动态地将Java类加载到JVM中&#xff0c;以便在运行时使用这些类。Java类加载器通常是由JVM的实现者提供…

什么是类加载器,类加载器如何分类

一、类加载器 1.什么是类加载器 类加载器&#xff1a;负责将.class文件&#xff08;存储的物理文件&#xff09;加载到内存中 2.类加载时机&#xff1a; ① 创建类的实例&#xff08;对象&#xff09; ② 调用类的实例方法 ③ 访问类或者接口的类变量&#xff0c;或者为该…

深入理解Java虚拟机——再谈类的加载器——第十二章——中篇

深入理解Java虚拟机——Java虚拟机介绍——第一章 深入理解Java虚拟机——类加载子系统——第二章 深入理解Java虚拟机——运行时数据区和本地方法接口——详细篇——第三章 深入理解Java虚拟机——对象的实例化内存布局与访问定位——超级详细篇——第四章 深入理解Java虚拟机…

类加载器详解

类加载器的分类 JVM支持两种类型的类加载器,分别为引导类加载器(BootstrapClassLoader)和自定义类加载器(User-Defined ClassLoader) 从概念上来讲, 自定义类加载器一般指的是程序中由开发人员自定义的一类,类加载器,但是Java虚拟机规范却没有这么定义,而是将所有派生于抽象类…

Java - 类加载器

文章目录 1. 类加载的过程2. 类加载器的分类2.1 引导类加载器 Bootstrap2.2 扩展类加载器 ExtClassLoader2.3 系统类加载器 AppClassLoader2.4 三者之间的关系2.5 自定义类加载器2.6 注&#xff1a;谁来准备类加载器呢? 3. 双亲委派机制4. ClassLoader抽象类5. URLClassLoader…

DAU 和 MAU

DAU 和 MAU 日活跃用户占月活跃用户的比例越高&#xff0c;表明用户对App的使用粘性越高。 DAU&#xff0c;即&#xff1a;Daily Active User&#xff0c;指日活跃用户数 MAU&#xff0c;即&#xff1a;Monthly Active User&#xff0c;指月活跃用户数。 *例子1&#xff1a;…

如何通过DAU分析活跃用户?(案例:python绘制箱体图)

前言&#xff1a;本文内容以游戏产品为基础进行讲解&#xff0c;内容为以下4部分&#xff1a; 1. 如何理解DAU反映了哪些问题&#xff1f; 2. 有哪些因素会影响DAU变动&#xff1f; 3. 如何解读DAU的“箱体图”&#xff1f; 4. 如何使用python绘制“箱体图”&#xff1f; DAU的…

【数据分析】产品日活DAU下降,怎么分析

目录 案例简介 第一步&#xff1a;确认数据真实性 第二步&#xff1a;明确定义&#xff0c;并拆解指标&#xff0c;进一步定位原异常部分 第三步&#xff1a;根据几个常见维度初步拆分数据 第四步&#xff1a;进一步做假设并细分深入&#xff0c;得出结论 案例分析 例题…

数据分析——DAU下降问题(转)

文章转自&#xff1a; DAU异常下降该如何分析 1. 梳理公司的用户增长模式 尽管不同业务形态、以及不同发展阶段的公司&#xff0c;其用户增长模式各有差异&#xff0c;但都可以从拉新策略和促活策略进行分解。 常见的拉新策略有&#xff1a; 流量采购。比如通过厂商预装、…

有赞一面:亿级用户DAU日活统计,有几种方案?

说在前面 在40岁老架构师 尼恩的读者社区(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如极兔、有赞、希音、百度、网易、滴滴的面试资格&#xff0c;遇到一几个很重要的面试题&#xff1a; (1) 亿级用户场景&#xff0c;如何高性能统计日活&#xff1f; (2) 如何实现亿…

数据分析——用户粘性指标 DAU/MAU

&#xff08; 一 &#xff09;定义 DAU&#xff0c;即&#xff1a;Daily Active User&#xff0c;指日活跃用户数&#xff1b; MAU&#xff0c;即&#xff1a;Monthly Active User&#xff0c;指月活跃用户数。 日活/月活就是体现用户粘性最频繁使用的指标。日活跃用户占月活…

数据分析体系 - 用户粘性(DAU/MAU 和 月人均活跃天数)

对于常见的App&#xff0c;用户粘性的取值范围就是3%~100%&#xff0c; 不同领域的App也会有不同的基准值&#xff0c; 例如移动游戏会以20%为基线&#xff0c; 而工具类App会以40%为基线。 用户粘性的两个计算指标&#xff1a; 1、DAU/MAU 2、月平均活跃人数 这里其实…

数据分析——DAU下降/上升原因分析

本文是对“DAU变动原因”问题进行的思考整理&#xff0c;仅作记录&#xff0c;欢迎讨论。 &#xff08; 一 &#xff09;思维框架 图版&#xff1a; 文版&#xff1a; 内部原因&#xff1a; 1. 数据验证 如果DAU上升或者下降&#xff0c;且非日常波动&#xff0c;需先确…

数据分析 — 用户粘性的两个计算指标(DAU/MAU和月人均活跃天数)

很多运营都了解DAU&#xff08;日活跃用户数&#xff09;和MAU&#xff08;月活跃用户数&#xff09;的重要性&#xff0c;但在某些情况下这两个数值本身并不能反映出太多问题&#xff0c;这个时候就要引用到【DAU/MAU】的概念&#xff0c;即【日活/月活】。 用户粘性的两个计…

峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?

小游戏《羊了个羊》 短短的7天内&#xff0c;DAU突破了1亿、吞吐量峰值21WQps。 《羊了个羊》运营后台数据显示&#xff0c;在短短的7天内&#xff0c;这款小游戏的DAU就突破了1亿。 要知道&#xff0c;除了王者荣耀、原神等屈指可数的现象级手游之外&#xff0c;1亿DAU是这个…

dau、mau、pcu、dnu、wau、acu、uv分别是什么意思?

dau、mau、pcu、dnu、wau、acu、uv的意思是什么?怎么分析? DAU(Daily Active User)日活跃用户数量。常用于反映网站、互联网应用或网络游戏的运营情况。 MAU(monthly active users)月活跃用户人数。是在线游戏的一个用户数量统计名词&#xff0c;数量越大意味着玩这款游戏的人…

数据化运营04 DAU、MAU、UV:谁是最有参考价值的活跃指标?

活跃类指标是重要的用户质量指标&#xff0c;代表了产品上真正的用户&#xff0c;代表了具备营销价值的用户群&#xff0c;是几乎所有产品运营的重点。 在这一讲中&#xff0c;我会主要向你介绍 UV、DAU、MAU&#xff0c;以及每日使用时长和每日打开频次等指标。 你是否思考过…

如何理解、分析DNU/DAU?(案例:DNU、DAU面积图)

前言&#xff1a;本文内容以游戏产品为基础进行讲解&#xff0c;内容为以下4部分&#xff1a; 1. 如何理解DNU和DAU 2. 如何使用python绘制DNU和DAU的面积图 3. 如何分析DNU/DAU 4. 如何计算当日DAU有多少由往日N日DAU贡献的&#xff0c;并简单计算用户生命周期&#xff1f; DN…

DAU是啥,数据指标是啥?必知必会的数据分析常识

在刚迈入数据的大门时&#xff0c;我经常对一些数据指标或者数据本身的概念很模糊&#xff0c;尤其是当跟运营、数据分析师扯需求的时候&#xff0c;会被这些密密麻麻的指标给弄糊涂。为了更好的在行业里面摸打滚爬&#xff0c;花了很多时间阅读一些指标相关的文章、书籍&#…