编程

JVM、JRE 和 JDK 之间的区别

515 2024-10-24 12:30:00

1. 概述

本文中,我们将通过 JVM、JRE 和 JDK 的组件和用途来讨论它们之间的差异。

2. JVM

Java Virtual Machine (JVM) 是执行 Java 程序的虚拟机的实现。

JVM 首先解析字节码。然后,它将类信息存储在存储区中。最后,它执行 java 编译器生成的字节码。

它是一台具有自己的指令集的抽象计算机,在运行时操纵各种内存区域。

JVM 的组件包括:

  • 类加载器
  • 运行时数据区域
  • 执行引擎

2.1.类加载器

JVM 的初始任务包括加载、验证和链接字节码。类加载器处理这些任务。

2.2. 运行时数据区

JVM 定义了多个内存区域来执行 Java 程序。这些内存在运行时使用,被称为运行时数据区。其中一些区域在 JVM 启动时创建,并在 JVM 退出时销毁,而另一些区域在创建线程时创建,在线程退出时销毁。

让我们逐一细看这些内存区:

方法区

基本上,方法区类似于编译代码的存储区。它存储运行时常量池、字段和方法数据、方法和构造函数的代码以及完全限定的类名等结构。JVM 为每个类存储这些结构。

方法区,也称为永久生成空间(PermGen),是在 JVM 启动时创建的。此区域的内存不需要是连续的。所有 JVM 线程共享此内存区域。

堆(Heap Area)

JVM 从该区域为所有类实例和数组分配内存。

垃圾回收器(GC)回收对象的堆内存。基本上,GC 有三个阶段从对象中回收内存,即两个次要 GC 和一个主要 GC。

堆内存有三个部分:

  • 伊甸园区(Eden Space) – 它是新生区(Young Generation space)的一部分。当我们创建对象时,JVM 从该区域中分配内存。
  • 幸存者区(Survivor Space) –它也是新生区(Young Generation space)的一部分。Survivor space 包含在 GC 的次要 GC 阶段幸存下来的现有对象。
  • 养老区(Tenured Space) – 它也被称为老一代空间(Old Generation space)。它持有长期幸存的对象。基本上,先为新生代(Young Generation)对象设置阈值,当达到此阈值时,这些对象将被移动到养老区。

JVM 一启动就创建堆区域。JVM 的所有线程都共享这个区域。堆区域的内存不需要是连续的。

栈(Stack area)

将数据存储为帧,每个帧存储局部变量、部分结果和嵌套方法调用。JVM 每次创建新线程时都会创建堆栈区域。此区域对每个线程都是私有的。

栈中的每个条目称为栈帧或激活记录。每个帧包含三个部分:

  • 局部变量数组 – 包含方法的所有局部变量和参数
  • 操作堆栈 - 用作存储中间计算结果的工作区
  • 帧数据 - 用于存储部分结果、方法的返回值以及对 Exception 表的引用,该表在发生异常时提供相应的 catch 块信息

JVM 栈的内存不需要是连续的。

PC 计数器

每个 JVM 线程都有一个单独的 PC 计数器,用于存储当前执行的指令的地址。如果当前执行的指令是本地方法(native method)的一部分,则此值未定义。

原生方法栈

本地方法是用 Java 以外的语言编写的方法。

JVM 提供了调用这些本地方法的能力。本地方法栈也称为 “C 栈”。它们存储本地方法信息。每当将本地方法编译为机器代码时,它们通常会使用本地方法堆栈来跟踪其状态。

JVM 每次创建新线程时都会创建这些堆栈。因此 JVM 线程不共享这个区域。

2.3. 执行引擎

执行引擎使用内存区域中存在的信息来执行指令。它有三个部分:

解释器(Interpreter)

一旦类加载器加载并验证字节码,解释器就会逐行执行字节码。这个执行过程相当缓慢。解释器的缺点是,当一个方法被多次调用时,每次都需要新的解释。

然而,JVM 使用 JIT 编译器来减轻这一缺点。

即时(JIT)编译器

JIT 编译器在运行时将常用方法的字节码编译为本机代码。因此,它负责 Java 程序的优化。

JVM 会自动监视正在执行的方法。一旦一个方法符合 JIT 编译的条件,它就会被安排编译成机器代码。这种方法被称为热方法。这种机器代码的编译发生在单独的 JVM 线程上。

因此,它不会中断当前程序的执行。编译成机器代码后,它运行得更快。

垃圾回收器

Java 使用垃圾回收来管理内存。这是一个查看堆内存的过程,识别哪些对象正在使用,哪些没有使用,最后删除未使用的对象。

GC 是一个守护进程线程。可以使用 System.gc() 方法显式调用它,但是,它不会立即执行,JVM 会决定何时调用 GC。

2.4. Java 本地接口

它充当 Java 代码和本机(C/C++)库之间的接口。

在某些情况下,仅靠 Java 无法满足应用的需求,例如,实现依赖于平台的功能。

在这些情况下,我们可以使用 JNI 使 JVM中运行的代码能够调用。反之,它使本地方法能够调用 JVM 中运行的代码。

2.5. 本地库

它们是特定于平台的库,包含本地方法的实现。

3. JRE

Java 运行时环境(JRE)是一组用于运行 Java 应用 的软件组件。

JRE 的核心组件包括:

  • 一个 Java 虚拟机 (JVM)的实现
  • 运行 Java 程序所需要的类
  • 属性文件

我们在上一节中讨论了 JVM。在这里,我们将重点介绍核心类和支持文件。

3.1. 引导(Bootstrap)类

我们可以在 jre/lib/ 下找到引导类。该路径也被视为引导的类路径(classpath)。它包括:

  • rt.jar 中的运行时类
  • i18n.jar 中的国际化类
  • charsets.jar 中的字符转换类
  • 其他

引导类加载器在 JVM 启动时加载这些类。

3.2. 扩展类

我们可以在 jre/lib/ext/ 中找到扩展类,它是 Java 平台扩展的目录。此路径也称为扩展的类路径(classpath)

它包含 jfxrt.jar 中的 JavaFX 运行时库,以及 localedata.jarjava.textjava.util 包的区域设置数据。用户还可以将自定义 jar 添加到此目录中

3.3. 属性设置

Java 平台使用这些属性设置来维护其配置。根据它们的使用情况,它们位于 /jre/lib/ 中的不同文件夹中。包括:

  • calendar.properties 中的日历配置
  • logging.properties 中的日志配置
  • net.properties 中的网络配置
  • /jre/lib/deploy/ 中的部署属性
  • /jre/lib/management/ 中的管理属性

3.4. 其他文件

除了上述文件和类,JRE 还包含其他事项的文件:

  • 安全管理位于 jre/lib/security
  • 放置 applet 支持类的目录位于 jre/lib/applet
  • 字体相关文件位于 jre/lib/fonts 等等

4. JDK

Java 开发工具包(Java Development Kit,JDK)为开发、编译、调试和执行 Java 程序提供了环境和工具

JDK 的核心组件包括:

  • JRE
  • 开发工具

前文中,我们讨论过了 JRE,此处不再赘述。

接下来,我们专注于各种开发工具。让我们先根据这些工具的用途对其进行分类:

4.1. 基础工具

这些工具为 JDK 奠定了基础,用于创建和构建 Java 应用。在这些工具中,我们可以找到用于编译、调试、归档、生成 Javadoc 等的工具

其中包括:

  • javac – 读取类和接口定义,并将它们编译到类文件中
  • java – 启动 Java 应用
  • javadoc – 从 Java 源文件中生成 API 文档的 HTML 页面
  • apt – 根据指定源文件集中的注解查找并执行注解处理器
  • appletviewer – 使我们能够在没有 web 浏览器的情况下运行 Java applet
  • jar – 将 Java applet 或应用打包到单个存档中
  • jdb – 命令行调试工具用来查找和修复 Java 应用中的 Bug
  • javah – 从 Java 类中生成 C 头文件和源文件
  • javap – 反汇编类文件并显示有关类文件中存在的字段、构造函数和方法的信息
  • extcheck – 检测目标 Java Archive (JAR) 文件和当前安装的扩展 JAR 文件之间的版本冲突

4.2. 安全工具

其中包括用于操纵 Java 密钥库的密钥和证书管理工具。

Java Keystore 是授权证书或公钥证书的容器。因此,它经常被基于 Java 的应用用于加密、身份验证和通过 HTTPS 提供服务。

此外,它们还有助于在我们的系统上设置安全策略,并创建可以在生产环境中在这些策略范围内工作的应用。包括:

  • keytool – 有助于管理密钥库条目,即加密密钥和证书
  • jarsigner – 使用密钥库信息生成数字签名的 JAR 文件
  • policytool –  使我们能够管理外部策略配置文件,该文件定义了安装的安全策略

一些安全工具也有助于管理 Kerberos Ticket。

Kerberos 是一种网络身份验证协议。

它基于票据工作,允许在非安全网络上通信的节点以安全的方式相互证明其身份:

  • kinit – 用于获取和缓存 Kerberos 票据授予票据(Kerberos ticket-granting ticket)
  • ktab – 管理密钥表中的 principle 名和密钥对
  • klist – 显示本地凭据缓存和密钥表中的条目

4.3. 国际化工具

国际化是设计应用程序使其能够适应各种语言和地区的过程,而无需对设计进行修改。

为此,JDK 带来了 native2ascii。此工具将包含 JRE 支持的字符的文件转换为 ASCII 或 Unicode 转义编码的文件。

4.4. 远程方法调用(RMI)工具

RMI 工具支持 Java 应用之间的远程通信,从而为分布式应用的开发提供了空间。

RMI 允许在一个 JVM 中运行的对象调用在另一个 JVM 上运行的对象的方法。这些工具包括

  • rmic – 使用 Java 远程方法协议(JRMP)或 InternetInter-Orb Protocol (IIOP)为远程对象生成桩代码(stub)、骨架(skeleton)和衔接(tie)类
  • rmiregistry – 创建并开启远程对象注册
  • rmid – 启动激活系统守护进程。这允许在 Java 虚拟机中注册和激活对象
  • serialver – 返回指定类的串行版本 UID

4.5. Java IDL 和 RMI-IIOP 工具

Java 接口定义语言(Interface Definition Language, IDL)为 Java 平台添加了基于通用对象请求代理架构(Common Object-Based Request Broker Architecture, CORBA)功能。

这些工具使分布式 Java web 应用能够使用行业标准对象管理组(Object Management Group, OMG) – IDL 调用远程网络服务上的操作。

同样,我们可以使用 Internet InterORB 协议(IIOP)。

RMI-IIOP,即 IIOP 上的 RMI,能够通过 RMI API 对 CORBA 服务器和应用进行编程。因此,能够通过互联网 InterORB 协议(IIOP)在用任何符合 CORBA 的语言编写的两个应用之间建立连接。

这些工具包括:

  • tnameserv – 瞬态命名服务(transient Naming Service),为对象引用提供树形结构的目录
  • idlj – IDL-to-Java 编译器,用于为指定的 IDL 文件生成 Java 绑定
  • orbd – 使客户端能够在 CORBA 环境中透明地定位和调用服务器上的持久化对象
  • servertool – 提供命令行界面,用于使用 ORB Daemon(orbd) 注册或注销持久化服务器,启动和关闭使用 ORB Daemon 注册的持久化服务器等

4.6. Java 部署工具

这些工具有助于在 web 上部署 Java 应用和小程序。其中包括:

  • pack200 – 使用 Java gzip 解压器将 JAR 文件转换成 pack200 文件
  • unpack200 – 将 pack200 文件转换成 JAR 文件

4.7. Java Plug-in 工具

JDK 为我们提供了 htmlconverter。此外,它还与 Java Plug-in 结合使用。

一方面,Java Plug-in 在流行浏览器和 Java 平台之间建立了连接。因为这种连接,网站上的 applet 小程序可以在浏览器中运行。

另一方面,htmlconverter 是一个工具程序,用于将包含 applet 的 HTML 页面转换为适合 Java Plug-in 的格式。

4.8. Java Web 启动工具

JDK 引入了 javaws我们可以将其与 Java Web Start 结合使用。

此工具允许我们从浏览器中单击一下即可下载和启动 Java 应用。因此,不需要运行任何安装过程。

4.9. 监控及管理工具

它们是监控 JVM 性能和资源消耗的好工具。以下是其中一些:

  • jconsole – 提供了一个图形控制台,可以用于监控和管理 Java 应用
  • jps – 列出目标系统上的 instrumented JVM
  • jstat – 监控 JVM 统计信息
  • jstatd – 监控 instrumented JVM 的创建和终止

4.10. 诊断工具

这是我们可以用于故障排除任务的实验工具

  • info – 为指定的 Java 进程生成配置信息
  • jmap – 打印指定进程的共享对象内存映射或堆内存详细信息
  • jsadebugd – 附加到 Java 进程并充当调试服务器
  • jstack – 打印给定 Java 进程的 Java 线程的 Java 堆栈跟踪

5. 小结

本文中,我们基于用途中识别了 JVM、JRE 和 JDK 之间的基本区别。

首先,我们描述了 JVM 是一台抽象计算机,以及它如何实际执行 Java 字节码。

然后,我们解释了如何使用 JRE 运行 Java 应用。

最后,我们了解了如何开发 Java 应用,我们使用 JDK。

我们还花了一些时间深入研究这些组件的工具和基本概念。