无论你是否使用Java来开发程序,你可能已经在某个时刻听说过Java虚拟机(JVM)。
JVM是Java生态系统的核心,使得基于Java的软件程序能够遵循“一次编写,到处运行”的方法。你可以在一台机器上编写Java代码,然后使用JVM在任何其他机器上运行它。
JVM最初是为了支持Java而设计的。然而,随着时间的推移,许多其他语言如Scala、Kotlin和Groovy也被采用在Java平台上。所有这些语言统称为JVM语言。
在本文中,我们将了解更多关于JVM的信息,它是如何工作的,以及它由哪些不同组件组成。
什么是虚拟机?
在我们深入了解JVM之前,让我们重新审视虚拟机(VM)的概念。
虚拟机是物理计算机的虚拟表示。我们可以称虚拟机为来宾机器,而它运行的物理计算机为主机机器。
一台物理机器可以运行多个虚拟机,每个都有自己的操作系统和应用。这些虚拟机彼此隔离。
什么是Java虚拟机?
在像C和C++这样的编程语言中,代码首先被编译成特定平台的机器代码。这些语言被称为编译语言。
另一方面,在像JavaScript和Python这样的语言中,计算机直接执行指令而不需要编译它们。这些语言被称为解释型语言。
Java结合了这两种技术。Java代码首先被编译成字节码,生成一个类文件。然后,Java虚拟机为底层平台解释这个类文件。相同的类文件可以在任何版本的JVM上运行在任何平台和操作系统上。
类似于虚拟机,JVM在主机机器上创建了一个隔离空间。这个空间可以用来执行Java程序,而不管机器的平台或操作系统是什么。
Java虚拟机架构
JVM由三个不同的组件组成:
类加载、 运行时数据区、执行引擎
让我们更详细地看看它们。
类加载器
当你编译一个.java源文件时,它被转换成字节码作为一个.class文件。当你尝试在程序中使用这个类时,类加载器将其加载到主内存中。
通常首先加载到内存中的类是包含main()方法的类。
类加载过程有三个阶段:加载、链接和初始化。
加载
加载涉及获取具有特定名称的类或接口的字节码二进制表示,并从中生成原始类或接口。
Java中有三个内置的类加载器:
引导类加载器 - 这是根类加载器。它是扩展类加载器的超类,并加载标准Java包,如java.lang、java.net、java.util、java.io等。这些包位于rt.jar文件和其他核心库中,位于$JAVA_HOME/jre/lib目录中。 扩展类加载器 - 这是引导类加载器的子类,也是应用程序类加载器的超类。它加载标准Java库的扩展,位于$JAVA_HOME/jre/lib/ext目录中。 应用程序类加载器 - 这是最后一个类加载器,是扩展类加载器的子类。它加载类路径上的文件。默认情况下,类路径设置为应用程序的当前目录。类路径也可以通过添加-classpath或-cp命令行选项来修改。
JVM使用ClassLoader.loadClass()方法将类加载到内存中。它试图根据一个完全合格的名字来加载类。
如果父类加载器无法找到类,它会委托给子类加载器。如果最后一个子类加载器也无法加载类,它会抛出NoClassDefFoundError或ClassNotFoundException。
链接
类被加载到内存后,会经历链接过程。链接一个类或接口涉及到将程序的不同元素和依赖关系组合在一起。
链接包括以下步骤:
验证:这一阶段通过检查它是否符合一组约束或规则来检查.class文件的结构正确性。如果验证失败,我们会得到一个VerifyException。
例如,如果代码是用Java 11构建的,但运行在安装了Java 8的系统上,验证阶段将失败。
准备:在这一阶段,JVM为类或接口的静态字段分配内存,并用默认值初始化它们。
例如,假设你在类中声明了以下变量:
private static final boolean enabled = true;
在准备阶段,JVM为变量enabled分配内存,并将其值设置为布尔型的默认值,即false。
解析:在这一阶段,符号引用被替换为运行时常量池中的直接引用。
例如,如果你有对其他类或常量变量的引用,它们将在这一阶段被解析并替换为它们的实际引用。
初始化
初始化涉及到执行类或接口的初始化方法(称为)。这可以包括调用类的构造函数,执行静态块,以及为所有静态变量赋值。这是类加载的最后一个阶段。
例如,当我们之前声明了以下代码:
private static final boolean enabled = true;
在准备阶段,变量enabled被设置为布尔型的默认值false。在初始化阶段,这个这可以包括调用类的构造函数,执行静态块,以及为所有静态变量赋值。这是类加载的最后一个阶段。
本文暂时没有评论,来添加一个吧(●'◡'●)