面试知识库

类加载过程 → 双亲委派 → 打破双亲委派 追问链#

追问路径#

涉及知识点#

核心串联逻辑#

  1. 生命周期:加载→验证→准备→解析→初始化→使用→卸载,其中准备只赋零值、初始化才执行<clinit>
  2. 初始化时机:只有6种主动引用触发,被动引用(子类用父类静态字段、SuperClass[]数组、final编译期常量)不触发
  3. 双亲委派三层:Bootstrap(核心类)→Ext/Platform→App(classpath)→自定义;向上委派保证核心类不可被覆盖
  4. 类相等条件:全限定名 + ClassLoader实例都相同才是同一类——这是Tomcat类隔离和热部署的底层依据
  5. 打破方式:SPI靠线程上下文类加载器反向委派、Tomcat的WebappClassLoader重写loadClass优先自己加载
  6. 代码示例
    // 自定义类加载器打破双亲委派:重写loadClass而非findClass
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class<?> c = findLoadedClass(name);
            if (c == null && !name.startsWith("java.")) {  // 核心类仍走双亲,其余自己加载
                try { c = findClass(name); } catch (ClassNotFoundException ignore) {}
            }
            if (c == null) c = super.loadClass(name, false);
            return c;
        }
    }
    java

面试回答串联#

30秒速答#

“类加载经历加载、验证、准备、解析、初始化五个阶段,准备阶段只给静态变量赋零值、初始化才赋真实值。双亲委派是加载请求逐层向上委派,保证核心类不被篡改、类型一致。判断两个类相等要全限定名和ClassLoader都相同。JDBC的SPI、Tomcat类隔离都打破了双亲委派,SPI靠线程上下文类加载器反向加载实现类。“

2分钟展开答#

“类从字节流到可用经历五个阶段:加载读取字节流生成Class对象,验证保证字节码安全,准备给静态变量分配内存并赋零值(注意此时int是0不是赋的值),解析把符号引用转直接引用,初始化才执行给静态变量赋真实值和跑静态块。初始化是懒触发的,只有new、读写静态字段、调静态方法、反射、初始化子类、启动类这6种主动引用会触发,而用父类静态字段、定义数组、引用final编译期常量都是被动引用不触发。双亲委派模型是加载请求先逐层委派给父加载器,父加载不了才自己加载,层次是Bootstrap加载核心类→Platform/Ext→App加载classpath→自定义。它解决两个问题:一是安全,你自己写个java.lang.String也会被Bootstrap的版本顶替;二是避免重复加载保证类型一致。判断两个类是否同一个类要看全限定名和加载它的ClassLoader是否都相同,这正是Tomcat给每个webapp配独立WebappClassLoader做应用隔离、支持同名类不同版本的底层原理——它故意重写loadClass优先自己加载打破了双亲委派。另一个经典打破场景是JDBC的SPI:DriverManager由Bootstrap加载但要加载厂商的驱动实现类(在classpath上),只能通过线程上下文类加载器拿到App加载器反向去加载。字节码层面class文件包含常量池、字段表、方法表,运行时JIT的C1/C2会把热点代码编译成机器码,配合逃逸分析做栈上分配和锁消除。“

相关追问链#