classpath到底是干嘛的
一句话总纲classpath 是类名 → 字节码文件的寻址方案。它把逻辑世界(包名、类名)映射到物理世界(磁盘上的路径),是 JVM 运行期寻址的第一站。它和 CPU 的地址翻译、操作系统的 PATH、DNS 是同一类东西——都是名字 → 位置的解析器。它不是被设计成多层的;是多层,恰好在你平时看不见的地方,classpath 站在其中一层。代码从文字到运行要过的每一层写下的代码到真正运行,是一个管道,每一层回答一个问题:层回答的问题载体/机制你平时在哪见到它① 格式层代码长什么样?.java源码 → javac →.class字节码javac、编译报错② 存储层字节码放哪?文件系统、jar、.m2仓库你.m2里那 7 个 jackson 版本共存③ 定位层给一个类名,去哪找?classpath 列表 顺序dependency:tree、IDEA External Libraries④ 加载层以什么身份读进来?ClassLoader、双亲委派ClassNotFoundException、类隔离、Tomcat 多 webapp⑤ 链接层这份字节码靠谱吗、引用能对上吗?验证/准备/解析NoSuchMethodError、VerifyError⑥ 初始化层类第一次真正被用起来static 块、类初始化ExceptionInInitializerError⑦ 执行层怎么高效地跑?解释器、JIT、GC性能调优、JIT 日志为什么一定会有这个分界:数据可以并存,实体必须唯一核心矛盾是:字节码是数据,Class 对象是运行时实体。数据(磁盘上的.class)——多份拷贝共存毫无问题,这就是存储层,所以你.m2里 7 个版本和平共处实体(JVM 内存里的 Class)——同一时刻,同一个名字在同一 classloader 里只能有一个定义,这就是加载层两层之间天然隔着一条鸿沟,必须有一个选择器把多份数据变成唯一实体:存储层: 2.13.4 的 ObjectMapper.class ←—— 并存,没问题 存储层: 2.18.3 的 ObjectMapper.class ←—— 并存,没问题 ↓ 定位层(classpath):按顺序选一份 加载层: 内存里唯一的 ObjectMapper Class ←—— 一名一义classpath 就是那个选择器。不是 classpath 被分成了存储层和加载层,而是 classpath 站在存储层和加载层之间,各管各的事。你前面几轮的困惑(树 vs 列表、目录 vs jar、版本共存)——全是这条鸿沟在不同侧面的投影。大局观检验:同一张错误,能告诉你错在哪层这套分层不是理论摆设,是排错的坐标系——报错类型直接对应层:报错哪层出了问题翻译ClassNotFoundException③ 定位层类名没映射到任何字节码(连找都找不到)NoClassDefFoundError④/⑤ 加载/链接找到过,但后来又没了/链接失败(编译时在,运行时不在了)NoSuchMethodError⑤ 链接层类在,但版本不对,方法签名对不上(经典依赖冲突)VerifyError⑤ 验证字节码本身不合规ExceptionInInitializerError⑥ 初始化类加载成功了,static 块抛了异常ClassCastException④ 加载层两个 classloader 各加载了一份同名类,身份不同看到没:你之前所有关于 classpath 的讨论,都发生在第 ②③④ 层;而版本共存问题之所以存在,是因为 ② 允许并存、④ 禁止并存、③ 负责在中间裁决。最终的大局观所有这七层,都是为同一个目标服务的:把可以无限并存的静态代码安全、可控地变成运行时唯一、可靠的实体。层与层的边界,就是数据与实体的分界;classpath 之所以重要又难懂,是因为它恰好站在这个分界线上,一半在构建期、一半在运行期。把这七层刻在脑子里,以后任何 Java 报错、任何依赖问题、任何为什么要在 pom 里配这个的疑问,都可以先问一句:这是哪一层的问题?答案往往就自己浮出来了。