javac负责将Java源码编译为JVM可执行的字节码,仅做静态检查;java命令启动JVM执行字节码,经历加载、验证、准备、解析、初始化五阶段。

javac 和 java 看似都是命令行工具,但它们干的是完全不同的事:一个在“翻译”,一个在“演戏”。javac 把人写的 Java 代码变成 JVM 能看懂的字节码;java 则是启动 JVM,让字节码真正活起来——加载、校验、准备、初始化、执行。
javac 是纯前端翻译器,不碰运行环境
javac 只做静态工作,不依赖 JVM 运行时,也不访问类路径里的实际类文件(除非你用 -classpath 指定源依赖)。它只检查语法、符号引用是否存在、类型是否匹配,比如 public 类名必须和文件名一致、方法调用参数个数对不对。它不验证方法体里会不会空指针、数组会不会越界——这些留到运行时。
- 词法分析:把
int a = 10 + b;拆成int、a、=、10、+、b等 token - 语法分析:构建抽象语法树(AST),确认结构合法(如 if 后面有没有花括号)
- 语义分析:检查变量是否声明、方法是否重载合理、final 常量能否编译期折叠
- 字节码生成:输出 .class 文件,里面是操作码(如
iload_0、invokestatic)和常量池,不含任何机器指令
java 命令背后是整套 JVM 生命周期
执行 java HelloWorld 不是简单“跑个文件”,而是触发 JVM 的五阶段类加载流程:加载 → 验证 → 准备 → 解析 → 初始化。只有完成这些,main 方法才真正开始执行。
- 加载:从 CLASSPATH 或模块路径读取
HelloWorld.class字节码,生成 Class 对象 - 验证:确保字节码没被篡改、不会破坏 JVM 安全(如跳转指令不越界、类型不冲突)
- 准备:为 static 变量分配内存并设默认值(如 int → 0,引用 → null)
- 解析:把符号引用(如
"java/lang/System.out")转成直接内存地址 - 初始化:执行
static { }块和 static 字段赋值语句(若出异常,会抛NoClassDefFoundError)
关键差异藏在输出物与执行时机里
javac 输出的是平台无关的二进制字节码,内容稳定、可离线查看(用 javap -c);java 启动的是动态运行环境,同一份 .class 在不同 JVM 上可能走不同路径:解释执行、JIT 编译成本地机器码(C1/C2)、甚至 AOT 预编译。而编译时确定的事(如常量折叠、泛型擦除)和运行时才决定的事(如多态分派、反射调用、类加载顺序)泾渭分明。
立即学习“Java免费学习笔记(深入)”;
-
static final int x = 2 * 3;→ javac 直接写入 6 到字节码,不保留乘法运算 -
List list = new ArrayList();→ javac 擦除为List list = new ArrayList();,运行时才确定实际类型 -
obj.toString()→ 编译时只记下符号引用,运行时查虚方法表找真正实现
错误提示能立刻暴露两者分工
报错位置就是原理分水岭。javac 报错一定是源码层面问题:缺少分号、类名不匹配、找不到 import 的类名;java 报错则发生在运行时:类找不到(NoClassDefFoundError)、方法不存在(NoSuchMethodError)、初始化失败(ExceptionInInitializerError)、或逻辑异常(NullPointerException)。前者你改完就能重编译,后者得查环境、类路径、静态块逻辑甚至 JVM 参数。



















