JVM、JRE 和 JDK 之间的区别
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.jar
中 java.text
和 java.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。
我们还花了一些时间深入研究这些组件的工具和基本概念。