当前位置: 首页 > news >正文

JVM从操作系统层面的总体启动流程

简化的高层流水线(后面我会逐步展开):

  1. 操作系统启动 java 可执行程序(Launcher)
  2. JVM 进程初始化(解析命令行、设置环境)
  3. 启动类加载子系统(Bootstrap → Extension → App)
  4. 加载并解析引导类(例如 java.lang.Objectjava.lang.String 等)
  5. 初始化运行时数据区(方法区/Metaspace、堆、栈、本地方法栈、程序计数器)
  6. 解析 JAR 的 Main-Class,创建主线程并初始化 SystemInput/OutputProperties
  7. 类的加载 → 验证 → 准备 → 解析 → 初始化(静态块/静态字段)
  8. 执行 main():解释器执行,热点代码由 JIT 编译器编译为本地代码(逐渐优化)
  9. 运行时继续(线程、GC、JNI、safepoint 等机制持续工作)
  10. JVM 退出(main 结束或 System.exit,被 shutdown hook 管理)

下面逐步拆解每一环节。


1) Launcher(启动器)——java -jar app.jar

  • 职责:这是 OS 层调用的二进制($JAVA_HOME/bin/java),用来解析命令行参数、设置进程环境、加载 JVM 动态库并启动 JVM 实例(通常是 HotSpot 的 libjvm.so / jvm.dll)。

  • 解决的问题:桥接操作系统和 JVM 的原生实现;负责把用户命令(如 -Xmx, -D 系列系统属性,-jar 等)传入 JVM。

  • 细节

    • -jar app.jar:Launcher 会打开 jar,读取 META-INF/MANIFEST.MFMain-Class 属性,作为程序入口。
    • 如果有 -cp-jar 同时出现,-jar 优先,jar 内的 classpath 控制类加载。
  • 观察方式

    • java -version 检查使用的 JVM 实现/版本。

2) JVM 进程初始化(解析命令行、设置环境)

  • 职责:JVM 内部完成命令参数解析(堆大小、GC 策略、JIT 参数等)、初始化 JIT/GC 子系统的默认配置、设置默认类库路径、初始化日志与诊断接口。
  • 解决的问题:把用户想法(内存限制、调试选项)转换成各子系统的运行参数。
  • 注意:很多 -XX: 参数和 -X 参数会影响后续模块(比如 -Xmx 决定堆的大小,进而影响 GC 策略)。

3) 初始化运行时数据区(Java 内存模型的主要区域)

JVM 启动时会分配/准备下面这些区域(每个区域的职责我也列出):

  • 方法区 / Metaspace(Java 8+)

    • 存放类的元数据(类结构、常量池、方法字节码的元信息等)。
    • Java 8 之前是 PermGen,Java 8 起改为 Metaspace(native 内存中管理类元数据)。
    • 解决的问题:持久保存“类的信息”,运行时需要这些信息来创建对象、解析方法调用等。
    • 可调参数示例:-XX:MaxMetaspaceSize=...
  • 堆(Heap)

    • 存放对象实例,GC 管理的主体区域。通常分代(年轻代 Eden + Survivor、老年代)。
    • 解决的问题:对象的分配与回收(内存管理)。
    • 可调参数示例:-Xms(初始堆)、-Xmx(最大堆)、-XX:+UseG1GC 等。
  • Java 栈(每线程一个)

    • 存放局部变量表、操作数栈、帧数据。用于方法调用的局部信息。
    • 解决的问题:方法调用状态、局部基本类型和对象引用的存储、方法返回/异常处理支持。
    • 可调参数:-Xss 设置栈大小。
  • 本地方法栈(Native Stack)

    • 保存 JNI 本地方法调用的本地帧(在某些实现与 Java 栈合并)。
    • 解决的问题:支持调用本地(C/C++)方法的运行时栈。
  • 程序计数器(PC寄存器)

    • 每线程保存当前字节码指令地址/下一条执行指令。
    • 解决的问题:线程切换时保存执行位置(轻量小结构)。

4) 类加载子系统(ClassLoaders)——把类字节码带入运行时

  • 总体职责:把 .class(来自 jar、目录、网络)字节码读进来,转换成 JVM 内部的 Class 对象(驻留在方法区/Metaspace)。

  • 三大类加载器(常见的层次委托)

    • Bootstrap ClassLoader(引导类加载器):用本地代码实现,加载 JRE 核心类(rt.jar / jmods 中的类,如 java.lang.*)。
    • Extension / Platform ClassLoader:加载扩展/平台类。
    • Application / System ClassLoader:加载应用类路径下的类(jar 内的应用类通常由它加载)。
  • 双亲委派模型

    • 默认情况下,类加载器会先请求父加载器加载,父加载器找不到才由子加载器尝试加载。这样可保证核心类不会被覆盖(安全与一致性)。
  • -jar 的特例

    • 当使用 java -jar app.jar 时,jar 内的 Class-Path 和 Main-Class 决定应用类加载器的搜索空间。
  • 观察/调试

    • -verbose:class 可以打印类加载信息。

5) 类加载后的“链接”阶段:验证 → 准备 → 解析

类从字节码到可用的 Class 对象,会经过链接(linking)三个子阶段:

  1. 验证(Verification)

    • 目的:确保字节码符合 JVM 规范,不会危害 JVM 安全(如越界访问、类型不匹配等)。
    • 解决的问题:防止恶意或损坏的类破坏 VM(安全性、可靠性)。
    • 内容:文件格式验证、元数据验证、字节码验证(数据流/控制流分析、操作数栈正确性)等。
  2. 准备(Preparation)

    • 目的:为类变量分配内存并设置默认初始值(static fields)。
    • 解决的问题:在初始化前建立类的静态存储位置。
  3. 解析(Resolution)

    • 目的:把常量池中的符号引用解析为直接引用(比如把方法符号解析到具体方法) —— 这是懒解析或提前解析,取决于实现。
    • 解决的问题:把符号层(名字)转换为内存地址或直接指针,便于运行时快速访问。

6) 类初始化(Initialization)

  • 执行时间点:在首次主动使用类(创建实例、访问静态方法/字段、反射调用等)之前,JVM 会执行类的初始化。

  • 初始化内容

    • 执行类的 <clinit>(由编译器生成,包含静态变量赋值和 static 块的内容)。
    • 按照严格的语义保证初始化的线程安全(只有一个线程执行 <clinit>,其他线程会阻塞或看到初始化结果)。
  • 解决的问题:保证静态变量与静态块的正确、可见、顺序执行(Java 内存模型语义)。


7) 启动主线程与 java.lang.System / I/O 初始化

  • 创建主线程(main thread):JVM 创建第一个 Java 线程并在其中调用 Main-Classpublic static void main(String[])

  • System 初始化

    • 初始化 System.out/err/inProperties(包括 user.dirjava.home 等系统属性),并把 args 传入 main
    • 如果有 SecurityManager(过去常用,近年来不常用/被弃用),会进行安全策略初始化。
  • 解决的问题:建立 Java 程序入口,连接标准 I/O,与系统环境做衔接。


8) 执行引擎:解释器 + JIT(即时编译器)

  • 解释器(Interpreter)

    • 字节码逐条解释执行,启动速度快但性能较低。
    • 解决问题:快速启动、低延迟的代码可执行性。
  • JIT 编译器(Just-In-Time)

    • 热点代码(被频繁执行的方法/循环)会被编译成本地机器码以加速执行。
    • HotSpot 常见有 Tiered Compilation(解释器 → C1(快速编译)→ C2(高优化)),也可能使用 Graal(在某些发行版中)。
    • 优化示例:内联(method inlining)、逃逸分析(escape analysis,栈上分配/标量替换)、循环优化、逃逸消除、锁消除/偏向锁等。
    • 解决的问题:把长期运行的 Java 代码性能接近手写本地代码,同时保持安全性与可移植性。
  • 观察/调试

    • -XX:+PrintCompilation-XX:+PrintInlining,以及 -XX:+UnlockDiagnosticVMOptions 系列可以打印编译与内联信息。

9) 运行时服务与关键机制(持续运行阶段)

这些机制在 JVM 运行过程中持续运作:

  • 垃圾回收器(Garbage Collector)

    • 负责回收堆中的无用对象。常见算法:Serial、Parallel、CMS(较老)、G1(默认在很多 JDK 版本中)、ZGC、Shenandoah(低停顿 GC)。
    • 工作机制:分代回收(年轻代频繁回收、老年代少量回收),并发/并行/暂停策略各异。
    • 解决的问题:自动内存管理,避免内存泄露和野指针问题,同时努力降低停顿时间(GC 暂停影响应用响应)。
    • 观察-Xlog:gc*(Java 9+)或 -XX:+PrintGCDetails(早期)来打印 GC 日志。
  • safepoint(安全点)

    • 为了进行如 GC、类重定义、线程栈扫描等全局操作,JVM 需要在所有线程处于可暂停状态(safepoint)。JIT 编译插入检查来使线程到达 safepoint。
    • 解决的问题:在一致的状态下安全执行全局操作。
  • Deoptimization(去优化)与 On-Stack Replacement(OSR)

    • 当 JIT 在优化时基于某些假设(比如类未被子类化)做了优化,如果假设被破坏(运行时出现新的子类),JVM 可以撤销优化(deopt)并返回解释执行。
    • OSR 允许在方法内部循环热点处直接替换解释器为编译代码、或反向操作以保证语义一致。
    • 解决的问题:动态类型系统中保持安全性的同时尽量优化性能。
  • JNI(Java Native Interface)

    • Java 与本地 C/C++ 代码交互的接口。加载本地库、调用本地方法。
    • 注意:本地代码可以绕过 Java 的安全与内存管理,需要谨慎(容易导致崩溃)。
  • 类数据共享(CDS / AppCDS)

    • 提前把常用类元数据打包共享,缩短启动时间并降低内存占用。
    • 观察-Xshare:... 相关参数。

10) JVM 退出(Shutdown)

  • 触发条件

    • main 线程结束且没有其他非守护线程,或 System.exit() 被调用,或收到进程结束信号。
  • 关闭流程

    • 执行注册的 shutdown hooks(Runtime.addShutdownHook(...))并等待其完成(有超时/顺序)。
    • 执行 finalize()(Java 9 起被弃用/不推荐,且行为不可靠)。
    • 释放资源、卸载本地库、退出进程返回给 OS。
  • 注意:shutdown hook 的滥用会导致进程退出变慢或死锁。


常见观察与调试命令(实战)

  • -verbose:class:打印类的加载情况。
  • -XX:+PrintCompilation:打印哪些方法被 JIT 编译了。
  • -Xlog:gc*-XX:+PrintGCDetails + -XX:+PrintGCTimeStamps:查看 GC 行为。
  • jcmd <pid> VM.system_properties / jinfo / jmap / jstack / jstat:JVM 运行时诊断工具。
  • -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining:查看内联决策。

把每个组件“解决的问题”再归纳(便于记忆)

  • Launcher:把 OS 命令转成 JVM 配置并加载 JVM(进程启动桥接)。
  • ClassLoader:把类字节码带入运行时并实现模块化/隔离。
  • Verifier(验证器):保证字节码安全与规范。
  • Linker(准备/解析):为类分配内存并解析符号引用(加速运行时访问)。
  • Initializer(初始化):执行静态初始化,保证语义与线程安全。
  • Heap + GC:管理对象的分配与回收(自动内存管理,减少内存错误)。
  • Stack / PC / Frames:管理方法调用和局部变量(程序执行的临时状态)。
  • Interpreter + JIT:提供可执行性与性能:解释器保证可运行,JIT 提高长期运行性能。
  • JNI / Native:与平台/系统资源互操作(但更危险)。
  • Safepoint / Deopt / OSR:保证运行时可以安全执行全局操作并在动态场景下恢复正确性。

常见误解与陷阱

  • “JVM 没有垃圾回收,所以内存泄漏只是代码问题”:不是。内存泄漏在 Java 中通常表现为对象被意外持有引用,导致 GC 无法回收。JVM 的 GC 机制并不能替你避免逻辑上的泄漏。
  • “JIT 总是越晚越好”:JIT 编译提升了性能,但编译本身有成本(CPU/time)。Tiered 编译在折衷“启动速度 vs 稳定高性能”之间取平衡。
  • “PermGen 仍然存在”:Java 8+ 已弃用 PermGen,采用 Metaspace(native 内存)——这会影响类加载与内存监控策略。
  • “所有锁优化都透明且无风险”:锁消除/偏向锁能提高性能,但在极端多线程竞争下可能被撤销;理解 safepoint/trap 语义有助于 debug。

总结(关键记忆点)

  • JVM 启动:Launcher → JVM 初始化 → 内存区分配 → 类加载(验证/准备/解析/初始化)→ 创建主线程 & 执行 main → 解释 + JIT 优化 → GC / Thread / JNI 等持续工作 → 退出。
  • 每个组件的目标都是在安全、可移植的前提下提供高性能的 Java 运行环境:类加载保证模块化与安全,验证保证字节码安全,GC 管理内存,JIT 把热点变快。

http://www.dtcms.com/a/536582.html

相关文章:

  • C++list类的模拟实现
  • 深圳三站合一网站建设网站建设推广怎样找客户
  • 【多所高校主办】第七届机器人、智能控制与人工智能国际学术会议(RICAI 2025)
  • 做网站有虚拟服务器什么是网络营销产生的基础
  • 高配款浮标五参数—可以及时掌握水体的生态状况
  • 《Java 实用技巧:均匀取元素算法(支持不足补齐)》
  • 【Linux】nohup命令
  • 泰州网站建设案例昆明网站seo外包
  • 【成长纪实】星光不负 码向未来|我的 HarmonyOS 学习之路与社区成长故事
  • 网站服务器租用4t多少钱一年啊提供网站建设公司有哪些
  • 如何处理系统环境变量的字符长度超过了 Windows 对话框的限制(2047 个字符)
  • 快速上手大模型:深度学习1(初识、神经网络基础)
  • Java---StringBuffer类
  • 【从零开始构建性能测试体系-10】人工智能与性能测试:如何借助AI提升测试效率
  • 网站建设人员要与客户谈什么一篇关于大学网站建设与管理的论文
  • 子洲网站建设制作网站上做网页怎么改图片
  • kafka使用-Producer
  • CUDA实现的点云MLS滤波
  • Spring Framework源码解析——TaskScheduler
  • 【从零开始开发远程桌面连接控制工具】02-服务端实现详解
  • 湖州网站设计公司WordPress博客Vieu主题
  • 国外好看的网站设计国外网站需要备案
  • 福克斯特solo4 2i2 Focusrite solo4 2i2 录制音乐 全民K歌单声道问题
  • 《信息系统项目管理师》案例分析题及解析模拟题8
  • MCU中的HSE(高速外部时钟,High-Speed External)
  • 开发中的英语积累 P9:Dispatch、Multi、Retain、Restore、Yield、Interrupt
  • ViT算法流程——从 原始像素 → 网络输出 logits 的 每一步张量形状、公式、关键代码
  • 前端与移动开发之 CSS vs QSS
  • 上那个网站找手工活做网上项目外包
  • 网站建设项目开发响应式学校网站模板下载