类加载器详解

article/2025/9/18 8:45:34

类加载器的分类

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

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

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

image-20210826073741259

所以具体为引导类加载器(BootstrapClassLoader)和自定义类加载器(包括ExtensionClassLoader、Application ClassLoader、User Defined ClassLoader)

Application ClassLoader也叫System ClassLoader

image-20210826074203975

可以看出ExtensionClassLoader、APPClassLoader都间接继承了ClassLoader。

四类加载器之间的关系可以看做是阶级关系,分为上下级,并不是子父类的继承关系。

Boot strapClassLoader由C/C++实现的,其他三类加载器是由Java实现的

package com.dongguo.jvm02;/*** @author Dongguo* @date 2021/8/26 0026-9:07* @description:*/
public class ClassLoader1 {public static void main(String[] args) {//获取系统类加载器ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2//获取上层 扩展类加载器ClassLoader extClassLoader = systemClassLoader.getParent();System.out.println(extClassLoader);//sun.misc.Launcher$ExtClassLoader@1b6d3586//获取上层 引用类加载器  获取不到引用类加载器ClassLoader bootStrapClassLoader = extClassLoader.getParent();System.out.println(bootStrapClassLoader);//null//对于用户自定义类使用的加载器  默认使用系统类加载器加载ClassLoader classLoader = ClassLoader1.class.getClassLoader();System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@18b4aac2//String类使用引导类加载器加载,Java核心类库都是使用引导类加载器加载ClassLoader StringClassLoader = String.class.getClassLoader();System.out.println(StringClassLoader);//null}
}

虚拟机自带的加载器

启动类加载器(引导类加载器 BootStrap ClassLoader)

这个类加载使用C/C++语言实现的,嵌套在JVM内部。

它用来加载Java的核心库(JAVA HOME/jre/lib/rt.jar.resources.jar或sun.boot.class. path路径下的内容) ,用于提供JVM自身需要的类

并不继承自java.lang.ClassLoader,没有父加载器。

加载扩展类和应用程序类加载器,并指定为他们的父类加载器。

出于安全考虑, Bootstrap启动类加载器只加载包名为java, javax.sun等开头的类

扩展类加载器(Extension ClassLoader)

Java语言编写,由sun.misc. Launcher$ExtclassLoader实现

派生于ClassLoader类

父类加载器为启动类加载器

从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录(扩展目录)下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载。

应用类加载器(系统类加载器 AppClassLoader)

java语言编写,由sun.misc.Launcher$AppClassLoader实现

派生于classLoader类

父类加载器为扩展类加载器

它负责加载环境变量classpath或系统属性java.class.path指定路径下的类库

该类加载是程序中默认的类加载器,一般来说, Java应用的类都是由它来完成加载

通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器

package com.dongguo.jvm02;import com.sun.net.ssl.internal.ssl.Provider;
import sun.misc.Launcher;import java.net.URL;/*** @author Dongguo* @date 2021/8/26 0026-9:27* @description:*/
public class ClassLoader2 {public static void main(String[] args) {System.out.println("------启动类加载器------");//获取BootStrapClassLoader能够加载的API的路径URL[] urLs = Launcher.getBootstrapClassPath().getURLs();for (URL url : urLs) {System.out.println(url.toExternalForm());}/*------启动类加载器------file:/E:/software/java/jdk/jre/lib/resources.jarfile:/E:/software/java/jdk/jre/lib/rt.jarfile:/E:/software/java/jdk/jre/lib/sunrsasign.jarfile:/E:/software/java/jdk/jre/lib/jsse.jarfile:/E:/software/java/jdk/jre/lib/jce.jarfile:/E:/software/java/jdk/jre/lib/charsets.jarfile:/E:/software/java/jdk/jre/lib/jfr.jarfile:/E:/software/java/jdk/jre/classes*///从上面的路径中随意选择一个类,查看使用的类加载器ClassLoader classLoader = Provider.class.getClassLoader();System.out.println(classLoader);//null    //说明使用的是BootStrapClassLoader加载}
}

image-20210826094240834

package com.dongguo.jvm02;import sun.misc.Launcher;
import sun.security.ec.ECKeyFactory;import java.net.URL;
import java.security.interfaces.ECKey;
import java.util.Properties;/*** @author Dongguo* @date 2021/8/26 0026-9:47* @description:*/
public class ClassLoader3 {public static void main(String[] args) {System.out.println("------扩展类加载器------");//获取SystemClassLoader能够加载的API的路径String extDirs = System.getProperty("java.ext.dirs");for (String path : extDirs.split(";")) {System.out.println(path);}/*------扩展类加载器------E:\software\java\jdk\jre\lib\extC:\WINDOWS\Sun\Java\lib\ext*///从上面的路径中随意选择一个类,查看使用的类加载器ClassLoader classLoader = ECKeyFactory.class.getClassLoader();System.out.println(classLoader);//sun.misc.Launcher$ExtClassLoader@1b6d3586 //扩展类加载器}
}

image-20210826095309409

用户自定义的加载器

用户自定义类加载器

在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,来定制类的加载方式

为什么要自定义类加载器?

隔离加载类

修改类加载的方式

扩展加载源

防止源码泄漏

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

1,开发人员可以通过继承抽象类java . lang.ClassLoader类的方式,实现自己的类加载器,以满足一些特殊的需求

2,在JDK1.2之前,在自定义类加载器时,总会去继承ClassLoader类并重写loadClass ()方法,从而实现自定义的类加载类,但是在JDK1.2之后已不再建议用户去覆盖loadClass ()方法,而是建议把自定义的类加载逻辑写在findClass ()方法中

3·在编写自定义类加载器时,如果没有太过于复杂的需求,可以直接继承URLCIassLoader类,这样就可以避免自己去编写findclass ()方法及其获取字节码流的方式,使自定义类加载器编写更加简洁。

自定义用户类加载器大致流程

package com.dongguo.jvm02;import java.io.FileNotFoundException;/*** @author Dongguo* @date 2021/8/26 0026-10:01* @description: 自定义用户类加载器大致流程*/
public class CustomClassLoader extends ClassLoader {@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] result = getClassFromCustomPath(name);if (result == null) {throw new FileNotFoundException();} else {return defineClass(name, result, 0, result.length);}} catch (FileNotFoundException e) {e.printStackTrace();}throw new ClassNotFoundException();}private byte[] getClassFromCustomPath(String name) {//细节略//根据路径读取二进制流的方式将指定类读取到内存中形成字节数组//如果指定路径的字节码文件进行了加,则需要在此方法中进行解密操作。return null;}public static void main(String[] args) {CustomClassLoader customClassLoader = new CustomClassLoader();try {Class<?> clazz = Class.forName("your class path", true, customClassLoader);Object obj = clazz.newInstance();System.out.println(obj.getClass().getClassLoader());} catch (Exception e) {e.printStackTrace();}}
}

关于ClassLoader

ClassLoader类,它是一个抽象类,其后所有的类加载器都继承自ClassLoader (不包括启动类加载器)

image-20210826101617539

获取ClassLoader的途径

image-20210826101756258

package com.dongguo.jvm02;/*** @author Dongguo* @date 2021/8/26 0026-10:18* @description: 获取ClassLoader*/
public class ClassLoader4 {public static void main(String[] args) {try {//第一种方法ClassLoader classLoader1 = Class.forName("java.lang.String").getClassLoader();System.out.println(classLoader1);//第二种ClassLoader classLoader2 = Thread.currentThread().getContextClassLoader();System.out.println(classLoader2);//第三种ClassLoader classLoader3 = ClassLoader.getSystemClassLoader().getParent();System.out.println(classLoader3);} catch (ClassNotFoundException e) {e.printStackTrace();}}
}
运行结果
null
sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@1b6d3586

类加载器的特点

1.全盘负责:当一个类加载器加载一个类时,该类所依赖的其他类也会被这个类加载器加载到内存中。
2.缓存机制:所有的Class对象都会被缓存,当程序需要使用某个Class时,类加载器先从缓存中查找,找不到,才从class文件中读取数据,转化成Class对象,存入缓存中。

3.双亲委派机制

双亲委派机制

Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象。而且加载某个类的class文件时, Java虚拟机采用的是双亲委派模式,即把请求交由父类处理,它是一种任务委派模式。

工作原理

image-20210826111921911

1)如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行

2)如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达项层的启动类加载器;

3)如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。

自己创建一个java.lang.String类

package java.lang;/*** @author Dongguo* @date 2021/8/26 0026-11:22* @description: 自定义的String类*/
public class String {static {System.out.println("执行自定义的String类的静态代码快");}
}

调用自定义的String

package com.dongguo.jvm02;/*** @author Dongguo* @date 2021/8/26 0026-11:20* @description:*/
public class StringTest {public static void main(String[] args) {//如果使用自定义的String,会输出静态代码块的内容java.lang.String str = new java.lang.String();System.out.println("Hello JVM");}
}
运行结果:
Hello JVM

发现并没有输出自定义String类的静态代码块内容,说明并非使用的自定义的String类

没有加载自定义的String类,使用的还是核心api中的String类

尝试运行自定义的String类

package java.lang;/*** @author Dongguo* @date 2021/8/26 0026-11:22* @description: 自定义的String类*/
public class String {static {System.out.println("执行自定义的String类的静态代码快");}public static void main(String[] args) {System.out.println("启动自定义的String类");}
}

image-20210826113457186

类加载器加载自定义的类的加载请求时,由于双亲委派机制,加载请求到达引导类加载器,引导类加载器会去加载Java核心库自带的String类,String类并没有main方法,因此报错

整个过程并没有加载自定义的java.lang.String类

反向委托

image-20210826114147678

当类加载器加载第三方接口提供的jar时,

比如SPI接口,SPI属于核心api,首先通过双亲委托机制将类加载请求委托到引导类加载器,引用类加载器加载rt.jar 的SPI核心类,SPI接口中使用有第三方的jar包jdbc.jar。第三方的jar需要使用应用类加载器去加载,这个步骤就叫反向委派,通过线程上下文类加载器(通过调用当前线程的getContextClassLoader()方法获得**Thread.currentThread().getContextClassLoader();**)线程上下文类加载器就是应用类加载器

优势

避免类的重复加载

保护程序安全,防止核心AP1被随意篡改

自定义类: java.lang.String

自定义类: java.lang.Shkstart

在自己创建的java.lang包下创建一个类Shkstart,运行

package java.lang;/*** @author Dongguo* @date 2021/8/26 0026-11:55* @description:*/
public class Shkstart {public static void main(String[] args) {System.out.println("hello");}
}

image-20210826115701170

提示禁止使用包名java.lang,通过双亲委派机制引用类加载器加载Shkstart,发现Shkstart是自定义的类,为防止核心AP1被随意篡改,发生报错。

沙箱安全机制

自定义string类,但是在加载自定义string类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载jdk自带的文件’(rt.jar包中java\lang\string.class) ,报错信息说没有main方法,就是因为加载的是rt.jar包中的string类。这样可以保证对java核心源代码的保护,这就是沙箱安全机制。

其它

判断两个Class对象是否为同一个类

在JVM中表示两个Class对象是否为同一个类存在两个必要条件:

​ 类的完整类名必须一致,包括包名。

​ 加载这个类的Classoader (指ClassLoader实例对象)必须相同。

换句话说,在JVM中,即使这两个类对象(class对象)来源同一个Class文件,被同一个虚拟机所加载,但只要加载它们的ClassLoader实例对象不同,那么这两个类对象也是不相等的。

对类加载器的引用

JVM必须知道一个类型是由启动加载器加载的还是由用户类加载器加载的。如果一个类型是由用户类加载器加载的,那么JVM会将这个类加载器的一个引用作为类型信息的一部分保存在方法区中。当解析一个类型到另一个类型的引用的时候, JVM需要保证这两个类型的类加载器是相同的。

类的主动使用和被动使用

Java程序对类的使用方式分为:主动使用和被动使用。

主动使用,又分为七种情况:

​ 创建类的实例

​ 访问某个类或接口的静态变量,或者对该静态变量赋值

​ 调用类的静态方法

​ 反射(比如: Class. forName (“com. atguigu.Test”))

​ 初始化一个类的子类

Java虚拟机启动时被标明为启动类的类

JDK 7开始提供的动态语言支持:java.lang.invoke. MethodHandle实例的解析结果REF getstatic, REF putstatic, REF invokestatic句柄对应的类没有初始化,则初始化

除了以上七种情况,其他使用Java类的方式都被看作是对类的被动使用,都不会导致类的初始化。


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

相关文章

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;花了很多时间阅读一些指标相关的文章、书籍&#…

dau、mau、pcu、dnu、wau、acu、uv的意思是什么?怎么分析?

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

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

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

Spring AOP原理和用法

AOP也就是面向切面编程&#xff0c;首先AOP不等于Spring aop&#xff0c;前者是编程所要实现的目标&#xff0c;后者仅仅是AOP的实现方式之一&#xff0c;作为一种动态注入的实现方式&#xff0c;还有一些别的例如AspectJ&#xff08;静态注入&#xff09;等 一. Spring aop使…

一篇文章详细解读Spring的AOP原理过程(Spring面向切面详解)

目录 概述 使用登录例子说明 AOP AOP&#xff08;底层原理&#xff0c;了解&#xff09; 有两种情况动态代理 第一种 有接口情况&#xff0c;使用 JDK动态代理 第二种 没有接口情况&#xff0c;使用 CGLIB 动态代理 回顾JDK静态代理 ①明星唱歌&#xff0c;经纪人替他签…

IOC/DI、AOP相关原理

文章目录 IOC/DI为什么IOC就降低了耦合性 AOP原理使用AOP的名词们概念性名词JoinPoint/TargetIntroductionProxyWeaving 在代码中有对应注解的名词AspectPointcut&#xff08;在哪儿切&#xff09;Advice&#xff08;什么时候切&#xff09; 示例代码 IOC/DI IOC全称是Inversio…