Java类加载翻车现场:双亲委派不是你想的那样

avatar
小常在创业IP属地:上海
02026-02-13:22:14:53字数 3984阅读 2

上周线上事故,运维在群里吼:“生产环境ClassCastException!”
我查了3小时日志,最后发现:类加载器在背刺我
不是因为代码写烂了,是我根本没搞懂双亲委派
别被“类加载机制”这四个字吓到——
它就是Java的“身份证系统”,搞不定它,系统随时给你整活。


一、双亲委派是啥?一句话讲透

“类加载器像公司层级:小员工先找组长,组长不行才找经理。”

真实场景
你写了个com.example.MyClass,JVM要加载它:

  1. AppClassLoader(你自己的类加载器)先问:“组长(ExtClassLoader)能加载吗?”
  2. ExtClassLoader(扩展类加载器)问:“经理(BootstrapClassLoader)能加载吗?”
  3. BootstrapClassLoader(启动类加载器)直接查:
    • java.lang.String?有!
    • com.example.MyClass?没!
  4. 然后,AppClassLoader自己加载(因为父级都找不到)。

重点不是“子类加载器找父类”,是“父类先找,找不到才轮到子类”
(别问,问就是我第一次理解错,线上崩了30分钟。)


二、为什么双亲委派这么重要?(血泪教训)

错误场景:自己写类加载器,没遵守双亲委派

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // ❌ 错误!没调用父类的loadClass
        if (name.startsWith("com.example")) {
            return findClass(name);
        }
        return super.loadClass(name, resolve); // 没用!
    }
}

后果

  • 你项目里有com.example.MyClass,同时JDK也有java.lang.MyClass(虽然不存在,但逻辑一样)
  • 你的类加载器直接自己加载,没让父级查
  • 结果:系统报错java.lang.NoClassDefFoundError: com/example/MyClass
    (因为JDK的核心类被你“覆盖”了,系统以为你用了自己的类,但JDK没加载到)

💡 真实翻车
我在项目里加了个自定义类加载器,想热更新配置。
结果一启动,java.lang.SecurityException疯狂弹窗——
因为双亲委派被破坏,JDK的java.security类被我加载了两次


三、双亲委派的真正逻辑(别被文档忽悠)

正确流程(核心代码):

// ClassLoader.java 源码关键部分
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // 1. 先查缓存(避免重复加载)
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            // 2. 委派给父类加载器(这才是双亲委派精髓!)
            if (parent != null) {
                c = parent.loadClass(name, false);
            }
            if (c == null) {
                // 3. 父级找不到,自己加载
                c = findClass(name);
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

关键点:

步骤你写的代码真实逻辑
委派return super.loadClass(name, resolve);必须先调父类的loadClass
自己加载直接findClass只有父级找不到才自己干

正确写法(自定义类加载器):

public class CustomClassLoader extends ClassLoader {
    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        // 1. 先委派给父类(必须!)
        Class<?> clazz = super.loadClass(name, resolve);
        // 2. 如果是自定义包,才做特殊处理(比如热更新)
        if (name.startsWith("com.example")) {
            return findClass(name);
        }
        return clazz;
    }
}

四、什么情况下要打破双亲委派?(别乱破!)

双亲委派是默认规则,不是金科玉律
但只有在极少数场景下才需要打破,比如:

Tomcat(Web容器):

  • 每个Web应用需要独立加载类(避免A应用覆盖B应用的类)
  • 实现:Tomcat的WebAppClassLoader不调用父类的loadClass,直接自己加载应用类
  • 为什么能破:Tomcat是应用级容器,它知道自己的类加载边界。

OSGi(模块化框架):

  • 需要动态加载/卸载模块,类加载器要隔离
  • 实现:每个模块有独立类加载器,打破双亲委派。

别乱破

  • 我上次在工具类里加了个自定义类加载器,结果整个系统都崩了
  • 原因:没搞清边界,破坏了JDK核心类的加载顺序。

五、小白必踩的3个坑(我全踩过)

现象解决方案
1. 自己写类加载器没委派启动报NoClassDefFoundError必须调super.loadClass(别写return findClass
2. 误以为双亲委派是“子类找父类”类重复加载,内存泄漏记住:父类先找,找不到才子类自己干
3. 破坏双亲委派但没隔离java.lang.SecurityException只在需要隔离的场景(Tomcat/OSGi)破,别乱破

💡 血泪教训
我在项目里加了自定义类加载器,想热更新配置。
结果:JDK的java.util.Properties被我加载了两次
一调Properties.load(),就报ClassCastException——
因为JDK的类和我的类被当成两个不同类了


六、终极避坑指南(给新人)

  1. 别自己写类加载器(除非你100%知道为什么)

    • 99%的场景用默认的ClassLoader就行
    • 除非是Web容器、OSGi这种框架级需求
  2. 如果必须写

    • 先调super.loadClass(这是双亲委派的核心)
    • 只在需要隔离的包上做特殊处理(比如com.example.*
  3. 验证双亲委派

    // 在类加载时打印日志
    System.out.println("Loading " + name + " from " + getClass().getName());
    
    • 看日志:核心类(java.lang.*)应该由BootstrapClassLoader加载
    • 自己的类应该由AppClassLoader加载(除非你破了双亲委派)

七、最后说句大实话

双亲委派不是“理论”,是“生存法则”
你没遵守它,JDK会用NoClassDefFoundError让你跪着改代码。
别被“类加载机制”吓到——
它就是Java的“身份证核验系统”,你得按规矩来。

现在就去检查你的类加载器

  1. 找到所有自定义ClassLoader
  2. 确认有没有调super.loadClass
  3. 如果没有,立刻改掉(别等线上崩了才后悔)

(写完这篇,我删了项目里最后1个自定义类加载器——真香。)

记住

“类加载器不是玩具,是Java的命门。
你搞不懂双亲委派,系统随时给你送走。”

总资产 0
暂无其他文章

热门文章

暂无热门文章