JVM 基础知识
JVM(Java虚拟机)基础知识
什么是JVM?
JVM,全称Java虚拟机(Java Virtual Machine),是Java编程语言的运行环境。它是一个虚拟的计算机,可以在不同的操作系统上运行Java程序,实现了Java的跨平台特性。JVM负责将Java字节码(由Java源代码编译而成)解释或编译为特定平台的机器指令,从而实现Java程序的执行。
JVM 的主要功能是提供内存管理、垃圾回收、线程管理和安全等机制,以确保 Java 应用程序在不同的平台上具有可移植性和安全性。
JVM 的工作原理
JVM 的工作原理可以分为以下几个步骤:
- 编译:Java 源代码通过 Java 编译器(
javac
)将其转换为字节码文件(以.class
扩展名结尾)。字节码是一种中间形式的代码,类似于汇编语言,但并不直接运行在物理硬件上。
- 编译:Java 源代码通过 Java 编译器(
- 类加载:JVM 的类加载器负责将字节码文件加载到内存中,并将其转换为运行时数据结构(类对象)。类加载器根据需要动态加载类,解析类之间的依赖关系,并执行必要的验证步骤。
- 字节码解释与执行:JVM 内置的解释器将字节码逐行解释并执行。解释器将字节码转换为机器指令并执行,这种方式灵活但相对较慢。
- 即时编译:为了提高执行效率,JVM 还使用即时编译器(JIT)来将热点代码(经常执行的代码)编译成本地机器码。JIT 编译器分析运行时的程序行为,并根据需要将字节码转换为机器码,以提高执行速度。
- 内存管理与垃圾回收:JVM 负责动态分配和管理内存。它将内存划分为不同的区域,如堆(Heap)、栈(Stack)、方法区(Method Area)等。堆用于存储对象实例,栈用于存储方法调用和局部变量,方法区用于存储类信息和常量池。同时,JVM 还具备自动垃圾回收机制,通过标记-清除、复制、标记-整理等算法来回收不再使用的对象。
JVM的优点和限制
JVM作为Java的核心组件,具有以下优点:
跨平台性:Java程序只需在目标平台上安装符合该平台的JVM,就可以运行。这种跨平台性大大简化了软件的开发和部署。
自动内存管理:JVM负责自动分配和释放内存,开发者无需手动管理内存。JVM的垃圾回收机制可以自动检测不再使用的对象,并进行垃圾回收,减轻了开发者的负担,同时提高了应用程序的稳定性。
安全性:JVM提供了安全管理机制,可以控制Java程序的访问权限和资源使用。Java程序在JVM上运行时受到严格的安全限制,不允许越权访问和执行危险操作。
然而,JVM也有一些限制:
性能:JVM在将字节码转换为机器指令的过程中引入了一定的性能开销,相比原生的编译语言可能会有一些性能损失。虽然JVM的执行引擎和即时编译器(Just-In-Time Compiler)可以提高执行效率,但仍无法完全消除性能差距。
资源消耗:JVM在运行时需要占用一定的内存和计算资源。尤其是对于较大的Java应用程序,可能需要调整JVM的内存配置来满足需求,否则可能导致内存不足或性能下降。
关键部分讲解
类加载机制
类加载机制是JVM的核心功能之一,它负责将类的字节码加载到内存中,并进行初始化和连接操作。
类加载机制包括以下几个步骤:
加载(Loading):类加载的第一步是查找并加载类的字节码文件。字节码文件通常存储在磁盘或网络上。类加载器根据类的全限定名找到相应的字节码文件,并将其加载到内存中。
验证(Verification):在验证阶段,JVM会对加载的字节码进行验证,以确保其符合Java虚拟机规范。验证过程包括文件格式验证、元数据验证、字节码验证和符号引用验证等。
准备(Preparation):准备阶段是为类的静态变量分配内存空间,并设置默认初始值。这些静态变量通常在类加载完成后,且在初始化阶段之前进行准备。
解析(Resolution):解析阶段是将符号引用转换为直接引用的过程。符号引用是一种编译时的引用,直接引用是在执行时可以直接使用的引用。
初始化(Initialization):初始化阶段是类加载的最后一步,JVM会执行类的初始化代码,包括静态变量赋值和静态代码块的执行。只有在真正使用一个类时,JVM才会触发其初始化。
类加载器(ClassLoader)是负责加载类的关键组件,JVM内置了三个主要的类加载器:
启动类加载器(Bootstrap ClassLoader):负责加载Java核心类库,如
java.lang
包中的类。扩展类加载器(Extension ClassLoader):负责加载Java扩展类库,位于
jre/lib/ext
目录下的JAR包。应用程序类加载器(Application ClassLoader):也称为系统类加载器,负责加载应用程序的类,位于类路径(Classpath)下。
类加载器之间形成了一个层次结构,称为双亲委派模型。当需要加载一个类时,首先由启动类加载器尝试加载,如果无法找到,才会委托给父类加载器加载。这样可以实现类的隔离和共享,避免类的重复加载。
内存模型
JVM的内存模型定义了Java程序在运行时所使用的内存结构,包括方法区、堆、栈、程序计数器和本地方法栈等。每个线程都有自己的栈和程序计数器,而方法区和堆是线程共享的。
方法区:方法区用于存储类的元数据信息,包括类的结构、常量池、字段和方法信息等。方法区也被称为永久代(Permanent Generation),在Java 8及之前的版本中,它是使用永久代来实现的。从Java 8开始,永久代被元空间(Metaspace)所取代。
堆:堆是Java程序运行时的主要内存区域,用于存储对象实例。所有通过
new
关键字创建的对象都存储在堆中。堆被划分为年轻代(Young Generation)、老年代(Old Generation)和持久代(Perm Generation,Java 7及之前版本)/元空间(Metaspace,Java 8及之后版本)等不同的区域。年轻代:年轻代用于存储新创建的对象。它又被划分为一个Eden空间和两个Survivor空间(通常称为From和To空间)。新创建的对象首先分配在Eden空间,经过一次垃圾回收后,仍然存活的对象会被复制到Survivor空间。经过多次垃圾回收后,仍然存活的对象会被晋升到老年代。
老年代:老年代用于存储长时间存活的对象。当一个对象在年轻代经过多次垃圾回收后仍然存活,它会被晋升到老年代。老年代的垃圾回收频率比年轻代低,因为老年代的对象一般存活时间较长。
持久代/元空间:持久代(Java 7及之前版本)用于存储类的元数据信息和常量池等。而元空间(Java 8及之后版本)是一种与堆独立的本地内存,用于存储类的元数据信息。
栈:栈用于存储线程执行方法的局部变量、操作数栈、动态链接、方法出口等信息。每个线程在执行一个方法时都会创建一个栈帧,栈帧用于存储方法的局部变量和操作数栈等数据。
程序计数器:程序计数器是一块较小的内存空间,用于存储当前线程正在执行的字节码指令的地址或索引。在多线程环境下,每个线程都有自己独立的程序计数器。
本地方法栈:本地方法栈用于存储Java程序调用本地(非Java)方法时的相关信息。
垃圾回收
Java虚拟机提供了自动的内存管理机制,通过垃圾回收(Garbage Collection,GC)自动释放不再使用的对象所占用的内存空间,避免了手动释放内存的麻烦。
垃圾回收的基本原理是通过判断对象的可达性来确定是否可以被回收。当一个对象不再被任何活动对象引用时,它就成为垃圾,可以被回收。
JVM中的垃圾回收主要基于以下两个策略:
标记-清除(Mark and Sweep):标记-清除是最基本的垃圾回收算法。它通过标记所有活动对象,然后清除所有未标记的对象来进行垃圾回收。但是,标记-清除算法存在内存碎片化的问题,会导致大对象无法找到连续的内存空间而触发额外的内存分配和垃圾回收操作。
复制(Copying):复制算法将堆分为两个区域,一半是活动对象存放的区域,一半是空闲的区域。当进行垃圾回收时,只将活动对象复制到空闲区域,然后清除原有的活动对象区域。复制算法避免了内存碎片化的问题,但会浪费一部分内存。
JVM还使用了其他高级的垃圾回收算法和技术,如分代收集(Generational Collection)、标记-整理(Mark and Compact)、增量式垃圾回收等。这些算法和技术可以根据对象的存活时间和特性进行灵活的垃圾回收,以提高回收效率和减少停顿时间。
即时编译器
即时编译器(Just-In-Time Compiler,JIT)是JVM中的一个重要组件,它将热点代码(Hot Spot)编译成本地机器代码,以提高程序的执行效率。
在Java程序执行过程中,JVM会先解释执行字节码,称为解释模式。解释模式下,执行效率相对较低,因为每次执行都需要解释字节码。
为了提高执行效率,JIT编译器将频繁执行的热点代码编译成本地机器代码,称为编译模式。编译模式下,执行热点代码时直接执行本地机器代码,避免了解释的过程,从而提高了执行速度。
JIT编译器使用了即时编译的策略,根据运行时的性能数据动态地选择需要编译的代码,以及采用何种优化策略进行编译。
JIT编译器还使用了一些优化技术,例如内联(Inlining)、逃逸分析(Escape Analysis)、常量折叠(Constant Folding)等。这些技术可以进一步优化编译后的代码,提高执行效率。
通过使用JIT编译器,Java程序可以在运行时获得接近原生代码的性能,同时还具有平台无关性和动态性的优势。
总结
JVM作为Java的核心组件,扮演着将Java程序转换为机器指令并执行的重要角色。它通过虚拟化和自动内存管理等特性,为Java程序提供了跨平台性、安全性和便利性。通过了解JVM的基础知识和进行适当的调优,开发者可以充分发挥Java的优势,提高应用程序的性能和效率。