JVM概念及体系结构

JVM概念

JVM全称Java Virtual Machine,Java虚拟机,它是一种抽象的计算机,就像真正的计算机一样,它有自己的指令集,在执行指令运行程序时可以对各种内存区域进行操作。Java编程语言就是建立在Java虚拟机上的,JVM是Java的核心,它使得Java语言具有跨平台性的特性,屏蔽了操作系统的细节,真正实现程序代码一次编写,到处运行的效果。目前有不同厂商实现了各自的JVM,主要包括以下几种:

  1. Oracle HotSpot JVM:Oracle公司开发的JVM,是目前最常用的JVM之一,也是Java官方推荐的JVM之一。

  2. OpenJDK JVM:由Oracle公司主导的开源JVM项目,是Java官方的参考实现之一。

  3. IBM J9 JVM:由IBM公司开发的JVM,具有高性能和低内存占用等优点。

  4. Azul Zing JVM:由Azul Systems公司开发的JVM,专注于高性能、低延迟和可预测性等方面的优化。

  5. JRockit JVM:由BEA Systems公司开发的JVM,具有高性能和低内存占用等优点,目前已被Oracle公司收购。

  6. Excelsior JET JVM:由Excelsior公司开发的JVM,可以将Java程序编译成本地机器代码,提高程序的执行效率。

除了以上几种常见的JVM产品之外,还有一些其他的JVM实现,如GNU Classpath JVM、Dalvik JVM(用于Android系统)、Apache Harmony JVM等。

Oracle HotSpot JVM 体系结构

下面这张图描述的是目前最常用的JVM,Oracle HotSpot JVM的体系结构:

从图中我们可以看出,JVM主要由以下几个部分组成:

  1. 类加载器子系统:负责将编译后的Java类文件加载到JVM中,并生成对应的Java类对象。

  2. 运行时数据区:JVM中的内存区域,用于存储Java程序运行时的数据,包括方法区、堆、栈、程序计数器等。

  3. 执行引擎:主要包括负责解释执行Java程序中的字节码指令,将其转换为机器指令并执行。

  4. 本地方法接口:允许Java程序调用本地的C/C++等语言编写的方法。

  5. Java标准库:包含了Java语言中的核心类库,提供了大量的API供开发人员使用。

  6. JIT编译器:将Java程序中的热点代码(被频繁调用的代码)编译成本地机器指令,提高程序的执行效率。

  7. 解释器:它负责将Java字节码文件逐条解释并执行。

  8. 垃圾回收器:自动回收Java程序中不再使用的内存空间,避免内存泄漏和内存溢出等问题。

图中描述了JVM运行程序的过程:

首先,将编译好的应用程序class文件加载到类加载子系统,它会查找并验证class文件、进行内存空间的分配和对象赋值。

接着,当class文件加载到内存之后,由运行时数据区来完成数据数据存储和交换。运行时数据区又分为栈、堆、本地方法栈、程序计数器、方法区和运行时常量池。其中堆、方法区和运行时常量池是在JVM启动时创建的,程序计数器、栈和本地方法栈是与线程同时创建,在线程销毁后,会释放所分配的栈空间。另外,方法区和堆区是内存共享区域,对所有线程可见,也是程序员能够通过编写代码直接操作的内存区域,同时在这里也是最容易出现并发问题,是程序员写代码时需要特别注意之处。而栈、程序计数器和本地方法栈是JVM调度的内存区域。

然后,由执行引擎通过JIT编译器将class文件中的字节码指令翻译成操作系统能够执行的CPU指令,在执行过程中,垃圾回收器会回收堆中未被引用的垃圾对象,为程序运行腾出空闲空间。

最后,由JVM返回执行结果。

JVM 体系结构组成部分

程序计数器

程序计数器是一块较小的内存区域,可以把它看作是当前线程所执行的字节码的行号指示器。JVM支持多个执行线程。每个Java线程都有自己的程序计数器。在任何时候,每个Java 虚拟机线程都在执行单个方法的代码,即该线程的当前方法。如果该方法不是 native,则程序计数器包含当前正在执行的 Java 虚拟机指令的地址。如果线程当前正在执行的方法是native,则 Java 虚拟机的程序计数器的值是未定义的。

每个 Java 虚拟机线程都有一个私有的栈,与线程同时创建。Java 虚拟机堆栈存储帧。Java 虚拟机堆栈类似于 C 等常规语言的堆栈:它保存局部变量和部分结果,并在方法调用和返回中发挥作用。因为 Java 虚拟机堆栈从不直接操作,除了压入和弹出帧外,帧可能是堆分配的。Java 虚拟机堆栈的内存不需要是连续的。

Java 虚拟机可以为程序员或用户提供对堆栈初始大小的控制,以及在动态扩展或收缩 Java 虚拟机堆栈的情况下,对最大和最小大小的控制。JVM对栈大小的设置参数是 -Xss。
以下异常情况与Java 虚拟机堆栈相关:

  1. 如果线程中的计算需要比允许的更大的Java 虚拟机堆栈,JVM将抛出一个StackOverflowError.

  2. 如果Java 虚拟机堆栈可以动态扩展,并且尝试扩展但没有足够的内存可用于实现扩展,或者如果没有足够的内存可用于为新线程创建初始 Java 虚拟机堆栈,则JVM会抛出一个OutOfMemoryError.

Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的堆。堆是运行时数据区域,从中分配所有类实例和数组的内存。

堆是在虚拟机启动时创建的。对象的堆存储由垃圾收集器回收;对象永远不会显式释放。堆可以是固定大小的,也可以根据计算的需要进行扩展,如果不需要更大的堆,则可以收缩。堆的内存不需要是连续的。

Java 虚拟机实现可以让程序员或用户控制堆的初始大小,如果堆可以动态扩展或收缩,还可以控制最大和最小堆大小。JVM对堆大小的设置参数是-Xms和-Xmx。

以下异常情况与堆相关联:

  1. 如果计算需要的堆多于自动存储管理系统所能提供的堆,Java 虚拟机将抛出一个 OutOfMemoryError.

方法区

Java 虚拟机有一个在所有 Java 虚拟机线程之间共享的方法区。方法区类似于常规语言的编译代码的存储区,或者类似于操作系统进程中的“文本”段。它存储每个类的结构,例如运行时常量池、字段和方法数据,以及方法和构造函数的代码,包括类和实例初始化以及接口初始化中使用的特殊方法。

方法区是在虚拟机启动时创建的。尽管方法区在逻辑上是堆的一部分,但不会对其进行垃圾收集或压缩。方法区的大小可以是固定的,也可以根据计算的需要进行扩展,如果不需要更大的方法区,则可以缩小。方法区的内存不需要是连续的。

Java 虚拟机实现可以为程序员或用户提供对方法区初始大小的控制,以及在可变大小方法区的情况下,对最大和最小方法区大小的控制。JVM对方法区大小的设置参数是-XX:MetaspaceSize和 -XX:MaxMetaspaceSize

以下异常情况与方法区相关联:

  1. 如果方法区中的内存无法满足分配请求,Java 虚拟机将抛出一个OutOfMemoryError.

运行时常量池

运行时常量池它包含多种常量,从编译时已知的数字文字到必须在运行时解析的方法和字段引用。运行时常量池的功能类似于传统编程语言的符号表,尽管它包含的数据范围比典型的符号表更广泛。

每个运行时常量池都是从Java 虚拟机的方法区分配的。类或接口的运行时常量池是在Java 虚拟机 创建类或接口时构建的。

以下异常情况与类或接口的运行时常量池的构造有关:

  1. 在创建类或接口时,如果构建运行时常量池需要的内存多于Java 虚拟机方法区可用的内存,Java 虚拟机将抛出一个OutOfMemoryError.

本机方法栈

Java 虚拟机可以使用传统的堆栈,通俗地称为“C 堆栈”,以支持native方法(用 Java 编程语言以外的语言编写的方法)。Java 虚拟机指令集的解释器的实现也可以使用本机native 方法堆栈,例如 C。如果提供,本地方法堆栈通常在创建每个线程时按线程分配。

Java 虚拟机实现可以为程序员或用户提供对本地方法堆栈初始大小的控制,以及在可变大小的本地方法堆栈的情况下,对最大和最小方法堆栈大小的控制。JVM对方法区大小的设置参数是-Xoss

以下异常情况与本机方法堆栈相关联:

  1. 如果线程中的计算需要比允许的更大的本机方法堆栈,Java 虚拟机将抛出一个StackOverflowError.

  2. 如果本机方法堆栈可以动态扩展并且尝试本机方法堆栈扩展但可用内存不足,或者如果可用内存不足以为新线程创建初始本机方法堆栈,Java 虚拟机将抛出一个OutOfMemoryError.

执行引擎

执行引擎负责将Java字节码文件翻译成机器码并执行。它主要由解释器、即时编译器和垃圾回收器组成

解释器

解释器是JVM的执行引擎的核心部分,它负责将Java字节码文件逐条解释并执行。解释器每次只能解释一条指令,因此执行效率较低。但是解释器具有跨平台的优势,因为它可以在任何平台上运行。

即时编译器

即时编译器是JVM的执行引擎的重要组成部分,它可以将Java字节码文件翻译成本地机器码并执行。通俗地理解就是用来将字节码翻译成操作系统能够执行的CPU指令。即时编译器可以一次性将多条指令编译成本地机器码,因此执行效率较高。但是即时编译器需要根据不同的平台进行优化,因此不具有跨平台的优势。

JVM的执行引擎可以根据需要动态地选择使用解释器或即时编译器来执行Java字节码文件。具体来说,当程序刚开始运行时,JVM会使用解释器来逐条解释和执行Java字节码文件,以便能够快速地启动程序。但是随着程序的运行,执行引擎会监测程序的运行情况,并将热点代码(Hotspot Code)标记为需要优化的代码。热点代码是指程序中频繁执行的代码块,因为这些代码块的执行频率高,因此优化它们可以提高程序的执行效率。当JVM检测到热点代码时,它会使用即时编译器将这些代码块编译成本地机器码,并将编译后的代码缓存起来,以便下次执行时直接调用。这样可以避免重复解释和执行相同的代码,从而提高程序的执行效率。

我们也可以通过JVM参数来设置选择解释执行或者是编译执行。所谓解释执行就是直接将字节码作为源程序输入解释执行,不必等待编译器全部编译完成再执行,这样可以省去许多不必要的编译时间。而编译执行就是就是由编译程序将目标代码一次性编译成目标程序,再由机器运行,执行效率更高,占用内存资源也更小。在Hotspot的实现中默认是两种方式的组合。

垃圾回收器

垃圾回收器主要负责对运行时数据区的数据进行管理和回收,其实就是对各种垃圾回收算法的实现,总体来说有三种核心算法,分别是复制算法、标记清除算法和标记整理算法,这些算法的选择呢,我们可以通过JVM参数来设置。

本地方法接口

本地方法接口也就是JNI技术。在我这的这篇文章中提到过《从JDK源码级深入剖析main方法的运行机制》,JNI全称(Java Native Interface,java本地接口)是Java平台提供的一种编程框架,用于实现Java应用程序与本地(非Java)应用程序之间的相互调用。JNI提供了一组规范和工具,使Java应用程序能够调用C、C++和其他本地语言编写的代码,反之亦然。通过JNI,Java程序员可以利用现有的本地代码库,并与其他本地应用程序进行交互,从而扩展Java应用程序的功能和性能。

类加载子系统

在我的这篇文章中描述了其原理《从源码级剖析Java类加载原理》,JVM的类加载子系统主要负责将类的class文件加载到JVM中,并将其转换为可以被JVM执行的代码。它主要由以下三部分组成:

  1. 类加载器(ClassLoader):类加载器负责将类的字节码文件加载到JVM中,并生成对应的Java类。JVM中有三个内置的类加载器:启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)和应用程序类加载器(Application ClassLoader)。

  2. 连接器(Linker):连接器主要负责将类加载器生成的Java类与JVM中的其他类进行连接,生成可以被JVM执行的代码。连接器主要包括验证、准备和解析三个阶段。

  3. 初始化器(Initializer):初始化器主要负责执行Java类中的静态代码块和静态变量初始化,将Java类初始化为可用状态。
    类加载子系统是JVM中非常重要的一部分,可以保证Java程序的正确性和安全性。在Java程序运行时,JVM会根据需要动态地加载和卸载类,从而实现Java程序的灵活性和扩展性。

Java标准库

Java标准库,其主要是rt.jar包,rt.jar是Java Runtime Environment(JRE)的一部分,包含了Java标准库中的所有类和接口,是Java程序运行时必须加载的核心库之一。除了rt.jar之外,Java标准库还包括了以下几个jar包:

  1. jce.jar:Java Cryptography Extension(JCE)扩展库,提供了加密和解密算法的支持。

  2. jndi.jar:Java Naming and Directory Interface(JNDI)库,提供了访问命名和目录服务的API。

  3. jmx.jar:Java Management Extensions(JMX)库,提供了管理Java应用程序的API。

  4. jms.jar:Java Message Service(JMS)库,提供了异步消息传递的API。

  5. jaxp.jar:Java API for XML Processing(JAXP)库,提供了处理XML数据的API。

  6. sa-jdi.jar:Serviceability Agent(SA)库,提供了JVM监控和调试的API。

这些jar包都是Java标准库的一部分,提供了各种不同的功能和工具,方便开发人员开发各种类型的Java应用程序。其中rt.jar和jce.jar是引导类加载器BootstrapLoader必须加载的文件。

posted @ 2023-06-18 12:17  编程老司机A  阅读(109)  评论(0编辑  收藏  举报