首页 > 编程语言 >Java虚拟机揭秘-底层驱动力,性能保障!

Java虚拟机揭秘-底层驱动力,性能保障!

时间:2024-05-26 09:59:13浏览次数:16  
标签:Java 字节 虚拟机 内存 JVM 执行 揭秘


Java虚拟机作为Java技术体系的核心组成部分,其重要性不言而喻。它不仅为Java提供了跨平台的能力,更是Java程序运行的基石。本文将为您深入解析Java虚拟机的工作原理、作用和应用场景,并通过生动的实例让您彻底理解这一关键技术。


一、Java虚拟机详细介绍


1、什么是Java虚拟机?

Java虚拟机(Java Virtual Machine,JVM)是一个虚拟的计算机,它有自己完善的硬件架构,能够运行Java字节码。JVM有着与硬件无关的指令集,屏蔽了与具体操作系统相关的信息,使得Java语言编译后的字节码可以在多种平台上运行。这正是Java"一次编写,处处运行"的根本所在。

JVM 并非只能运行Java语言,而是面向多语言的运行平台,只要遵守JVM的约定(只要编译成.class文件),就可以运行在JVM虚拟机上。

现在除了Java之外,比如scala、kotlin、groovy等语言都运行在JVM上。


在这里插入图片描述


2、Java虚拟机的作用

(1)、内存管理

自动内存管理是JVM最基本的作用之一。JVM通过自动分配和管理内存,防止了手动编码过程中可能出现的内存泄漏等问题。

(2)、跨平台能力

Java字节码可以在任何安装了JVM的设备上运行,这使得Java拥有了出色的跨平台能力,避免了操作系统、硬件的制约。

(3)、自动优化

现代JVM能够对热点代码进行运行时的动态优化,包括生成本地机器代码、内联编译等,大幅提升程序性能。

(4)、安全性

JVM对字节码进行了语法检查、类型检查和安全检查,并提供了沙箱防护机制,确保了Java程序的健壮与安全。


3、JVM、JDK、JRE 的区别与联系

JVM、JDK、JRE是Java开发和运行环境中的三个核心组件,它们之间有着紧密的关系:


(1)、JVM(Java Virtual Machine,Java虚拟机)

JVM是Java平台的核心组成部分,它是一个可以执行Java字节码的虚拟计算机。JVM提供了一个运行时环境,用于加载Java程序的类文件,执行其中的字节码,并管理程序的执行,如内存管理、垃圾回收等。JVM确保了Java的跨平台性,即所谓的“一次编写,到处运行”。


(2)、JRE(Java Runtime Environment,Java运行时环境)

JRE是运行Java应用程序所必需的环境。它包含了JVM以及Java核心类库和其它运行Java程序所需的资源。简单来说,JRE是JVM的宿主环境,它提供了JVM运行所需的所有资源。JRE不包含开发工具,因此它只适用于运行Java程序。


(3)、 JDK(Java Development Kit,Java开发工具包)

JDK是为Java开发者提供的一套工具包,它包含了JRE的所有内容,并且还包括了用于开发Java应用程序的工具和库。JDK中包含了javac编译器、调试器、jar工具、Java文档生成器(javadoc)等开发工具。此外,JDK还包含了一些用于Java性能测量和监控的工具,以及Java平台的源代码。


(4)、JDK、JRE和JVM的关系

  • JVM是JRE的一部分:JRE提供了JVM,使得Java程序可以在不同的平台上运行。

  • JDK包含了JRE:JDK中包含了一个完整的JRE,因此安装了JDK就不需要单独安装JRE。

  • JDK为开发者提供工具:JDK提供了编译器和调试器等开发工具,而JRE和JVM则提供了运行Java程序所需的环境。


(5)、应用场景

  • JRE适用于只运行Java程序的用户:如果只需要运行Java程序,不需要开发Java程序,那么安装JRE就足够了。
  • JDK适用于Java开发者:如果你是一名Java开发者,需要编写、编译和调试Java程序,那么需要安装JDK。

总之:

JVM作为核心,提供了字节码的执行环境;JRE作为JVM的宿主,提供了运行时所需的资源;JDK则为开发者提供了完整的开发和运行环境。


二、Java虚拟机的工作原理

Java虚拟机的工作过程可以概括为以下几个阶段:

在这里插入图片描述


1、编译阶段

首先 java经过javac编译成.class文件。

类的编译阶段主要是把源码文件编译成JVM可以解析的class文件,这个阶段会经过词法分析、语法语义分析、生成字节码,这个几个阶段后生成最后的class,用16进制的方式打开class文件后如图:

在这里插入图片描述


class 文件是Java编译器将Java源代码(.java 文件)编译后生成的字节码文件。这个文件是JVM执行引擎执行的基础。.class 文件包含了多种类型的数据,用于确保JVM能够正确地加载、链接和执行代码。以下是.class 文件中包含的主要内容:

  • 魔数(Magic Number)
    每个.class 文件的开始都是魔数,用于标识这个文件是否是一个有效的类文件。魔数是四个字节,值为 0xCAFEBABE

  • 版本信息
    包括主版本号和次版本号,用于确定这个.class 文件是为哪个版本的Java虚拟机编译的。

  • 常量池(Constant Pool)
    常量池用于存储编译期间生成的各种字面量和符号引用。这部分可能包括类和接口的名称、字段名、方法名、字符串常量等。

  • 访问标志(Access Flags)
    定义了这个类或接口的访问权限,如是否是公共的(public)、抽象的(abstract)等。

  • 类索引、父类索引
    类索引用于确定这个类的全名,父类索引指向常量池中的一个引用,表示这个类的直接父类。

  • 接口索引表(Interfaces)
    如果这个类实现了一个或多个接口,接口索引表将列出这些接口。

  • 字段表集合(Fields)
    字段表包含了类的字段信息,每个字段都有其名称、描述符、访问修饰符等。

  • 方法表集合(Methods)
    方法表包含了类的方法信息,包括方法的名称、返回类型、参数列表、局部变量表、字节码指令等。

  • 属性表集合(Attributes)
    属性表可以包含多种不同的属性,如代码(Code)属性包含了实际的字节码指令、行号表、异常处理表等;SourceFile属性表示源代码文件的名称;还有其他如Deprecated、Synthetic等属性。

  • 字节码(Bytecode)
    方法的字节码是JVM执行的指令集,定义了方法的行为。

  • 栈映射表(Stack Map Table)
    在较新的JVM版本中,栈映射表用于支持字节码的验证和JIT编译器的优化。

  • 异常表(Exception Table)
    如果方法中有try-catch块,异常表将列出可能抛出的异常类型和异常处理器。

这些组成部分共同定义了Java类的结构和行为,使得JVM能够加载和执行Java程序。开发者通常不会直接编辑.class 文件,而是通过Java源代码进行编程,并利用编译器生成这些字节码文件。


2、类加载阶段

Java 类加载过程是 Java 运行时环境中的一个关键特性,它涉及到将 Java 类(通常是 .class 文件)加载到 JVM(Java 虚拟机)中,并为类的实例化和执行做准备。类加载过程大致可以分为以下几个阶段:


(1)、加载(Loading)

加载是类加载过程的第一个阶段,在这个阶段,JVM 完成以下任务:

  • 通过类的全名查找 .class 文件。

  • 加载 .class 文件到内存中,通常使用二进制形式。

  • 通过解析常量池中的符号引用(如类名、方法名、字段名等),将它们转换为直接引用。


(2)、验证(Verification)

验证阶段确保加载的类信息是合法的、符合JVM规范的,不会危害虚拟机安全。验证包括以下几个方面:

  • 文件格式验证:确保 .class 文件符合Java类文件的规范。

  • 元数据验证:检查类的结构信息,如访问修饰符、类和接口的继承关系等。

  • 字节码验证:确保字节码的指令不会做出危害虚拟机安全的行为。

  • 符号引用验证:确保符号引用能正确解析。


(3)、准备(Preparation)

准备阶段是为类的静态变量分配内存,并设置初始值。这里的初始值指的是数据类型的零值,如:

  • 整数类型 int 的初始值为 0
  • 浮点类型 float 的初始值为 0.0
  • 引用类型(如类、接口、数组)的初始值为 null
  • boolean 类型的初始值为 false
  • char 类型的初始值为 \u0000(Unicode编码中的空字符)。

(4)、解析(Resolution)

解析阶段是将常量池中的符号引用转换为直接引用的过程。直接引用是指向内存中的直接地址。解析包括:

  • 类或接口的解析。

  • 字段的解析。

  • 方法的解析。

  • 接口方法的解析。

  • 字段和方法的访问权限的解析。


(5)、初始化(Initialization)

初始化是类加载的最后一个阶段,它包括:

  • 执行静态初始化块(使用 static 关键字声明的代码块)。
  • 为静态变量赋予正确的初始值。
  • 如果类具有超类(除了 Object 类之外),则先初始化其超类。

执行顺序和触发条件:

  • 类的加载过程是按照上面列出的顺序进行的,但并不是所有的类都会经过所有阶段。例如,如果一个类没有进行任何静态变量的赋值操作,那么它可能不会进入初始化阶段。
  • 类的加载是由某个特定的动作触发的,如 new 关键字实例化对象、访问静态字段、调用静态方法等。

注意事项:

  • 类加载器不仅仅加载 .class 文件,它还负责加载包和其他资源文件。
  • 类加载过程是线程安全的。
  • 类加载器的父子委派模型确保了Java核心库的安全性,防止核心库被随意替换。

通过以上各个阶段,JVM 确保了类的安全性、一致性和可用性,为类的执行提供了必要的准备和初始化工作。


3、解释和执行阶段

加载完成后,执行引擎负责解释和执行字节码指令。执行引擎主要由解释器和即时编译器(JIT)两部分组成。解释器逐条解释字节码,JIT则能够动态编译热点代码为优化后的本地机器码以提高执行效率。

执行引擎通常包括以下部分:

  • 字节码解释器:负责逐条解释执行字节码。

  • 即时编译器:负责将热点字节码编译为机器码。

  • 代码优化器:负责对编译后的机器码进行进一步的优化。

  • 代码缓存:存储编译后的机器码,供后续执行使用。


(1)、解释执行(Interpretation)

执行引擎的一种基本工作模式是解释执行。在这种模式下,执行引擎逐条读取字节码,并根据字节码执行相应的操作。这种方式类似于我们在学习编程时使用的解释器。

过程:

  • 第一步,字节码扫描:执行引擎从PC寄存器(程序计数器)指向的字节码指令开始执行。

  • 第二步,解释执行:对每条字节码进行解释,并执行其对应的操作。

  • 第三步,状态更新:更新操作数栈、局部变量表和程序计数器等状态。

  • 第四步,循环执行:继续扫描下一条字节码,直到方法执行完毕或遇到返回指令。


(2)、即时编译(Just-In-Time Compilation, JIT)

为了提高程序的执行效率,现代JVM通常采用即时编译技术。即时编译器将热点代码(频繁执行的代码)编译为本地机器码,以提高执行速度。

过程:

  • 第一步,代码选择:JIT编译器选择需要编译的热点代码,这些代码可能是被多次调用的方法或循环体。

  • 第二步,编译优化:JIT编译器将字节码编译为机器码,并进行优化,如分支预测、循环展开等。

  • 第三步,本地代码缓存:编译后的机器码被存储在执行引擎的代码缓存中,以便重复使用。

  • 第四步,执行本地代码:执行引擎直接执行编译后的机器码,而不是逐条解释字节码。


(3)、热点代码探测(HotSpot Code Detection)

为了确定哪些代码需要即时编译,JVM需要能够探测热点代码。这通常通过以下方式实现:

  • 计数器:为每个方法入口和循环回边设置计数器,统计执行次数。
  • 阈值设定:当计数器达到一定阈值时,认为该代码是热点代码,适合进行即时编译。

(4)、多版本优化(Multi-Versioning)

为了进一步优化性能,JVM可以为同一段代码生成多个版本,以适应不同的执行环境或条件。例如,基于处理器的CPU架构特点,生成优化的机器码。

JVM的执行引擎是实现Java程序高效运行的关键。通过解释执行和即时编译,执行引擎能够在保持跨平台特性的同时,提供接近本地编译语言的性能。随着JVM技术的发展,执行引擎的优化策略也在不断进步,以满足日益增长的性能需求。


4、内存管理阶段

Java虚拟机负责自动分配和管理堆、栈、方法区等运行时数据区域。其中垃圾收集器(GC)通过跟踪可达性分析,自动回收无用对象的内存空间。

JVM内存管理是确保Java程序高效运行的关键环节。JVM内存管理主要包括以下几个阶段:

(1)、类加载过程中的内存分配

当JVM开始加载一个类时,它会为这个类分配内存以存储类的元数据。这包括类的信息、字段、方法等。这个阶段的内存分配是在方法区(Method Area)进行的。


(2)、对象创建时的内存分配

当使用new关键字创建一个对象时,JVM会在堆(Heap)中为这个新对象分配内存。堆是JVM中用于存储对象实例和数组的部分。

过程:

  • 确定对象大小:JVM首先确定对象所需的内存大小。

  • 内存分配:在堆中找到足够大的连续空间分配给对象。

  • 对象头信息设置:设置对象头(Mark Word),包含对象的哈希码、GC信息、锁状态等。

  • 初始化:执行<init>方法,对对象进行初始化。


(3)、 运行时的内存管理

在程序运行期间,JVM需要不断管理内存的使用,包括局部变量的创建和销毁,以及对对象的访问。

过程:

  • 局部变量表:方法执行时,其局部变量会在栈(Stack)的局部变量表中分配空间。

  • 操作数栈:方法执行过程中,临时数据会在操作数栈中存取。

  • 引用访问:通过引用访问堆中的对象,进行读写操作。


(4)、垃圾收集(Garbage Collection, GC)

JVM周期性地执行垃圾收集,回收不再被引用的对象所占用的内存。

过程:

  • 可达性分析:从一系列的GC Roots开始,标记所有可达的对象。
  • 清理:清除未被标记的对象,回收内存。
  • 内存整理:可选地整理内存,减少碎片。

(5)、 内存分配和回收策略

JVM使用不同的算法和策略来优化内存的分配和回收。例如:

  • 复制算法:用于新生代,将内存分为一个eden区和两个survivor区,对象在这些区域之间复制。
  • 标记-清除算法:用于老年代,首先标记所有存活的对象,然后清除未被标记的对象。
  • 标记-清除-整理:在标记-清除的基础上,对内存进行整理,消除碎片。

(6)、内存分配的并发处理

为了避免在内存分配和回收过程中的Stop-The-World现象,JVM采用了并发处理机制:

  • 并发标记:在应用程序运行的同时进行对象的标记。
  • 并发清理:在应用程序运行的同时进行内存的清理。
  • 增量收集:将垃圾收集过程分步骤执行,减少GC暂停时间。

(7)、 内存溢出处理

当JVM的内存不足时,会抛出内存溢出异常。例如,堆内存不足时会抛出OutOfMemoryError

JVM内存管理是一个复杂的过程,涉及到内存的分配、垃圾收集、内存整理和并发处理等多个方面。通过有效的内存管理策略,JVM确保了Java程序的高效运行,同时提供了自动内存管理和垃圾回收功能,简化了内存管理的复杂性。了解JVM内存管理的工作原理对于Java开发者来说是非常重要的,它有助于我们编写更高效的程序,并在遇到性能问题时能够快速定位和解决。


// GC示例
public static void main(String[] args) {
   byte[] array = new byte[1024 * 1024 * 50]; //50MB数组
   array = null; // 数组不可访问
   System.gc(); // 触发GC
}

5、执行子系统阶段

JVM执行子系统不仅仅是执行字节码的组件,它还提供了与操作系统之间通信的能力,支持线程同步、文件I/O等操作。以下是执行子系统在这些方面的作用和实现方式:


(1)、线程管理

JVM执行子系统负责管理Java程序中的线程。每个线程在操作系统中都对应一个内核线程,JVM需要管理这些线程的创建、同步、调度和终止。

  • 线程创建

    • Java程序通过Thread类或Runnable接口启动新线程。JVM负责将这些线程映射到操作系统的线程。
  • 线程同步

    • JVM提供了同步机制,包括synchronized关键字和java.util.concurrent包中的并发工具,以确保线程安全。
  • 线程调度

    • JVM执行子系统与操作系统的调度器协作,管理线程的执行顺序和时间分配。


(2)、系统调用

JVM提供了对本地系统调用的接口,使得Java程序能够执行操作系统级别的操作。

JVM通过本地库接口(如Java Native Interface, JNI)允许Java代码调用本地方法,实现与操作系统的交互。


(3)、文件I/O 及操作

JVM执行子系统支持文件输入输出操作,使得Java程序能够读写文件。

I/O操作:

Java程序使用java.iojava.nio包中的类进行文件I/O操作。JVM负责将这些操作映射到对应的操作系统I/O调用。


(4)、网络通信

JVM支持网络通信,使得Java程序能够进行网络连接和数据传输。

网络I/O:

Java程序使用java.net包中的类进行网络编程。JVM负责网络连接的建立、数据的发送和接收。


(5)、 安全性

JVM执行子系统还负责执行安全策略,确保Java程序不会执行有害的系统操作。


(6)、安全管理器

JVM提供了安全管理器(java.lang.SecurityManager),可以监控和限制程序对系统资源的访问。


(7)、 性能监控与优化

JVM执行子系统还包括性能监控和优化工具,帮助开发者了解程序的运行情况,并进行性能调优。


JVM执行子系统是Java程序与操作系统之间的桥梁。它不仅负责执行字节码,还提供了线程管理、内存管理、系统调用、文件I/O、网络通信等能力,确保Java程序能够高效、安全地运行。通过JVM执行子系统,Java程序能够充分利用操作系统提供的资源和功能。


以上就是Java虚拟机的基本工作流程,体现了JVM为Java提供了一个安全、高效的运行环境。


三、Java虚拟机的应用场景


Java虚拟机为我们提供了无数的便利,可以在以下场景大显身手:
  • 跨平台应用开发

    JVM的存在使得Java可以运行于任何安装了虚拟机的平台,从而实现了跨平台的能力,如Android应用、嵌入式设备、云服务器等。

  • 大数据分析

    Hadoop、Spark等大数据处理框架都是建立在JVM之上,利用其高效的内存管理和多线程支持,极大提高了分布式计算能力。

  • 中间件服务

    Java的各种应用服务器中间件如WebLogic、WebSphere、Tomcat等,都是基于JVM构建的,发挥了JVM提供的跨平台、高效、安全等特性。


总之,Java虚拟机作为Java技术体系的根基,贯穿于Java应用的方方面面。了解JVM的工作原理和特性,将使我们能够编写出更加高效、可靠的程序。期待在下期与您分享更多关于JVM调优和故障排查的干货知识,敬请期待!


标签:Java,字节,虚拟机,内存,JVM,执行,揭秘
From: https://blog.csdn.net/lizhong2008/article/details/139200649

相关文章