Java Runtime Environment (JRE) 详解:从入门到精通 – wiki基地


JRE 详解:从入门到精通

引言:Java 的运行基石

在当今软件开发的浩瀚宇宙中,Java 无疑是那颗璀璨的明星之一。从大型企业级应用到移动设备、从物联网到云计算,Java 的身影无处不在。而支撑这一切“无处不在,一次编写,处处运行”宏伟愿景的核心,便是 Java Runtime Environment(JRE),即 Java 运行时环境。

对于初学者而言,JRE 可能只是一个安装包,用来让 Java 程序在自己的电脑上运行;对于进阶开发者而言,JRE 是 JVM 及其支撑库的集合,是优化应用性能的关键;而对于精通 Java 运行机制的专家来说,JRE 则是一个充满可配置参数、深度优化潜力的沙盒环境。

本文将带领您深入探索 JRE 的世界,从最基础的概念出发,逐步深入其内部机制、核心组件、与 JDK 的关系、性能调优以及在现代 Java 生态系统中的演变,助您真正做到对 JRE 的“从入门到精通”。


第一部分:初识 JRE – 入门篇

1. JRE 是什么?核心概念剖析

Java Runtime Environment (JRE) 是运行 Java 应用程序所必需的软件环境。简而言之,如果您想在计算机上运行一个已经编译好的 Java 程序(.class 文件或 .jar 文件),那么您就需要安装 JRE。它提供了一个执行 Java 字节码的平台。

JRE 并不包含 Java 开发工具包 (JDK) 中的编译器(javac)或其他开发工具。它专注于“运行”而非“开发”。您可以将其比作一个播放器:您可以使用它播放电影(运行 Java 程序),但不能用它来制作电影(开发 Java 程序)。

核心构成:
JRE 主要由以下两个核心部分组成:
* Java 虚拟机 (JVM – Java Virtual Machine):这是 JRE 的心脏,负责解释和执行 Java 字节码。
* Java 类库 (Java Class Libraries):这是一组预编译的类文件,提供了 Java 应用程序所需的核心功能,如输入/输出、网络、图形用户界面等。

2. JRE 的使命:实现“一次编写,处处运行”

Java 最著名的口号是“Write Once, Run Anywhere”(WORA,一次编写,处处运行)。JRE 就是这一承诺的实际执行者。

当 Java 源代码(.java 文件)被编译器(javac,属于 JDK)编译后,它会生成一种平台无关的中间代码,称为字节码(.class 文件)。这种字节码不直接在硬件上运行,而是由 JVM 加载并执行。

由于每个操作系统(Windows、macOS、Linux 等)都有其对应的 JRE 版本,而这些 JRE 版本都包含了一个专门为该平台设计的 JVM,所以只要您的计算机上安装了相应平台的 JRE,就可以运行相同的字节码。这样一来,开发者无需为每个操作系统重新编写和编译代码,极大地提高了开发效率和程序的跨平台性。

3. JRE 的主要组成部分

除了 JVM 和 Java 类库这两个核心,JRE 还包含一些支持文件,它们共同构成了完整的运行时环境:

  • JVM (Java Virtual Machine):

    • 类加载器 (Class Loader):负责从文件系统、网络或其他来源加载 Java 类的字节码到 JVM。
    • 运行时数据区 (Runtime Data Areas):JVM 内存的逻辑划分,用于存储程序运行时的数据,包括堆(Heap)、方法区(Method Area)、Java 栈(Java Stacks)、本地方法栈(Native Method Stacks)和程序计数器(PC Register)。
    • 执行引擎 (Execution Engine):包括解释器(Interpreter)、即时编译器(JIT Compiler)和垃圾回收器(Garbage Collector),负责执行字节码。
  • Java 类库 (Java Class Libraries):

    • 也称为 Java API(Application Programming Interface),是 Java 提供的核心类和接口的集合。它们以 JAR 文件的形式(如 rt.jar 或在模块化后的现代 Java 中的 jmod 文件)存在。
    • 这些类库覆盖了从基本数据结构(java.lang)、工具类(java.util)、输入/输出(java.io)、网络编程(java.net)到图形界面(java.awt, javax.swing)等各个方面,为应用程序开发提供了丰富的功能支持。
  • 支持文件:

    • 运行时库 (Runtime Libraries):一些用于 JVM 自身运行的本地代码库,通常是操作系统相关的动态链接库(如 .dll on Windows, .so on Linux)。
    • 属性文件 (Property Files):配置 JVM 或 Java 类库行为的各种属性文件。
    • 安全策略文件 (Security Policy Files):定义了 Java 应用程序在沙盒环境中可以访问的资源和权限。

4. JRE 的安装与验证

安装 JRE 通常是一个简单的过程,您可以从 Oracle 官网(针对商业用途或旧版本)或 OpenJDK 社区(如 Adoptium、Azul Zulu、Amazon Corretto 等,推荐用于多数场景)下载适合您操作系统的安装包。

安装步骤概要:
1. 访问下载网站。
2. 选择合适的 JRE 版本(通常建议选择 LTS – Long Term Support 版本)和您的操作系统架构。
3. 下载安装包(通常是 .exe for Windows, .dmg for macOS, .rpm/.deb for Linux)。
4. 运行安装程序,按照提示点击“下一步”直到完成。

验证安装:
安装完成后,您可以在命令行中输入以下命令来验证 JRE 是否成功安装:
bash
java -version

如果安装成功,您将看到类似以下输出,显示 Java 版本信息:
openjdk version "17.0.8" 2023-07-18
OpenJDK Runtime Environment (build 17.0.8+7-LTS)
OpenJDK 64-Bit Server VM (build 17.0.8+7-LTS, mixed mode, sharing)

5. 运行第一个 Java 程序

在了解 JRE 的概念后,让我们通过一个简单的例子来体验如何使用 JRE 运行 Java 程序。

步骤 1:编写 Java 源代码
创建一个名为 HelloWorld.java 的文件,并输入以下内容:
java
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JRE!");
}
}

步骤 2:编译 Java 源代码 (需要 JDK)
这一步需要使用 JDK 中的 javac 编译器。如果您安装的是完整的 JDK,那么就可以执行此操作。
打开命令行,导航到 HelloWorld.java 所在的目录,然后执行:
bash
javac HelloWorld.java

如果编译成功,将生成一个 HelloWorld.class 文件。

步骤 3:使用 JRE 运行字节码
现在,使用 JRE 来运行这个 HelloWorld.class 文件:
bash
java HelloWorld

您将在命令行中看到输出:
Hello, JRE!
这就是 JRE 的基本工作原理:接收编译后的字节码,并通过 JVM 将其执行。


第二部分:深入理解 JRE – 进阶篇

1. JRE 与 JDK 的关系:开发者与用户

在 Java 生态系统中,JRE 和 JDK 是两个经常被提及但又容易混淆的概念。理解它们之间的关系是进阶学习的关键。

  • JRE (Java Runtime Environment):正如我们之前所述,它是 Java 程序的运行环境,只包含运行 Java 应用所需的一切(JVM、核心类库及支持文件)。它的目标用户是最终用户,即只需要运行 Java 应用程序的人。
  • JDK (Java Development Kit):它是 Java 开发工具包,是编写 Java 应用程序所必需的工具集合。JDK 包含了 JRE 的所有内容,此外还包含了开发工具,如:
    • javac:Java 编译器,将 .java 源文件编译成 .class 字节码文件。
    • javadoc:Java 文档生成器。
    • jar:JAR 包工具,用于打包和解压 Java 类库。
    • jdb:Java 调试器。
    • 以及其他诊断、监控工具等。

关系总结:
JDK = JRE + 开发工具
形象地说,JRE 是一个 DVD 播放器,而 JDK 则是一个完整的 DVD 录制工作室,它包含了播放器以及录制、编辑 DVD 的所有设备。如果您是 Java 开发者,您需要安装 JDK;如果您只是想运行 Java 程序,安装 JRE 即可。然而,由于 JDK 包含了 JRE,所以安装 JDK 也意味着您具备了运行 Java 程序的能力。

2. JVM 架构深度剖析

作为 JRE 的核心,JVM 的内部机制是理解 Java 运行原理的关键。

a. 类加载子系统 (Class Loader Subsystem)
负责从磁盘或其他网络位置加载 .class 文件到内存中。它有三个主要职责:
* 加载 (Loading):根据类的全限定名获取二进制字节流,将其加载到内存中,并将其转换为 java.lang.Class 对象。
* 链接 (Linking)
* 验证 (Verification):确保字节码符合 JVM 规范,没有安全问题。
* 准备 (Preparation):为类的静态变量分配内存,并初始化为默认值。
* 解析 (Resolution):将符号引用(如方法名、变量名)转换为直接引用(内存地址)。
* 初始化 (Initialization):执行类的 <clinit>() 方法,即执行静态代码块和静态变量的赋值操作。

JVM 内置了三种类加载器:
* 启动类加载器 (Bootstrap ClassLoader):加载 JRE/lib 目录下的核心类库。
* 扩展类加载器 (Extension ClassLoader):加载 JRE/lib/ext 目录下的扩展类库。
* 应用程序类加载器 (Application ClassLoader):加载应用程序的 CLASSPATH 环境变量所指定的路径中的类库。
它们之间构成了一种父子层级关系,但不是继承关系,而是委托关系。

b. 运行时数据区 (Runtime Data Areas)
JVM 内存的逻辑划分,每个线程都有私有的部分,也有所有线程共享的部分:

  • 线程私有区:

    • 程序计数器 (PC Register):一块很小的内存空间,用于存储当前线程正在执行的字节码指令的地址。
    • Java 虚拟机栈 (Java Virtual Stacks):每个方法执行时都会创建一个栈帧(Stack Frame),用于存储局部变量、操作数栈、动态链接、方法出口等信息。方法调用从入栈到出栈,对应着栈帧的压入和弹出。
    • 本地方法栈 (Native Method Stacks):与 Java 虚拟机栈类似,但是为 JVM 调用的本地方法(Native Method,用 C/C++ 编写)服务。
  • 线程共享区:

    • 堆 (Heap):JVM 中最大的一块内存区域,用于存储对象实例和数组。所有线程共享堆内存,也是垃圾回收的主要区域。堆又通常被划分为新生代(Young Generation)和老年代(Old Generation)。
    • 方法区 (Method Area):存储已被 JVM 加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。在 Java 8 及以后,方法区由元空间 (Metaspace) 实现,元空间位于本地内存而非 JVM 内存,因此默认情况下不会溢出。

c. 执行引擎 (Execution Engine)
负责执行字节码。
* 解释器 (Interpreter):逐行解释执行字节码。优点是启动速度快,缺点是执行效率相对较低。
* 即时编译器 (JIT Compiler – Just-In-Time Compiler):为了提高执行效率,JVM 会将频繁执行的“热点代码”编译成机器码,然后直接执行机器码。JIT 编译器通常在程序运行时进行优化,但编译本身需要时间,因此是解释器和 JIT 编译器协同工作。
* Client Compiler (C1):启动速度快,优化强度相对较低。
* Server Compiler (C2):启动速度慢,优化强度高,适用于长时间运行的服务器应用。
* 垃圾回收器 (Garbage Collector – GC):自动管理堆内存,回收不再被引用的对象所占用的内存空间,避免内存泄漏,减轻开发者的内存管理负担。

3. Java 类库的模块化与演进 (Jigsaw 项目)

在 Java 9 之前,所有的核心类库都被打包在一个巨大的 rt.jar 文件中,导致 JRE 体积庞大,难以优化。为了解决这个问题,Java 9 引入了 模块系统 (Java Platform Module System, JPMS,代号 Jigsaw)

  • 目标:提升 Java 平台的可伸缩性、安全性,并减少 JRE 的内存占用。
  • 核心思想:将 JDK 和 JRE 拆分为一系列相互依赖的模块。每个模块都有清晰的 API 边界和依赖关系。
  • 优势
    • 可配置的运行时环境:通过 jlink 工具,开发者可以根据应用程序的实际需求,打包一个只包含所需模块的定制化 JRE,极大减小了分发包的大小。
    • 更强的封装性:模块限制了对内部 API 的访问,提高了平台的安全性。
    • 更好的性能:更小的运行时可以更快地启动,减少内存占用。

现在,您在 JRE 的 lib 目录下看到的不再是一个巨大的 rt.jar,而是一系列 jmod 文件和模块化的运行时映像。

4. 垃圾回收机制 (Garbage Collection) 深入

垃圾回收是 JRE 自动内存管理的核心。理解其基本原理和不同策略,对于编写高性能的 Java 应用至关重要。

a. 基本原理:
GC 的目标是识别并回收那些不再被应用程序引用的对象所占用的内存空间。它通常通过以下步骤进行:
* 可达性分析 (Reachability Analysis):从一组被称为“GC Roots”的对象开始,遍历所有可达的对象。所有从 GC Roots 不可达的对象都被认为是“垃圾”,可以被回收。GC Roots 包括虚拟机栈中的引用对象、方法区中的静态属性引用、本地方法栈中的引用对象等。
* 标记 (Mark):标记出所有可达对象。
* 清除 (Sweep) / 整理 (Compact)
* 标记-清除 (Mark-Sweep):直接清除未被标记的对象,会产生大量内存碎片。
* 标记-整理 (Mark-Compact):在清除前将所有存活对象移动到内存的一端,从而解决内存碎片问题。
* 复制 (Copying):将存活对象从一块区域复制到另一块区域,效率高,但会浪费一半内存空间,常用于新生代。

b. 常见的垃圾回收器:
HotSpot JVM 提供了多种垃圾回收器,各有优缺点,适用于不同的应用场景:
* Serial GC:单线程,GC 时会暂停所有用户线程(Stop The World, STW)。简单高效,适用于单核 CPU 或内存小的客户端应用。
* Parallel GC (Parallel Scavenge):多线程回收新生代,STW。吞吐量优先,适用于多核 CPU、高吞吐量的后台应用。
* CMS GC (Concurrent Mark Sweep):目标是降低 STW 时间,通过并发标记和清除,在应用线程运行时进行大部分工作。但有内存碎片问题,且对 CPU 敏感。
* G1 GC (Garbage-First):JDK 9+ 的默认 GC。将堆划分为多个区域,并跟踪每个区域的垃圾量,优先回收垃圾最多的区域。目标是实现可预测的暂停时间,同时兼顾吞吐量和延迟。
* ZGC / Shenandoah GC:极低暂停时间的并发垃圾回收器,旨在应对超大堆内存(TB 级别)和对延迟要求极高的场景。

c. 内存区域与 GC:
* 新生代 (Young Generation):新创建的对象通常先在新生代分配内存。新生代又分为一个 Eden 区和两个 Survivor 区 (S0, S1)。采用复制算法。
* 老年代 (Old Generation):经过多次垃圾回收仍然存活的对象会被晋升到老年代。采用标记-清除或标记-整理算法。

5. JRE 的安全模型

JRE 提供了一个健壮的安全模型,用于保护用户免受恶意 Java 代码的侵害。
* 沙盒 (Sandbox) 模型:Java 程序在 JVM 的安全沙盒中运行,其对系统资源的访问受到严格控制。默认情况下,未签名的 Applet 或 WebStart 应用程序只能访问很少的本地资源。
* 类加载器架构:通过委派模型,确保系统核心类不会被恶意代码替换或篡改。
* 安全管理器 (Security Manager):一个可编程的接口,允许开发者在运行时定义细粒度的安全策略,控制 Java 代码可以执行的操作(如文件读写、网络连接、系统属性访问等)。虽然在现代 Java Web 应用中,由于容器和框架的安全机制,Security Manager 的直接使用减少,但其理念仍是核心。
* 代码签名 (Code Signing):通过数字证书对 JAR 文件进行签名,验证代码的来源和完整性。


第三部分:JRE 的精通之路 – 高级篇

1. JRE 性能调优与监控

掌握 JRE 的性能调优和监控是成为 Java 高手的必经之路。

a. JVM 启动参数调优:
通过在 java 命令后添加参数,可以精细控制 JVM 的行为:
* 内存参数:
* -Xms<size>:设置 JVM 初始堆内存大小。
* -Xmx<size>:设置 JVM 最大堆内存大小。
* -Xmn<size>:设置新生代大小。
* -XX:NewRatio=<N>:设置新生代和老年代的比例,如 -XX:NewRatio=2 表示新生代占 1/3。
* -XX:MetaspaceSize=<size>:设置元空间初始大小(Java 8+)。
* -XX:MaxMetaspaceSize=<size>:设置元空间最大大小。
* 垃圾回收器选择与配置:
* -XX:+UseSerialGC:使用 Serial GC。
* -XX:+UseParallelGC:使用 Parallel GC。
* -XX:+UseConcMarkSweepGC:使用 CMS GC。
* -XX:+UseG1GC:使用 G1 GC (JDK 9+ 默认)。
* -XX:+UseZGC / -XX:+UseShenandoahGC:使用 ZGC / Shenandoah GC (需要特定版本和实验性参数)。
* -XX:MaxGCPauseMillis=<N>:设置 G1/ZGC 等 GC 期望的最大暂停时间。
* -XX:+PrintGCDetails / -XX:+PrintGCDateStamps:打印详细的 GC 日志。
* JIT 编译器参数:
* -XX:TieredCompilation:启用分层编译(默认开启),C1 和 C2 协同工作。
* -XX:CompileThreshold=<N>:设置方法被 JIT 编译的调用次数阈值。

b. 性能监控工具:
JDK 提供了一系列强大的命令行和可视化工具来监控 JRE 的运行时状态:
* jps:列出所有运行中的 Java 进程。
* jstat:监控 JVM 各种运行状态信息,如堆内存使用、GC 次数和时间等。
* jmap:生成 Java 堆内存转储文件(heap dump),用于分析内存泄漏。
* jstack:生成 Java 线程堆栈跟踪信息(thread dump),用于分析死锁、高 CPU 占用等问题。
* jcmd:多功能诊断命令,可以执行 jmapjstackjstat 的大部分功能。
* JVisualVM (JDK 8 及以前默认集成,之后需要单独下载插件):可视化监控工具,提供 CPU、内存、线程、GC 等图形化界面。
* Java Mission Control (JMC):更强大的可视化监控和诊断工具,可以进行生产环境的低开销监控。

c. 常见的性能问题与诊断:
* 内存泄漏 (Memory Leak):对象持续累积,GC 无法回收,最终导致 OutOfMemoryError。通过 jmap 生成堆转储文件,结合 Eclipse Memory Analyzer Tool (MAT)JVisualVM 进行分析。
* CPU 使用率过高:线程长时间占用 CPU。通过 jstack 生成线程堆栈,结合 top -H -p <pid> (Linux) 或任务管理器 (Windows) 定位具体线程,分析代码执行路径。
* GC 停顿时间过长:频繁且长时间的 GC 暂停。通过 jstat -gc 或 GC 日志分析 GC 类型、频率和耗时,调整 GC 策略和堆大小。
* 死锁 (Deadlock):多个线程互相持有对方所需的资源,导致所有线程都无法继续执行。通过 jstack 定位死锁线程。

2. 自定义 JRE:使用 jlink 创建运行时镜像

在 Java 9 引入模块化之后,jlink 工具成为了一个革命性的功能。它允许您根据应用程序所需的模块,创建一个精简的、自定义的 JRE 运行时镜像。

a. 为什么使用 jlink
* 减小应用包体积:只包含应用程序实际使用的模块和 JVM 组件,可以将运行时环境大小从数百 MB 缩减到几十 MB。
* 提高启动速度:更小的运行时,加载更快。
* 增强安全性:只包含必需组件,减少了攻击面。
* 简化部署:将应用程序和运行时打包成一个独立的单元,无需预装 Java 环境。

b. jlink 的基本用法:
“`bash

假设你的模块化应用程序位于 out/modules

你的主模块是 my.app,依赖 java.base 和 java.desktop 模块

1. 确保你的应用已经被编译成模块化的 JAR

2. 使用 jlink 命令创建运行时镜像

jlink –module-path <路径到你的模块化的JARs和JDK模块> \
–add-modules <你的主模块名> \
–add-modules java.base,java.desktop \
–output my-app-runtime \
–compress=2 \
–no-header-files \
–no-man-pages \
–strip-debug
``
上述命令将创建一个名为
my-app-runtime的目录,其中包含了运行my.app所需的定制化 JRE。然后,你可以通过my-app-runtime/bin/java -m my.app` 来运行你的应用。

3. JRE 的未来展望

Java 平台及其 JRE 在不断进化,以适应云计算、微服务、边缘计算等现代技术趋势:
* Project Valhalla (值类型):旨在引入值类型(Value Types)和泛型特化(Specialized Generics),以提高内存效率和性能。
* Project Loom (虚拟线程/纤程):引入轻量级用户态线程,极大提升并发处理能力,简化异步编程。
* GraalVM (AOT 编译):GraalVM 提供了 Ahead-Of-Time (AOT) 编译能力,可以将 Java 代码直接编译成原生机器码,生成更小的独立可执行文件,实现极快的启动速度和更低的内存占用,特别适用于云原生和函数式计算场景。虽然这不是标准 JRE 的功能,但它代表了 Java 运行时在特定场景下的未来方向。
* OpenJDK 各发行版:除了 Oracle JDK,现在有众多 OpenJDK 发行版,如 Adoptium (Eclipse Temurin), Azul Zulu, Amazon Corretto, Red Hat OpenJDK 等,它们提供了免费且维护良好的 JRE/JDK,并针对特定场景进行优化。

4. JRE 最佳实践与注意事项

  • 选择合适的 JRE 版本:始终使用与您的应用程序兼容且经过充分测试的 JRE 版本,并优先选择 LTS (Long Term Support) 版本。
  • 保持更新:及时更新 JRE 以获取最新的安全补丁和性能改进。
  • 理解内存模型:深入理解堆、栈、方法区(元空间)等内存区域的作用,是诊断和优化性能的基础。
  • 避免过度优化:在进行 JVM 调优前,先进行基准测试和性能分析,找到真正的瓶颈,避免盲目调整参数。
  • 生产环境监控:在生产环境中,持续监控 JRE/JVM 的健康状态(CPU、内存、GC、线程),以便及时发现和解决问题。
  • 利用 jlink 优化部署:对于模块化应用程序,使用 jlink 创建定制化的运行时镜像,可以显著提升部署效率和资源利用率。
  • 避免直接修改 JRE 内部文件:除非您非常清楚自己在做什么,否则不应直接修改 JRE 目录下的文件,这可能导致不稳定或安全问题。

结论

Java Runtime Environment (JRE) 不仅仅是一个运行 Java 程序的平台,它是 Java 生态系统的核心,承载着“一次编写,处处运行”的伟大愿景。从最初的简单执行器,到如今融合了先进的 JVM 技术、模块化类库、高效的垃圾回收机制和强大的安全模型,JRE 在不断地演进和完善。

通过本文的详细阐述,相信您已经对 JRE 有了一个全面的理解:从其基本构成、与 JDK 的区别,到 JVM 的内部机制、内存管理、垃圾回收策略,再到现代 Java 的模块化和性能调优。掌握 JRE 不仅能让您更好地运行和部署 Java 应用程序,更能帮助您编写出更健壮、更高效、更易于维护的 Java 代码。

Java 的旅程永无止境,JRE 的世界也充满了持续学习和探索的乐趣。希望本文能成为您在 Java 之路上迈向精通的坚实一步。祝您在 Java 的世界里,编写出更多精彩的程序!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部