类加载核心是“首次主动使用”触发,遵循双亲委派(Bootstrap为终点,Ext/AppClassLoader执行委派),初始化仅一次且按静态顺序、父类优先、准备与初始化分离。

直接抓住三个锚点:加载时机、双亲委派链、初始化顺序。不背流程,重在理解“谁在什么时候做了什么”。
搞清类加载的触发点
不是一运行就全加载,而是“首次主动使用”才触发。常见触发场景有:
- new 一个对象(哪怕只 new 一次,后续再 new 不再触发类加载)
- 调用类的静态方法(如
Math.max()) - 访问非 final static 字段(
System.out触发System类加载) - 反射调用(
Class.forName("X"),注意带参版本默认初始化) - 子类初始化时,父类一定先初始化(哪怕没显式写
extends,所有类都隐式继承Object)
注意:访问 public static final int VAL = 100 这种编译期常量,不会触发类加载——值直接内联进调用方字节码了。
吃透双亲委派怎么走、为什么这么走
核心不是“层层上报”,而是“先让长辈试试,长辈不行我才动手”。关键细节:
- 启动类加载器(Bootstrap)不参与委派动作,它是终点:没有父加载器,也不继承
ClassLoader,String.class.getClassLoader()返回null - 扩展类加载器(ExtClassLoader)和应用类加载器(AppClassLoader)才是 Java 层真正执行委派逻辑的主体
- 委派失败后,子加载器才调用
findClass()自己加载——这才是你写自定义加载器要覆盖的方法 - 沙箱保护机制会拦截
java.*包下的类加载请求,防止恶意替换核心类
典型陷阱:自己写个 java.lang.String 放进 classpath,编译能过,运行必报错或被忽略——因为 Bootstrap 已加载真版,且委派机制保证它永远优先命中。
死磕初始化阶段的执行逻辑
初始化( 方法)只发生一次,但顺序极容易出错。重点记清三层次:
- 静态内容按源码顺序执行:静态变量赋值与静态块混合时,谁在上面谁先跑
-
父类优先于子类:先完成父类的
,再执行子类的;哪怕子类静态块里调用了父类静态方法,也会先触发父类初始化 -
准备 vs 初始化要分清:“准备”阶段只给静态变量设默认值(0、null、false),真正赋值(如
static int x = 5;)发生在“初始化”阶段
例子:static A a = new A(); static int x = 10; —— 创建 A 实例时若访问了 x,此时 x 还是 0(未到初始化阶段),这就是“半初始化陷阱”。
动手验证比死记更有效
写几行代码就能看清全过程:
- 打印各类加载器:
System.out.println(Foo.class.getClassLoader());看归属 - 加静态块+日志,在不同场景(main 中访问、反射、子类引用)下观察输出次数和顺序
- 用
javap -c查看编译后的和方法,确认语句合并逻辑 - 故意把
java.util.List拷一份改名放本地,观察是否能被加载——验证沙箱机制
不复杂但容易忽略
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











