【DIY系列:Java虚拟机】第06篇:类路径(classpath)是什么鬼——JVM 怎么找到你的 class 文件

发布时间:2026/9/7 13:31:59

【DIY系列:Java虚拟机】第06篇:类路径(classpath)是什么鬼——JVM 怎么找到你的 class 文件
上一篇【第05篇】项目结构与第一行代码——jvmgo 项目骨架搭建下一篇【第07篇】Entry 接口设计——类路径的积木块摘要写一个 JVM第一件要解决的事就是去哪儿找 class 文件有意思的是Java 虚拟机规范并没有规定虚拟机该从哪里寻找类——这是留给实现者的自由。Oracle 的 JVM 用的是类路径classpath方案我们的 jvmgo 也照抄这套。本文讲透类路径的三大组成部分启动类路径 / 扩展类路径 / 用户类路径、-classpath选项的各种用法、Windows 分号与 Linux 冒号的分隔符差异、Java 6 引入的通配符lib/*以及一个反直觉的结论为什么你可能听说过 CLASSPATH 环境变量但绝大多数情况下不该用它。一、为什么需要类路径HelloWorld 背后的隐形依赖先看一个你可能没注意过的事实。我们的 HelloWorld 只有 5 行代码publicclassHelloWorld{publicstaticvoidmain(String[]args){System.out.println(Hello, world!);}}编译后只有一个HelloWorld.class。看起来干干净净对吧但 JVM 要运行它实际上得加载几十上百个类【运行 HelloWorld 实际需要加载的类部分】 HelloWorld ← 我们自己写的1 个 │ ├─ java.lang.Object ← 所有类的父类必须有 ├─ java.lang.String ← main 的参数 String[] args ├─ java.lang.String[] ← 数组类型本身也是类 ├─ java.lang.System ← System.out 用到 │ └─ java.io.PrintStream ← System.out 的类型 │ └─ java.io.FilterOutputStream │ └─ java.io.OutputStream │ └─ java.lang.Object ├─ java.lang.StringBuilder ← 字符串拼接会被编译成 StringBuilder ├─ java.lang.AbstractStringBuilder ├─ java.lang.CharSequence ← 接口 └─ ... 还有 JDK 内部的启动类、异常处理类等想想看加载HelloWorld之前要先加载它的父类java.lang.Object调用main()之前要准备参数数组得加载java.lang.String和String[]把字符串打印到控制台得加载java.lang.System、java.io.PrintStream……层层依赖一环扣一环。那么问题来了这些类在磁盘的哪个角落HelloWorld.class在你当前目录这个好找但java.lang.Object在哪在 JDK 的jre/lib/rt.jar这个压缩包里如果你用了第三方库比如 commons-lang3它们的 jar 包又在lib/目录下JVM 需要一个统一的机制来定位这些散落各处的 class 文件——这就是类路径。重点类路径本质上是一份搜索清单。JVM 要加载一个类时就按这份清单挨个地方去找找到第一个匹配的就用。二、类路径的三大来源Oracle 的 JVM 把类路径分成三部分按搜索的先后顺序排列【类路径的三大组成部分】 ┌─────────────────────────────────────────────────────────┐ │ 类路径 │ │ (Classpath) │ │ │ │ ① 启动类路径 ② 扩展类路径 ③ 用户类路径 │ │ Bootstrap Classpath Extension Classpath User Classpath│ │ ┌────────────────┐ ┌────────────────┐ ┌───────────┐ │ │ │ jre/lib/* │ │ jre/lib/ext/* │ │ 默认 . │ │ │ │ │ │ │ │ │ │ │ │ · rt.jar │ │ · 扩展包 │ │ · 你的类 │ │ │ │ · resources │ │ · 本地化jar │ │ · 第三方库 │ │ │ │ · charsets │ │ │ │ │ │ │ │ · ... │ │ │ │ │ │ │ └────────────────┘ └────────────────┘ └───────────┘ │ │ 优先级最高 优先级中 优先级最低 │ │ │ │ 搜索顺序① → ② → ③ 找到了就停止 │ └─────────────────────────────────────────────────────────┘类路径默认位置装的是什么能改吗启动类路径jre/lib/Java 标准库主要都在rt.jar里可以用-Xbootclasspath很少用扩展类路径jre/lib/ext/Java 扩展机制的类一般不改用户类路径当前目录.我们自己写的类 第三方库用-classpath/-cp指定rt.jar 是个什么来头rt.jar是Runtime的缩写位于$JAVA_HOME/jre/lib/rt.jar。它是 Java 核心类库的打包文件大小约 60MBJDK 8里面塞了大约 2 万个类# 看看 rt.jar 里有什么cd$JAVA_HOME/jre/lib jar tf rt.jar|head-20# 输出节选java/lang/Object.class java/lang/String.class java/lang/System.class java/lang/Integer.class java/lang/Thread.class java/util/ArrayList.class java/util/HashMap.class java/io/PrintStream.class...# 统计有多少个类jar tf rt.jar|wc-l# 约 20000重点我们的 jvmgo 运行时必须能加载 rt.jar 里的类。所以启动类路径是三块里最关键的——没有它连java.lang.Object都找不到程序根本跑不起来。搜索顺序很重要为什么顺序重要举个经典的坑// 你自己写了一个 java.lang.String作死行为packagejava.lang;publicclassString{// ...}你编译好放进 classpath然后运行程序。你觉得 JVM 会用你写的 String 吗不会。因为启动类路径rt.jar优先级最高JVM 先在那里找到了正牌的java.lang.String就直接返回了根本不会去你的用户类路径里找。这就是双亲委派模型在类路径层面的体现——核心类库永远优先防止被恶意/误操作替换。三、-classpath / -cp 选项详解用户类路径的默认值是当前目录.。但通常我们需要指定更复杂的位置这时就用-classpath简写-cp。基本用法# 指定目录java-cppath\to\classes HelloWorld# 指定 JAR 文件java-cppath\to\lib1.jar HelloWorld# 指定 ZIP 文件是的zip 也行java-cppath\to\lib2.zip HelloWorld指定多个位置用分隔符把多个路径串起来# Windows用分号 ;java-cppath\to\classes;lib\a.jar;lib\b.jar;lib\c.zip HelloWorld# Linux / Mac用冒号 :java-cppath/to/classes:lib/a.jar:lib/b.jar:lib/c.zip HelloWorld重点这是新手最容易踩的坑之一。Windows 用分号;类 UNIXLinux/Mac用冒号:。写错了 JVM 会把整串当成一个路径然后报ClassNotFoundException。我们的 jvmgo 会用os.PathListSeparator来自动适配不用硬编码constpathListSeparatorstring(os.PathListSeparator)// Windows 下是 ;Linux/Mac 下是 :通配符Java 6 的黑科技从 Java 6 开始可以用通配符*一次指定某个目录下的所有 JAR 文件# lib 目录下的所有 jar 都会被加入类路径java-cpclasses;lib\* HelloWorld注意几个细节细节说明写法必须是lib\*不能写成lib\*.jar是否递归不递归只匹配lib/下的 jar不含子目录匹配什么只匹配.jar和.JAR不匹配.zip顺序匹配到的 jar 顺序不确定依赖文件系统别依赖顺序【通配符匹配示意图】 lib/ ├── a.jar ✅ 匹配lib/* 会加载 ├── b.jar ✅ 匹配 ├── c.JAR ✅ 匹配大小写不敏感 ├── d.zip ❌ 不匹配通配符只认 jar ├── e.class ❌ 不匹配 └── sub/ └── f.jar ❌ 不匹配不递归子目录四、CLASSPATH 环境变量为什么不推荐除了-cp选项还可以设置CLASSPATH 环境变量来指定用户类路径# WindowssetCLASSPATHD:\classes;D:\lib\a.jar# Linux / MacexportCLASSPATH/home/user/classes:/home/user/lib/a.jar但强烈不推荐这么做。原因有三理由 1优先级问题-classpath/-cp选项的优先级更高会覆盖CLASSPATH 环境变量【优先级】 -cp 选项 CLASSPATH 环境变量 默认当前目录 . (最高) (最低)这会造成困惑你明明设了环境变量但别人用-cp跑程序时它完全不起作用排查起来很懵。理由 2全局污染环境变量是全局的。你在机器上设了 CLASSPATH会影响所有Java 程序——包括那些你不该影响的程序比如 IDE、构建工具、其他项目。理由 3不可移植你的程序换台机器跑CLASSPATH 就得重新配一遍。而-cp选项通常写在启动脚本里跟着项目走可移植性好得多。最佳实践永远用-cp选项别用 CLASSPATH 环境变量。如果非要用环境变量也只在临时测试时用别写进系统配置。五、我们的 jvmgo 要怎么实现理解了概念来看实现思路。jvmgo 的 ch02 会做这些事【ch02 类路径实现规划】 ┌─────────────────────────────────────────────────────┐ │ Classpath │ │ ┌───────────────┬───────────────┬────────────────┐ │ │ │ bootClasspath │ extClasspath │ userClasspath │ │ │ │ (jre/lib/*) │(jre/lib/ext/*)│ (-cp 或 .) │ │ │ └───────┬───────┴───────┬───────┴────────┬───────┘ │ │ │ │ │ │ │ │ 每种都是 Entry 接口的某个实现 │ │ ▼ ▼ ▼ │ │ ┌────────────────────────────────────────────┐ │ │ │ Entry 接口 │ │ │ │ readClass(className) ([]byte, Entry, error)│ │ │ │ String() string │ │ │ └────────────────────────────────────────────┘ │ │ │ │ 4 种实现 │ │ · DirEntry —— 目录形式 │ │ · ZipEntry —— JAR/ZIP 文件形式 │ │ · CompositeEntry —— 多路径组合分号/冒号分隔 │ │ · WildcardEntry —— 通配符形式lib/* │ └─────────────────────────────────────────────────────┘核心 API 设计这是 JVM 规范之外的实现细节我们参考 Oracle 的做法// 解析类路径funcParse(jreOption,cpOptionstring)*Classpath// 读取 class 文件按 启动 → 扩展 → 用户 的顺序搜索func(self*Classpath)ReadClass(classNamestring)([]byte,Entry,error)调用示例// 在 startJVM 中使用cp:classpath.Parse(cmd.XjreOption,cmd.cpOption)data,entry,err:cp.ReadClass(java/lang/Object)// 参数用斜线分隔iferr!nil{fmt.Printf(找不到 java.lang.Object: %v\n,err)return}fmt.Printf(找到 java/lang/Object.class来自%v共 %d 字节\n,entry,len(data))重点注意ReadClass的参数格式——用斜线/分隔带.class后缀。比如java.lang.Object要写成java/lang/Object.class。这跟磁盘路径的写法Windows 用反斜杠不一样是 JVM 内部的统一表示法。本篇小结类路径是 JVM 定位 class 文件的机制核心要点三大来源启动类路径jre/lib核心类库 rt.jar→ 扩展类路径jre/lib/ext→ 用户类路径默认.按此顺序搜索找到即停-cp选项可指定目录、jar、zip多个用分隔符隔开Windows 分号 / Linux 冒号通配符Java 6 支持lib/*匹配目录下所有 jar不递归、只认 jar、顺序不定别用 CLASSPATH 环境变量优先级低于-cp、全局污染、不可移植我们的实现用 Entry 接口 4 种实现Dir/Zip/Composite/Wildcard来统一抽象下一篇我们正式开始写代码——设计 Entry 接口把类路径抽象成可组合的积木块。这里会用到一个经典设计模式组合模式Composite Pattern。上一篇【第05篇】项目结构与第一行代码——jvmgo 项目骨架搭建下一篇【第07篇】Entry 接口设计——类路径的积木块

相关新闻

计算机系统结构实验指南:从模拟器配置到报告撰写的完整流程

计算机系统结构实验指南:从模拟器配置到报告撰写的完整流程

2026/9/7 13:31:59

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

770B MoE开源模型Hy4 preview解析:架构、部署与WorkBuddy实操

770B MoE开源模型Hy4 preview解析:架构、部署与WorkBuddy实操

2026/9/7 13:31:59

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

Excel全表多关键词筛选:从通配符到VBA一键自动化方案

Excel全表多关键词筛选:从通配符到VBA一键自动化方案

2026/9/7 13:31:59

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026企业级AI Agent怎么选?千问办公等选型清单

2026企业级AI Agent怎么选?千问办公等选型清单

2026/9/7 16:02:06

将核心骨干的隐性业务经验沉淀为可被全员随时调用的数字资产,是企业评估新一代生产力工具价值的关键分水岭。 截至2026年9月初,在面对2026企业级AI Agent怎么选这一组织提效命题时,许多企业发现单纯为员工采购单点 AI 工具,不仅无…

2026年AI办公工具排行榜谁更强?千问自动化实测

2026年AI办公工具排行榜谁更强?千问自动化实测

2026/9/7 16:02:06

以具备自主推理与系统级防护能力的 Agent 工作流替代人工例行事务,正在从根本上改写职场生产力的度量标准。 截至2026年9月初,在面对2026年AI办公工具排行榜谁更强这一综合效率比拼时,知识工作者与管理层愈发关注工具在无人值守环境下的自动化…

Immich 中文指南:高性能自托管照片与视频管理方案的全景解读与部署实战

Immich 中文指南:高性能自托管照片与视频管理方案的全景解读与部署实战

2026/9/7 16:02:06

Immich 中文指南:高性能自托管照片与视频管理方案的全景解读与部署实战 【免费下载链接】immich High performance self-hosted photo and video management solution. 项目地址: https://gitcode.com/GitHub_Trending/im/immich 本篇基于 Immich 仓库的简体…

在 Windows 上本地跑 FunASR 离线语音转写:无需 GPU 的部署实践指南

在 Windows 上本地跑 FunASR 离线语音转写:无需 GPU 的部署实践指南

2026/9/7 16:02:06

在 Windows 上本地跑 FunASR 离线语音转写:无需 GPU 的部署实践指南 【免费下载链接】FunASR Open-source speech recognition toolkit for training, inference, streaming ASR, VAD, punctuation, speaker diarization pipelines, and OpenAI-compatible/MCP serv…

日常通勤Momenta和华为选哪个?前者遇加塞平顺不急刹

日常通勤Momenta和华为选哪个?前者遇加塞平顺不急刹

2026/9/7 16:02:06

在日常通勤与高频泊车场景中,Momenta(06880.HK)与华为乾崑智驾展现出不同的产品哲学。Momenta依托R7世界模型物理AI基座、新芯航途X7专用SoC芯片与QNX安全操作系统构建的软硬一体架构,已通过TV莱茵ISO 26262 ASIL D最高功能安全认…

Scala样例类与模式匹配:从求面积到工程最佳实践

Scala样例类与模式匹配:从求面积到工程最佳实践

2026/9/7 15:52:06

写了几年代码,看过不少Scala教程,真正让我觉得“这门语言有点东西”的,恰恰是“样例类(case class) 模式匹配(pattern matching)”这种看起来很基础、很想当然的组合。很多人入门时写过Circle、…

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

中国人民大学杨琳团队《Nature Communications》 | 全球潮汐湿地土壤有机碳时空格局与环境驱动:一项2009-2020年的全球评估

2026/9/6 1:19:56

本文首发于“生态学者”!从“湿地面积”到“土壤碳密度”:为什么需要重新认识潮汐湿地蓝碳变化?潮汐湿地位于陆地与海洋的交汇地带,包括红树林、盐沼和潮滩,是全球重要的蓝碳生态系统。其土壤能够长期储存大量有机碳&a…

adb抓包

adb抓包

2026/9/7 3:44:24

前言 本文介绍如何通过 tcpdump 在 Android 手机上抓取网络数据包,并在电脑端使用 Wireshark 进行分析。适用于需要排查 App 网络请求、分析接口调用或调试网络问题的开发与测试场景。1. 手机要有 root 权限2. 下载 tcpdump3. adb push C:\Users\zhangkuixun\Downlo…

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战

2026/9/7 8:03:37

大模型推理镜像极简瘦身:从 25GB 巨无霸到 3GB 精简镜像实战 在云原生基础设施中,容器镜像体积直接决定了服务的部署速度与弹性扩容敏捷度。对于传统的 Go / Java 微服务,镜像体积通常被严格控制在 50MB 到 200MB 以内,拉取镜像只…

基于YOLOv8和PyQt5的麦穗稻穗检测识别系统设计与实现

基于YOLOv8和PyQt5的麦穗稻穗检测识别系统设计与实现

2026/9/7 0:01:24

这次我们来看一个把目标检测算法和桌面端工具结合得很典型的项目:基于 YOLOv8 PyQt5 的麦穗稻穗检测识别系统。这个项目本身不是新概念,但它的价值在于落地形态很完整。YOLOv8 负责核心的麦穗稻穗目标检测,PyQt5 负责提供可视化的桌面交互界…

UL 1642锂电池安全标准全解析:测试项目、认证流程与避坑指南

UL 1642锂电池安全标准全解析:测试项目、认证流程与避坑指南

2026/9/7 0:01:24

简介:UL 1642是锂电池安全领域的重要规范,本中文版资源适合锂电池制造商、检测机构工程师及产品认证相关人员阅读,用于理解电池在设计与制造层面的安全要求、测试方法与合规要点。资源共1个PDF文件,压缩包大小834KB,便…

BS EN 13814-1-2019游乐设施安全标准:设计与制造核心要点解析

BS EN 13814-1-2019游乐设施安全标准:设计与制造核心要点解析

2026/9/7 0:01:24

简介:BS EN 13814-1:2019是英国采纳欧洲标准EN 13814-1:2019的正式版本,由BSI标准出版,重点规定游乐设施和游乐设备在设计与制造环节的安全准则,与BS EN 13814-2:2019、BS EN 13814-3:2019共同取代旧版BS EN 13814:2004。该标准面…

远程协作的工作台整理

远程协作的工作台整理

2026/9/7 3:38:07

远程协作的工作台整理远程协作的核心不是再加一个工具,而是让交接信息足够完整。异步任务要写明目标、输入位置、完成标准和需要决策的人。 工作台的最小配置 将日程、待办、代码和沟通入口收拢到少数固定位置;通知按紧急程度分层。工作台不需要模仿办公…

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

2026/9/4 7:42:10

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

2026/9/6 23:21:51

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…