【Java ee初阶】jvm(1)

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

一、JVM

Java虚拟机

面试中相关的问题有三块:

1.JVM内存区域划分

2.JVM的类加载机制

3.JVM的垃圾回收机制

JDK、JRE 和 JVM 的关系

JDK(Java Development Kit)是 Java 开发工具包,包含了编写、编译和调试 Java 程序所需的所有工具和库。JDK 包含了 JRE(Java Runtime Environment)以及编译器(javac)、调试器(jdb)等开发工具。

JRE(Java Runtime Environment)是 Java 运行时环境,包含了运行 Java 程序所需的核心库和 JVM(Java Virtual Machine)。JRE 是运行 Java 程序的最小环境,不包含开发工具。

JVM(Java Virtual Machine)是 JRE 的核心模块,负责执行 Java 字节码。JVM 是 Java 实现跨平台的关键,它通过将 Java 字节码翻译成特定平台的机器码来实现跨平台运行。

JVM 带来的好处

JVM 的主要优势在于其跨平台性和与硬件无关性。通过 JVM,Java 程序可以在不同的操作系统和硬件架构上运行,而无需修改代码。

跨平台性:Java 程序可以在不同的操作系统上运行,因为 JVM 提供了统一的运行环境。不同的操作系统有不同的 API,JVM 通过提供统一的接口屏蔽了这些差异。

与硬件无关性:Java 程序可以在不同架构的 CPU 上运行,因为 JVM 将 Java 字节码翻译成特定 CPU 的机器码。不同的 CPU 架构有不同的指令集,JVM 通过提供不同的实现来支持这些架构。

JVM 的实现

官方提供了多个版本的 JVM,支持不同的操作系统和 CPU 架构。例如,Windows x86、Windows ARM、Linux x86、Linux ARM、macOS ARM 等。这些 JVM 对上层 Java 程序的要求是统一的,程序员只需要编写一套代码,就可以在所有支持的环境中运行。

JVM 的优缺点

优点:JVM 提高了开发效率,程序员无需为不同的平台编写不同的代码。JVM 还提供了内存管理、垃圾回收等高级功能,简化了开发过程。

缺点:JVM 的翻译过程降低了运行效率,因为 Java 字节码需要被翻译成机器码才能执行。然而,这种效率的降低通常被开发效率的提高所抵消,整体上仍然是划算的。

编程语言与虚拟机的关系

许多现代编程语言,如 Python、JavaScript、Ruby 和 Lua,都采用了虚拟机或运行时环境的架构。虽然这些语言通常不直接称为“虚拟机”,但它们的运行时环境在功能上与虚拟机类似,负责解释和执行代码。

生态系统的关键性

创建一个新的编程语言,最大的挑战往往不在于语言本身的设计,而在于构建一个强大的生态系统。生态系统包括库、框架、工具和社区支持,这些因素共同决定了语言的普及程度和实用性。

  • Java 的成功很大程度上归功于其强大的生态系统,提供了丰富的库和工具,支持各种应用场景。
  • Python 的流行同样得益于其广泛的生态系统,特别是在数据科学、机器学习和Web开发领域。
  • C++ 的不可替代性则源于其高性能和底层控制能力,尽管其生态系统相对复杂。

利用现有生态系统的策略

一些新兴语言通过利用现有语言的生态系统来加速自身的发展。例如:

  • KotlinScalaGroovy 等语言通过编译成与 Java 虚拟机(JVM)兼容的字节码,直接利用 Java 的生态系统。这意味着这些语言可以无缝使用 Java 的库和工具,极大地降低了开发者的学习曲线和开发成本。

应用场景

  • 安卓开发:Kotlin 已经成为安卓开发的首选语言,逐渐取代了 Java 的地位。
  • 大数据开发:Scala 在大数据领域,特别是与 Apache Spark 的集成中,表现出色。
  • 生态共享:这些语言通过兼容 JVM,能够直接利用 Java 的丰富资源,实现生态系统的共享。

技术实现

  • 构建工具:这些语言可以作为构建工具中的脚本使用,简化项目的构建和部署过程。
  • 编译器:编译器将这些语言的代码编译成与 JVM 兼容的字节码,确保它们能够在 JVM 上运行。
  • 库共享:由于字节码的兼容性,所有 Java 的库和工具都可以被这些语言直接使用,极大地扩展了它们的功能和应用范围。

通过这种方式,新兴语言能够在短时间内建立起强大的生态系统,加速其普及和应用。

Java 程序的运行流程

编写 .java 文本文件是 Java 程序开发的第一步。通过 javac 命令行工具将 .java 文件编译成 .class 文件,每个 Java 中的 class 会对应一个 .class 文件。一个 .java 文件中允许有多个 class,但只能有一个 public class

通过 java 命令行工具运行 .class 文件,java 命令会启动一个 Java 进程,即 JVM 进程。JVM 负责解释执行 .class 文件中的字节码。

受查异常的编译报错与抛出异常的区别

受查异常(Checked Exception)在Java中是一种必须在编译时处理的异常类型。编译阶段的报错与运行时抛出的异常是两个不同的概念。

编译阶段的报错是编译器在检查代码时发现的语法或语义问题。例如,如果代码中未处理受查异常,编译器会报错,提示开发者必须处理这些异常。这种报错属于编译错误(Compilation Error),而不是异常抛出。

真正的异常抛出发生在程序运行时。当程序执行过程中遇到异常情况(如文件不存在、网络连接中断等),JVM会抛出相应的异常对象。这种异常抛出是运行时行为,与编译阶段的报错有本质区别。

Java受查异常处理机制

在Java中,受查异常的处理机制要求开发者在方法声明中明确指定可能抛出的受查异常,并在调用这些方法时显式处理这些异常。处理方式包括使用try-catch块捕获异常,或者在方法声明中继续抛出异常。

如果开发者未处理受查异常,编译器会报错,导致代码无法通过编译。这种编译报错是Java类型安全设计的一部分,确保开发者在编写代码时考虑到可能的异常情况。

与Python等动态语言的对比

与Java不同,Python等动态语言没有编译时异常检查机制。Python中的异常(包括语法错误)通常在运行时才会被发现。例如,文件操作异常在Python中只有在实际执行时才会抛出,而不会在编译阶段报错。

典型受查异常场景

在Java中,典型的受查异常场景包括磁盘已满时继续写文件、读取不存在的文件、TCP连接中断时继续通信等。这些情况下,开发者必须在代码中显式处理异常,否则代码将无法通过编译。

总结

编译阶段的报错是语法约束的结果,而运行时抛出的异常是程序执行过程中遇到的异常情况。Java通过受查异常机制确保开发者在编写代码时考虑到可能的异常情况,从而提高代码的健壮性和安全性。

二、JVM的内存划分

java程序跑起来,得到java进程,需要从操作系统中申请一大块内存空间。

java进程就需要把这一大块空间,分成多个区域,分别赋予不同的功能。

JVM内存区域划分(简化模型)

程序计数器(Program Counter Register)

程序计数器用于保存当前线程即将执行的下一条字节码指令地址,其特性包括:

  • 线程私有,每个线程独立拥有一个程序计数器。
  • 唯一不会发生OutOfMemoryError的内存区域。
  • 执行Native方法时,程序计数器的值为undefined

程序计数器可以类比为书签,用于记录线程切换时的执行位置。程序计数器是一个非常小的空间,只需要保存一个“地址”。描述当前Java程序要运行的下一个字节码指令的位置。

线程是cpu调度执行的基本单位,每个线程都得有一个程序计数器来进行记录。cpu执行指令,只是“默认是顺序执行”,如果遇到if while for 方法调用 抛出异常等等,都会使得指令不再顺序执行,而是跳转到某个位置

线程是并发执行的,会产生调度,一个线程,执行一段时间之后,需要调度走,后续再调度回来。此刻就体现了保存上下文这个特点,这个线程执行过程中的所有相关寄存器的值(包括了pc)都被记录下来了。

程序计数器的值,java代码是干预不了的。

元数据区(Metaspace)详解

1. 基本概念

元数据区是JVM用于存储类元数据的内存区域,在Java8之前,称为方法区,放的是方法相关的指令,放的是类 对应的指令。元数据区的内容,java代码也干预不了,你的代码中写了多少类,元数据区的内容也就确定了。

2. 存储内容

元数据区主要存储以下内容:

  • 类信息:包括类名、父类、实现的接口、类静态成员、字段和方法的信息。
  • 运行时常量池:包含字符串常量、final常量以及符号引用。
  • 程序计数器值、代表的要执行的指令的地址,也是在元数据区域。

JVM 栈(Stack)

1. 基本概念

JVM 栈是线程私有的内存区域,每个线程在创建时都会分配一个独立的 Java 虚拟机栈。栈采用后进先出(LIFO)的数据结构,与方法调用的顺序一致。栈的基本单位是栈帧(Stack Frame),每个栈帧代表一个方法调用。

2. 栈帧结构

每个栈帧包含以下部分:

  • 局部变量表(Local Variables):存储方法参数和方法内定义的局部变量。基本类型直接存储,引用类型存储引用地址。
  • 操作数栈(Operand Stack):方法执行时的工作区,用于存放计算中间结果。
  • 动态链接(Dynamic Linking):指向运行时常量池的方法引用,用于支持方法调用时的动态绑定。
  • 方法返回地址(Return Address):方法执行完毕后返回的位置。
  • 附加信息:如调试信息等,用于支持调试和异常处理。
3. 方法调用示例

以下是一个简单的方法调用示例:

void a() {
    b();  // 调用方法b
}
void b() {
    c();  // 调用方法c
}
void c() {
    // 方法体
}

调用过程如下:

栈底 [a的栈帧] -> [b的栈帧] -> [c的栈帧] 栈顶
4. 关键特性
  • 栈大小:可通过-Xss参数设置栈的大小,默认大小为1MB。
  • 栈溢出:当栈深度超过限制时,会抛出StackOverflowError,常见于递归调用过深的情况。
  • 调试信息:调试器会将栈帧中的地址转换为可读的代码行号,便于调试。
5. 与数据结构栈的区别
特性 JVM栈 数据结构栈
内容 栈帧(方法调用) 任意数据
管理 JVM自动管理 程序员控制
大小 固定(可配置) 动态增长
6. 多线程场景

在多线程环境中,每个线程独立维护自己的调用栈。线程切换时,栈状态会被保存,以便线程恢复执行时能够继续从上次的状态开始。

JVM 堆(Heap)详解

1. 基本概念
  • 存储内容:所有通过new创建的对象实例和数组
  • 线程共享:整个JVM进程只有一块堆内存(元数据区域也只有一个,但是栈和程序计数器不只一个)


网站公告

今日签到

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