从 `.java` 到程序真正跑起来:编译、类加载、JIT 与面试高频点(附流程图)

这篇文章按“结构流程”把 Java 程序从源码到运行的每一步串起来:
编译期(javac) → 运行期(java + JVM) → 类加载(ClassLoader) → 执行(解释器 + JIT)

0. 先定一个目标:Java 程序“运行”的定义是什么?

很多人说“运行 Java 文件”,但更精确的说法是:

  • JVM 不会直接执行 .java 源文件
  • JVM 执行的是 .class 字节码(或者 jar 包里的 class)

因此,Java 程序跑起来至少经历两段:

  1. 编译期.javajavac.class(字节码)
  2. 运行期java 启动 JVM → 类加载/链接/初始化 → 找到 main → 执行字节码(解释 + JIT)

1. 总体流程图:从源码到 JVM 退出(主线必须背)

.java 源码

javac 编译器

词法/语法/语义分析\n(可含注解处理)

输出 .class 字节码

java 命令启动器

创建 JVM 进程\n初始化运行环境

定位主类 Main Class

ClassLoader 加载主类

Linking(链接)\n验证 / 准备 / 解析

Initialization(初始化)\nstatic 赋值 + static 块

调用 main(String[] args)

执行引擎执行字节码\n解释器 + JIT

main 返回且无非守护线程

JVM 退出


2. .java 文件的结构:入口在哪里?

一个最小可运行 Java 程序的关键点在于 入口方法

publicclassHello{publicstaticvoidmain(String[] args){System.out.println("hi");}}
  1. 程序入口public static void main(String[] args)
  2. java 命令运行的是“主类的全限定名”,不是文件名
    • 有包名:java com.example.Hello
    • 没包名:java Hello
  3. 一个 .java 文件可以写多个类,但 public 顶层类通常要求和文件名一致(Hello.java 对应 public class Hello

3. 编译期:javac 做了什么?为什么是 .class

3.1 你看到的命令

javac Hello.java 

3.2 编译器内部的“结构流程”

典型阶段:

  1. 词法分析:把字符流切分成 token(关键字、标识符、符号…)
  2. 语法分析:把 token 组合为 AST(抽象语法树)
  3. 语义分析:符号解析、类型检查、重载匹配、可达性检查…
  4. (可选)注解处理器:编译期生成代码或校验(如 Lombok、部分框架)
  5. 生成字节码:输出 .class

3.3 产物:一个 .java 可能生成多个 .class

  • 顶层类一个 .class
  • 内部类/匿名类常会额外生成多个 .class(如 Outer$Inner.class
为什么 Java 是“跨平台”?
因为输出的是统一的字节码,由不同平台的 JVM 实现去执行/编译成本地机器码。

4. 运行期:java 命令如何启动 JVM?

4.1 命令

java com.example.Hello 

这里 com.example.Hello主类的全限定名

4.2 进程层面发生了什么

  1. 操作系统启动一个 Java 进程(java 可执行程序)
  2. java 启动并初始化 JVM
  3. JVM 准备运行环境:类加载器体系、内存结构、线程等
  4. JVM 根据 classpath/modulepath 定位主类,开始类加载流程
  5. 类初始化完成后,调用 main

5. 核心重点:类是怎么被“找到并加载”的?(ClassLoader + 三阶段)

这是面试最容易深挖的部分:加载、链接、初始化
你只要把这三件事说清楚,回答就很稳。

5.1 类加载三大阶段(重点)

首次主动使用该类

Loading(加载)

Linking(链接)

Verification(验证)\n校验字节码合法/安全

Preparation(准备)\n为 static 字段分配内存并赋默认值

Resolution(解析)\n符号引用 -> 直接引用

Initialization(初始化)

static 显式赋值

static 块(static {})

类可用:new / 调静态 / 访问静态...

5.2 每一步重点解释

Loading(加载)
  • 通过 类加载器(ClassLoader) 从 classpath / jar / modulepath 等位置找到 .class
  • 把字节码读进 JVM,形成 JVM 内部的类结构
Linking(链接)
  • 验证:确保字节码合法、安全(避免恶意字节码破坏 JVM)
  • 准备:为 static 字段分配内存,并赋“默认值”
  • 解析:把常量池里的“符号引用”解析为可直接定位的“直接引用”
“准备阶段 static 就赋初值了吗?”
准备阶段是默认值,真正的显式赋值发生在初始化阶段(少数编译期常量除外)。
Initialization(初始化)
  • 执行:
    • static 字段的显式赋值(如 static int x = 10;
    • static { ... } 静态代码块
  • 每个类只会初始化一次(通常在首次“主动使用”触发)

6. 什么时候会触发“类初始化”?

关键词:首次主动使用(active use)

6.1 典型触发点

  • new 一个类:new A()
  • 调用静态方法:A.foo()
  • 访问/设置静态非 final 字段:A.x
  • 反射触发:Class.forName("A")
  • 初始化子类时,先初始化父类

6.2 不触发或容易误判的情况

  • 引用 static final编译期常量(可能被内联,不触发初始化)
  • 仅创建数组引用:A[] arr = new A[10];(通常不触发 A 初始化)
  • 仅拿到 A.class(通常不算主动使用)

7. 双亲委派与 ClassLoader(为什么能防止伪造核心类?)

7.1 双亲委派是什么?

加载一个类时:

  1. 先让父加载器尝试加载
  2. 父加载器找不到,子加载器才自己加载

7.2 为什么要这样做?

  • 避免重复加载同名类
  • 安全性:保证 java.lang.String 这类核心类只由更高层加载器加载,避免应用伪造核心类

7.3 什么时候会“打破委派”?

常见于:

  • SPI / 插件化
  • 应用隔离(如 Tomcat、多应用容器)
  • 自定义类加载器实现特殊来源

“原则与好处”->“为什么现实会打破”(框架/容器需求)。


8. .class 到底怎么执行?解释器 vs JIT(“Java 越跑越快”的原因)

JVM 执行字节码主要靠两套机制协同:

  1. 解释执行(Interpreter)
    • 逐条读取字节码并执行
    • 优点:启动快
    • 缺点:长期性能一般
  2. 即时编译(JIT, Just-In-Time)
    • JVM 识别热点方法(调用次数多、循环频繁)
    • 将热点字节码编译为本地机器码,CPU 直接执行
    • 优点:长期性能好
    • 常见现象:程序运行一段时间后更快

一句话记忆:

先解释跑起来,再把热点编译加速。

9. 运行时内存结构:对象在哪里?方法调用在哪里?

9.1 一张图建立直觉

Java 虚拟机 JVM

线程区(每线程独立)

元空间 Metaspace(共享)

类元信息/常量池/方法元数据

执行引擎
解释器 + JIT

Java 栈
栈帧:局部变量/操作数栈/返回地址

堆 Heap(共享)

对象实例(new 出来的)

GC 垃圾回收

PC 程序计数器

本地方法栈

9.2 常用的“标准描述”

  • 堆(Heap):绝大多数对象实例
  • 栈(Java Stack):方法调用形成栈帧,局部变量等
  • 元空间(Metaspace):类元信息、常量池等(JDK8+)
  • PC 计数器:记录当前线程执行到哪条字节码
  • GC:负责回收堆中不再可达对象(注意:资源释放要手动 close)

10. jar 是怎么跑的?(部署场景常见)

你常见的生产运行方式是 jar:

java -jar app.jar 

jar 内部通过 MANIFEST.MF 指定入口:

  • Main-Class: com.example.Main

本质上仍然是:

  • 从 jar 找 .class
  • 类加载 → 初始化 → 调用 main

11. 一个重要的例子:父子类初始化顺序

这段代码解释“初始化触发与顺序”:

classParent{staticint p =initP();static{System.out.println("Parent static block");}staticintinitP(){System.out.println("Parent.p init");return1;}}classChildextendsParent{staticint c =initC();static{System.out.println("Child static block");}staticintinitC(){System.out.println("Child.c init");return2;}}publicclassDemo{publicstaticvoidmain(String[] args){System.out.println(Child.c);}}

预期输出大致顺序为:

  1. Parent 的静态初始化(字段赋值、static block)
  2. Child 的静态初始化
  3. main 打印

重点:

  • “初始化子类前先初始化父类”
  • “静态字段显式赋值和 static block 都在 Initialization 阶段”

12. 要点清单

Q1:Java 文件是怎么运行的?

答:.java 先经 javac 编译为 .class 字节码;运行时用 java 启动 JVM,类加载器加载主类并经历加载-链接-初始化,然后调用 main,执行引擎解释执行并对热点做 JIT 编译,最终程序结束 JVM 退出。

Q2:类加载过程有哪些阶段?

答:加载(Loading)→ 链接(Linking:验证/准备/解析)→ 初始化(Initialization:static 赋值与 static 代码块)。

Q3:static 字段什么时候赋初值?

答:准备阶段赋默认值,初始化阶段执行显式赋值与 static 块(编译期常量可能被内联是例外)。

Q4:什么时候会触发类初始化?

答:首次主动使用:new、调用静态方法、访问静态非 final 字段、Class.forName、初始化子类先初始化父类。

Q5:解释执行与 JIT 的区别?

答:解释执行启动快但慢;JIT 把热点字节码编译为机器码长期更快,所以 Java 常见“越跑越快”。

Q6:双亲委派有什么作用?

答:避免重复加载并保证核心类安全(防止伪造 java.lang.*),父加载器优先,父找不到才子加载。

Q7:JVM 运行时内存怎么划分?

答:堆放对象;栈放栈帧和局部变量;元空间放类元信息;PC 记录执行位置;GC 回收堆中不可达对象。


13. 面试“回答思路模板”(建议照这个结构说,1 分钟很稳)

你可以按这个顺序回答任何“Java 如何运行”的问题:

  1. 先总述两段式:编译期 .java → .class,运行期 JVM 执行字节码
  2. 展开运行期关键:ClassLoader 加载主类 → 链接(验/备/解)→ 初始化(static)
  3. 入口与执行方式:找到 main,解释执行 + JIT 编译热点
  4. 补一句内存与收尾:堆/栈/元空间/PC,main 结束且无非守护线程 JVM 退出
这个结构的好处:层次清晰、可扩展、面试官容易顺着你设定的路线追问(而不是乱问)。

14. 一句话速记(临场救命)

javac 编译成 class 字节码,java 启动 JVM,ClassLoader 加载主类并经历加载-链接(验/备/解)-初始化(static),调用 main,解释+JIT 跑起来,对象在堆、调用在栈、类信息在元空间,main 结束 JVM 退出。

参考:你可以继续扩展的方向(进阶/加分)

  • classpath 与 modulepath 的区别(模块化系统)
  • 反射/动态代理与类加载的关系
  • JIT、逃逸分析、内联、锁消除(性能面试)
  • GC 算法与调参(高级岗位)

Read more

全网最全100道C++高频经典面试题及答案解析:C++程序员面试题库分类总结

全网最全100道C++高频经典面试题及答案解析:C++程序员面试题库分类总结

前言 C++作为一门兼具高性能与灵活性的语言,持续推动着量子计算、自动驾驶、区块链、AI编译器等领域的技术革命。本题库精选100道高频面试题,涵盖从内存模型、编译器内部机制到跨学科前沿应用的深度内容,专为资深工程师、系统架构师及科研岗位设计。无论是准备顶级科技公司面试,还是探索C++在安全关键系统(如航天、医疗)与新兴领域(如脑机接口、边缘AI)的工程实践,这些题目将帮助您展现对语言本质的理解和对复杂场景的掌控力。 题库特点: 垂直深入:超越语法层面,聚焦标准演进(C++20/23)、硬件协同优化及形式化验证等高级主题。 跨领域融合:结合LLVM/MLIR编译器开发、CUDA加速、实时操作系统等场景,体现C++的系统级控制能力。 第一部分:面向对象与内存管理(1-10题) 1. 虚函数实现原理(字节跳动/腾讯) 题目:虚函数表(vtable)在C++中是如何工作的?写出示例代码说明动态多态的实现。

By Ne0inhk

3.6-Web后端基础(java操作数据库)

目录 前言 JDBC 介绍 查询数据 需求 准备工作 代码实现 代码剖析 ResultSet 预编译SQL SQL注入 SQL注入解决 性能更高 增删改数据 需求 代码实现 Mybatis 介绍 快速入门 辅助配置 配置SQL提示 配置Mybatis日志输出 JDBC VS Mybatis 数据库连接池 介绍 产品 增删改查操作 删除 新增 修改 查询 XML映射配置 XML配置文件规范 XML配置文件实现 MybatisX的使用 SpringBoot配置文件 介绍 语法 案例 前言 在前面我们学习MySQL数据库时,都是利用图形化客户端工具(如:idea、datagrip),来操作数据库的。 我们做为后端程序开发人员,

By Ne0inhk
SkyWalking - .NET / C++ / Lua 探针现状与社区支持

SkyWalking - .NET / C++ / Lua 探针现状与社区支持

👋 大家好,欢迎来到我的技术博客! 📚 在这里,我会分享学习笔记、实战经验与技术思考,力求用简单的方式讲清楚复杂的问题。 🎯 本文将围绕SkyWalking这个话题展开,希望能为你带来一些启发或实用的参考。 🌱 无论你是刚入门的新手,还是正在进阶的开发者,希望你都能有所收获! 文章目录 * SkyWalking - .NET / C++ / Lua 探针现状与社区支持 🌐 * 一、SkyWalking 多语言探针架构概览 🧩 * 二、Java 探针:成熟稳定,功能最全 ☕️ * 示例:Spring Boot 应用接入 SkyWalking * Java 探针高级特性 * 三、.NET 探针现状:渐趋成熟,生产可用 🖥️ * 技术原理 * 使用方式 * 当前支持的功能 * 局限性 * 四、C++ 探针现状:SDK 形式,适合嵌入式场景 ⚙️ * cpp2sky SDK

By Ne0inhk

从“会聊天”到“会交付”:用 OpenClaw + DeepSeek 做一个可落地的 AI Agent 工程化流水线(Java/Go/Python)

从“会聊天”到“会交付”:用 OpenClaw + DeepSeek 做一个可落地的 AI Agent 工程化流水线(Java/Go/Python) 主品牌:王仕宇(JavaPub) 关键词:OpenClaw、DeepSeek、AI Agent、大模型工程化、AI Coding、面试提效 一、今天的行业信号:Agent 正在从 Demo 走向交付 过去一年,大家都在讨论“AI 会不会替代程序员”。到 2026 年,一个更务实的问题已经出现: 你的 Agent,能不能稳定、可观测、可复用地交付结果? 这背后不是模型参数竞赛,而是工程化能力竞赛: * 任务编排是否可控(Cron / Heartbeat

By Ne0inhk