Java虚拟机 - JVM与Java体系结构

发布于:2025-05-19 ⋅ 阅读:(19) ⋅ 点赞:(0)

JVM与Java体系结构

Java不仅仅是一个简单的编程语言,它是由一系列的软件与规范组成的技术体系。而JVM是Java程序的运行核心,通过字节码解释执行实现"一次编写,到处运行"特性,而Java体系结构由编程语言规范、类库体系及JVM运行时环境共同构成,支撑跨平台面向对象开发。可以说JVM是整个Java平台的基石,是将Java技术隔绝操作系统与硬件的关键部分。

为什么要学习JVM

功利点说,现在找工作的门槛越来越高,JVM知识点经常出现在面试题目中,要想通过面试,我们也需要了解JVM。但是除去面试,程序开发中我们也会遇见:

  1. 定位 OOM(内存溢出):通过分析堆转储(Heap Dump)和 GC 日志,快速定位内存泄漏的根源。
  2. 线程死锁:利用 JVM 工具(如 jstack)分析线程状态,解决多线程并发问题。
  3. 性能瓶颈:通过 Profiling 工具(如 VisualVM、Arthas)监控 CPU、内存使用情况,找到性能热点。
    这些都需要我们具备JVM的基础知识,才能在生产中遇到类似问题迎刃而解。掌握了基础知识,我们可以更好地做到下面几点:
  • 内存管理:理解 JVM 的内存结构(堆、栈、方法区等)能帮助你优化内存分配,避免内存泄漏和频繁的垃圾回收(GC)。
  • 垃圾回收机制:不同场景需要不同的 GC 算法(如 G1、ZGC、Shenandoah),学习 JVM 可以合理选择并配置 GC,减少程序停顿时间。
  • 代码优化:通过 JIT 编译器、逃逸分析等机制,理解 JVM 如何优化代码执行,从而编写更高效的代码

Java与JVM简介

Java 自 1995 年由 Sun Microsystems 发布以来,凭借其 跨平台能力、面向对象特性 和 丰富的生态系统,迅速成为全球最流行的编程语言之一。而 Java 虚拟机(JVM)作为 Java 技术的核心引擎,通过“一次编写,到处运行”(Write Once, Run Anywhere)的理念,彻底改变了软件开发的模式。

下面这张图是Java技术体系,从这张图我们可以了解Java与Jvm的关系。应该从这张图,我们可以形象地认识到JVM是Java语言的核心基石,所以我们要想学习好Java语言,一定要了解JVM。
java技术体系

Java 语言的核心特性

  1. 跨平台能力
    Java 的跨平台性依赖于 JVM 的中间层设计。开发者编写的 .java 文件会被编译为与平台无关的 字节码(.class 文件),由 JVM 在目标平台上解释或编译执行。

示例:同一份 Java 程序可在 Windows、Linux、macOS 上运行,无需修改源码。
在这里插入图片描述

意义:降低多环境适配成本,推动企业级应用的快速部署。

  1. 面向对象设计(OOP)
    Java 是纯粹的面向对象语言,其核心思想通过 封装、继承、多态 实现:

    • 封装:通过 private、protected 关键字隐藏实现细节,暴露安全接口。

    • 继承:extends 实现代码复用,implements 支持多接口扩展。

    • 多态:同一方法在不同子类中呈现不同行为(如 Animal 类的 sound() 方法被 Dog 和 Cat 重写)。

  2. 健壮性与安全性

    • 异常处理:强制检查异常(Checked Exceptions)要求开发者显式处理潜在错误(如 IOException)。

    • 内存管理:JVM 自动垃圾回收机制(GC)减少内存泄漏风险。

    • 安全沙箱:通过字节码验证和安全管理器(SecurityManager)限制恶意代码访问系统资源。

  3. 现代语言特性演进
    Java 持续吸收现代编程范式的优点:

    • Java 8:引入 Lambda 表达式、Stream API,支持函数式编程。

    • Java 11:var 关键字简化局部变量声明,HTTP Client 支持异步请求。

    • Java 17:密封类(sealed class)限制继承关系,模式匹配增强代码可读性。

JVM:Java 生态的基石

  1. JVM 的核心作用
    JVM 是 Java 程序运行的虚拟化环境,主要职责包括:
  • 字节码解释与执行:将 .class 文件转换为机器指令。

  • 内存管理:分配堆、栈、方法区等内存空间,并自动回收垃圾对象。

  • 线程调度:管理多线程的创建、同步与资源竞争。

  1. JVM 的运行时数据区
    JVM 内存划分为多个核心区域:
  • 堆(Heap):存储对象实例,是垃圾回收的主战场。

  • 方法区(Metaspace):存放类元数据(Java 8 后替代永久代)。

  • 虚拟机栈:存储方法调用的栈帧(局部变量、操作数栈)。

  • 程序计数器:记录当前线程执行的字节码位置。

  • 本地方法栈:支持 Native 方法(如 C/C++ 库调用)。

  1. 类加载机制
    JVM 通过 双亲委派模型 加载类:
  • 加载:从文件、网络等来源读取 .class 文件。

  • 验证:确保字节码符合 JVM 规范,防止恶意代码注入。

  • 准备:为静态变量分配内存并初始化默认值。

  • 解析:将符号引用转换为直接引用。

  • 初始化:执行静态代码块(static{})和赋值操作。

示例:自定义类加载器可实现热部署(如 Tomcat 为每个 Web 应用单独加载类)。

  1. 垃圾回收(GC)机制
    JVM 通过 GC 自动回收无用的对象,关键算法包括:
  • 标记-清除:简单但易产生内存碎片。

  • 复制算法:将存活对象复制到新空间(适用于年轻代)。

  • 标记-整理:整理内存碎片(适用于老年代)。

分代收集:根据对象生命周期划分年轻代(Young Generation)和老年代(Old Generation)。

现代 GC 器:G1(低延迟)、ZGC(TB 级堆内存)、Shenandoah(并发回收)。

调优场景:高并发服务可通过 -XX:+UseG1GC 启用 G1 垃圾回收器,减少停顿时间。

  1. JIT 编译器
    JVM 通过 即时编译(Just-In-Time Compilation) 提升性能:
  • 解释执行:初期逐行解释字节码,启动速度快。

  • 热点代码优化:频繁执行的代码(热点代码)被编译为本地机器码。

  • 逃逸分析:优化对象分配(如栈上分配、锁消除)。

JVM的架构模型

Java编译器输入的指令流是一种基于栈的指令集架构,另外一种常见的指令集架构则是基于寄存器的指令集架构。计算机指令集架构(ISA)是硬件与软件交互的核心接口,决定了程序如何被编译和执行。基于栈的指令集架构(如 JVM 字节码)和 基于寄存器的指令集架构(如 x86、ARM)是两种经典的设计范式,它们在指令执行方式、性能特点和应用场景上存在显著差异。

基于栈的指令集架构(Stack-Based)
  1. 核心原理
    数据操作依赖栈结构:所有计算通过操作数栈(Operand Stack)完成。指令从栈顶取操作数:例如,加法指令 iadd 会弹出栈顶两个整数,相加后结果压回栈顶。无需显式指定操作数地址:指令本身不包含寄存器或内存地址,隐含依赖栈顶数据。

  2. 典型示例(JVM 字节码)

	java
		// Java 代码:计算 3 + 5
		int a = 3;
		int b = 5;
		int c = a + b;
		对应的字节码:

字节码

	iconst_3    // 将常量3压入栈顶
	istore_1    // 弹出栈顶值(3),存入局部变量表第1槽位(a)
	iconst_5    // 将常量5压入栈顶
	istore_2    // 弹出栈顶值(5),存入局部变量表第2槽位(b)
	iload_1     // 加载局部变量a的值(3)到栈顶
	iload_2     // 加载局部变量b的值(5)到栈顶
	iadd        // 弹出栈顶两个值(3和5),相加后结果(8)压入栈顶
	istore_3    // 弹出栈顶值(8),存入局部变量表第3槽位(c)
  1. 优点
  • 指令紧凑:无需指定操作数地址,指令长度短(如 JVM 字节码通常为 1-2 字节)。

  • 跨平台友好:不依赖物理寄存器数量或布局,适合虚拟机实现(如 JVM)。

  • 代码生成简单:编译器无需处理寄存器分配,逻辑更简单。

  1. 缺点
  • 执行速度较慢:频繁的入栈、出栈操作导致内存访问开销大。

  • 指令数量多:简单操作可能需要多条指令(如加载变量到栈顶再计算)。

基于寄存器的指令集架构(Register-Based)
  1. 核心原理
    数据操作依赖寄存器:指令直接读写寄存器中的操作数。

显式指定操作数地址:指令需声明操作数所在的寄存器或内存地址。

结果直接写入寄存器:例如,加法指令 ADD R1, R2, R3 表示 R1 = R2 + R3。
2. 典型示例(ARM 汇编)

// 计算 3 + 5,结果存入寄存器 R0
MOV R1, #3     // 将立即数3存入寄存器R1
MOV R2, #5     // 将立即数5存入寄存器R2
ADD R0, R1, R2 // R0 = R1 + R2
  1. 优点
    执行效率高:减少内存访问次数,数据直接在寄存器中操作。

指令数量少:单条指令可完成复杂操作(如 ADD 直接操作三个寄存器)。

硬件优化潜力大:与现代 CPU 的多级流水线、乱序执行等特性契合。

  1. 缺点
    指令长度较长:需编码寄存器地址,指令占用空间更大。

依赖硬件寄存器数量:寄存器数量有限的架构(如 x86)可能需频繁内存交互。

编译器复杂度高:需优化寄存器分配策略(如避免寄存器溢出)。

JVM生命周期

JVM的生命周期分为三个状态:启动、执行和推出

  1. 启动: JVM可以通过java命令启动,接着通过引导类加载器加载类文件,最后找到程序中的main方法,接着开始执行Java应用程序
  2. 执行: JVM的执行,表示一个已经启动的JVM开始执行Java程序,执行一个Java程序,真正执行的是一个JVM的进程。
  3. 退出: JVM的退出有下面几种情况
  • Java程序正常结束,所有的非守护线程结束
  • Java程序异常
  • 操作系统故障
  • 用户手动关闭JVM
  • 调用Runtime或者System的exit方法

总结

我们本章简单介绍一下Java语言与JVM的结构,之后我们会在专栏文章中,逐步把我们上面介绍的知识点都覆盖到,争取能够做到让大家能够对JVM的知识有一个宏观的认识。


网站公告

今日签到

点亮在社区的每一天
去签到