Java基础与面试-每日必看(6)

前言

Java不秃,面试不慌!
欢迎来到这片 Java修炼场!这里没有枯燥的教科书,只有每日一更的 硬核知识+幽默吐槽,让你在欢笑中掌握 Java基础、算法、面试套路,摆脱“写代码如写诗、看代码如看天书”的困境。
记住: 代码会背叛你,但知识不会! 坚持积累,总有一天,HR会为你的八股文落泪,面试官会因你的算法沉默。
让我们一起卷出天际,优雅不秃! 🚀


🍔 类加载器的作用:它到底有什么用?

类加载器(ClassLoader)就像餐厅里的 “配菜大厨”,负责把 “生的类文件” 变成 “JVM 能吃的字节码”,然后交给 “JVM 这位食客” 来享用。

你写的 Java 代码,其实就是 .java 源文件,编译之后会变成 .class 文件(字节码)。但是 JVM 自己不会主动去找这些 .class 文件,它需要类加载器来帮忙

🎯 类加载器的主要作用

1️⃣ 按需加载类(Lazy Loading)

Java 不是一次性把所有类都加载进 JVM,而是 “需要的时候才加载”
如果你不使用某个类,类加载器就懒得去加载它,这样可以节省内存!

// 只有当 new User() 执行时,User 类才会被加载
User user = new User();

2️⃣ 隔离类(避免冲突)

类加载器可以 隔离不同的类,让不同的程序使用不同版本的同一个类,而不会互相干扰!
这在 插件系统Tomcat 这样的服务器 里非常重要。

比如,你的应用里有 mysql-connector-5.1.jar,但另一个应用需要 mysql-connector-8.0.jar
如果都放在同一个类路径里,就会冲突。但类加载器可以帮你隔离它们,让各自用自己的版本!

3️⃣ 动态加载(自定义加载逻辑)

有时候,你可能不想从文件系统里加载类,而是从 数据库、网络、加密文件 里加载。这时候,你就可以 自定义类加载器

// 让 MyClassLoader 从网络或者数据库加载类
ClassLoader myLoader = new MyClassLoader();
Class<?> myClass = myLoader.loadClass("com.example.MySecretClass");

4️⃣ 热部署(Hot Deployment)

在 Java Web 服务器(比如 Tomcat、Spring Boot)里,类加载器的另一个重要作用就是 热部署
当你修改代码后,应用可以 无须重启 就能更新类,而不会影响整个 JVM!

Tomcat 是怎么做到的? Tomcat 用了 每个应用一个类加载器,当你修改代码时,它只需要销毁旧的类加载器,再创建一个新的类加载器就行了,JVM 还可以继续运行。

5️⃣ 安全性(防止恶意代码)

类加载器还能 限制哪些类可以被加载,防止恶意代码被执行!

例如,Java 安全管理器(SecurityManager)可以限制哪些类加载器可以访问 java.lang.System,防止黑客恶意篡改系统类。
(当然,Java 11 之后,SecurityManager 被废弃了 😂)


Java 类加载器:它们是如何工作的?

想象一下,你走进一家餐厅,菜单上有各种美味佳肴。你点了一道“类加载”大餐,结果服务员(JVM)告诉你:“不好意思,这道菜需要由不同级别的大厨来准备。”这就是 Java 类加载器的工作方式!让我们来看看这些“类加载大厨”们是如何分工的。

🍳 BootStrapClassLoader(引导类加载器)—— 总厨

“我负责镇店之宝,所有核心配方都归我管!”

BootStrapClassLoader 是 Java 类加载界的“总厨”,它负责加载 Java 运行时的最核心的类,比如 java.lang.Stringjava.util.ArrayList 等。它只关心 %JAVA_HOME%/lib/ 目录下的 rt.jar(或者模块化之后的 java.base 模块)。

不过,BootStrapClassLoader 是一个相当神秘的家伙,它其实是用 C 语言写的,连 Java 世界的其他类加载器都找不到它的踪迹(null)。你要是想直接打印它的名字,那就别想了!

👨‍🍳 ExtClassLoader(扩展类加载器)—— 副厨

“大厨负责最基础的菜品,我来管理特别食材!”

ExtClassLoader 是 BootStrapClassLoader 的“得力副手”,它的主要职责是加载 %JAVA_HOME%/lib/ext/ 目录下的 JAR 包,比如一些额外的安全库或者 XML 解析库。如果你把一个 JAR 包丢进这个目录,ExtClassLoader 就会开心地帮你加载它。

System.out.println(System.getProperty("java.ext.dirs")); // 看看副厨管理的食材库
System.out.println(sun.security.x509.X500Name.class.getClassLoader()); // 试试看它的加载器

⚠️ 注意: 在 Java 9 之后,lib/ext/ 这个目录已经被模块化系统干掉了,副厨的工作也交给了模块系统来处理。

🍽 AppClassLoader(应用类加载器)—— 前厅经理

“我来给顾客提供主菜,所有应用代码都归我管!”

这个类加载器负责加载 classpath 目录下的类文件,比如你写的 com.example.MyClass。它才是你最常打交道的类加载器。

🍕 自定义类加载器(特别定制的私房菜厨师)

有时候,标准菜单不能满足你的需求,你想搞点“私房菜”。这时候,你可以自己写一个类加载器,比如从数据库、网络或者加密的文件中加载类。这种自定义类加载器一般继承 ClassLoader,并重写 findClass 方法。

class MyClassLoader extends ClassLoader {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        System.out.println("我正在神秘地加载类:" + name);
        return super.findClass(name);
    }
}

🔥 类加载器的“家族关系”

你可以把类加载器的关系理解成一个餐厅里的层级架构:

BootStrapClassLoader(总厨)
    ↑
ExtClassLoader(副厨)
    ↑
AppClassLoader(前厅经理)
    ↑
自定义类加载器(私房菜厨师)

🎭 类加载器的双亲委派模型:JVM 里的“请示汇报制”

想象一下,Java 的类加载器是一个 公司里的层级管理制度。当 AppClassLoader 想要加载一个类时,它不会自己直接去做,而是先 请示上级。如果上级能搞定,那就万事大吉;如果上级说 “不归我管”,它才会自己动手。

这个层级关系就像公司里的 请示汇报制,也就是我们常说的 “双亲委派模型”

📜 双亲委派模型的运作流程

JVM 在加载一个类时,采用 “长幼有序” 的方式,由下往上逐级请示:

  1. AppClassLoader(应用类加载器)请示 ExtClassLoader(扩展类加载器)
    • “老大,我要加载 com.example.MyClass,您看看能搞定吗?”
  2. ExtClassLoader(扩展类加载器) 也不会直接处理,而是再 请示 BootstrapClassLoader(引导类加载器)
    • “老板,这个类您认识吗?”
  3. BootstrapClassLoader(引导类加载器) 先尝试加载:
    • 如果它能加载到(比如 java.lang.String 这样的系统核心类),就直接返回!
    • 如果它加载不到,就告诉 ExtClassLoader:
      • “这个不归我管,你们自己看着办。”
  4. ExtClassLoader(扩展类加载器) 接到老板的回复后,自己尝试加载:
    • 如果它能加载,就返回。
    • 如果它也加载不到,就告诉 AppClassLoader:
      • “老弟,这事你自己处理吧!”
  5. AppClassLoader(应用类加载器) 最后才 自己动手 试着加载类:
    • 如果能加载成功,那就 OK。
    • 如果还是加载不到,那就抛出 ClassNotFoundException,宣告彻底失败!

🚀 换成代码理解一下

JVM 里的 ClassLoader 其实早就实现了这个 “请示汇报制”,它的 loadClass 方法是这样写的:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    // 1. 先请示上级(父类加载器)
    Class<?> c = parent.loadClass(name, false);

    // 2. 如果上级加载到了,就直接返回
    if (c != null) {
        return c;
    }

    // 3. 如果上级搞不定,自己尝试加载
    return findClass(name);
}

🎭 为什么要搞双亲委派?

你可能会想:“JVM 为什么非要让类加载器这么折腾?直接自己加载不就完了吗?”
其实,双亲委派有三个重要的好处👇

1️⃣ 避免核心类被篡改(安全性保障)

如果没有双亲委派,你完全可以写一个 java.lang.String 类,放在 classpath 里,让 JVM 优先加载你的假冒类。这样,整个 Java 运行环境就会被你篡改,后果不堪设想!😱

package java.lang; // 伪造一个 java.lang.String 类
public class String {
    public String toString() {
        return "我篡改了 Java 的核心类!";
    }
}

幸好,双亲委派机制java.lang.* 这样的核心类只能由 BootstrapClassLoader 加载,避免了这种篡改的可能!

2️⃣ 避免类的重复加载

如果没有双亲委派,每个类加载器都自己干活,那可能会发生 同一个类在不同的类加载器中被加载多次,导致 类不兼容的问题
比如你在不同的地方加载了 com.example.Utils,但它们其实是 两个不同的类,结果就 GG 了。

ClassLoader loader1 = new MyClassLoader();
ClassLoader loader2 = new MyClassLoader();
Class<?> class1 = loader1.loadClass("com.example.Utils");
Class<?> class2 = loader2.loadClass("com.example.Utils");

System.out.println(class1 == class2); // false,类不一样!

双亲委派机制确保同一个类只会被加载一次,避免了这些混乱情况。

3️⃣ 提高加载效率

如果没有双亲委派,类加载器每次都要自己去找类,加载逻辑会变得复杂、低效。
双亲委派 让最上层的 BootstrapClassLoader 先过滤掉大量核心类,只让底层加载器去加载它们自己特定的类,这样整个加载过程就更高效了!🚀


最后的话

🎉 最后的话:你的支持就是我的动力!

看到这里,恭喜你已经掌握了 Java 类加载器的奥秘,从 BootStrapClassLoader 总厨双亲委派的请示汇报制,希望这篇文章让你对 Java 的类加载机制有了更清晰的认识! 🚀

如果你觉得这篇文章有 帮助,别忘了: ✔ 点赞 👍,你的鼓励是我继续写作的最大动力!
关注 ⭐,第一时间获取更多 Java 干货!
评论 💬,一起交流学习,分享你的见解!

如果你还有什么 疑问 或者 想看的话题,欢迎在评论区留言,我一定会尽力解答!🤗

💡 关注不迷路,点赞更幸福! 让我们一起在 Java 的世界里探索更深的秘密吧!🔥🚀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Starry-Walker

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值