Java中的JVM(Java Virtual Machine,Java虚拟机)是一个抽象的计算机,它提供了一个运行时环境来执行Java字节码。JVM的结构清晰且复杂,主要包括以下几个关键组件:
1. 类加载器(Class Loader)
功能:负责将Java类文件加载到内存中,并生成对应的Class对象。它是Java实现动态性和灵活性的关键之一。
加载过程:包括加载(Loading)、链接(Linking,分为验证、准备和解析三个阶段)和初始化(Initialization)三个阶段。
2. 运行时数据区域(Runtime Data Area)
这是JVM在执行Java程序时用于存储各种数据的内存区域,主要包括:
方法区(Method Area,在Java 8及以后被元空间取代):存储已被虚拟机加载的类的信息、常量、静态变量、即时编译器编译后的代码等数据,是线程共享的。
- 字符串常量池:Java中用于存储字符串字面量的一个特殊区域,它位于方法区(在Java 8及之前的版本)或元空间(在Java 8及之后的版本)中。
本地方法栈(Native Method Stack):为Java虚拟机使用到的Native方法服务,与Java虚拟机栈类似,但用于执行本地方法,也是线程私有的。
虚拟机栈(Java Virtual Machine Stacks):每个Java方法在执行时都会创建一个栈帧(Stack Frame),用于存储局部变量表、操作数栈、动态链接、方法出口等信息,是线程私有的。
-
栈帧内部名词解释
-
局部变量表(Local Variable Table)
局部变量表用于存储方法的局部变量和参数。这些变量在方法执行期间有效,并在方法结束时被销毁。局部变量表的大小在编译期间就确定了,因此在方法调用时会分配足够的空间来存储这些变量。每个局部变量占用一个或多个变量槽(Variable Slot),具体取决于变量的数据类型。例如,int、float等32位以内的数据类型占用一个槽,而long、double等64位的数据类型占用两个槽。 -
操作数栈(Operand Stack)
操作数栈用于存放方法执行过程中的操作数和中间结果。它是一个后进先出(LIFO)的数据结构,用于支持方法的计算过程。在方法执行时,指令会从操作数栈中取出操作数,执行计算、赋值等操作,然后将结果再次存入操作数栈中。每个栈帧都有一个独立的操作数栈,多个栈帧之间不共享操作数栈。 -
动态链接(Dynamic Linking)
动态链接用于存储方法调用过程中的符号引用和直接引用的关系。在Java中,方法的调用是通过符号引用来表示的,这些符号引用在类加载期间被解析为直接引用。动态链接就是在运行时将这些符号引用转换为直接引用的过程。它支持方法的动态调用和多态性。 -
方法返回地址(Return Address)
方法返回地址用于存储方法执行完成后需要返回到的调用者的地址。当方法执行完毕后,JVM会根据这个地址将控制权返回给调用者。这是实现方法调用的重要机制之一。 -
附加信息(Additional Information)
除了上述主要组成部分外,栈帧还可能包含一些附加信息,如方法调用者的信息、方法的访问权限、异常处理表等。这些信息用于支持方法的执行和异常处理。
Java堆(Java Heap):存储Java对象实例和数组,是Java程序运行时的动态内存区域,也是垃圾收集器管理的主要区域。堆通常分为新生代、老年代和永久代(在Java 8及以后被元空间取代)。
- 运行时常量池(Runtime Constant Pool):每个类或接口在编译时生成一个对应的常量池表,在运行时被加载到方法区的运行时常量池中。在Java 8及以后,运行时常量池被移到了堆中。
程序计数器(Program Counter Register):存储当前线程执行的字节码指令地址,是线程私有的。
直接内存(Direct Memory):通过NIO库可以直接使用的内存,不受Java堆的限制,也不受垃圾回收的管理。
3. 执行引擎(Execution Engine)
功能:负责执行字节码,包括解释器和即时编译器(JIT)等。解释器逐条解释执行字节码,而JIT则可以将热点代码编译成机器码以提高执行效率。
4. 垃圾回收器(Garbage Collector, GC)
功能:自动管理内存,回收不再使用的对象,释放内存空间。JVM提供了多种垃圾回收算法和回收器,如标记-清除、复制、标记-整理以及分代收集等,以适应不同的应用场景和需求。
Minor GC默认处理机制
Minor GC主要处理新生代(Young Generation)中的垃圾回收。新生代通常包括Eden区和两个Survivor区(Survivor From和Survivor To)。以下是Minor GC默认的处理机制:
- 标记复制算法:
Minor GC通常使用标记复制算法。该算法首先标记出新生代中存活的对象,然后将这些对象复制到一个空闲的Survivor区或老年代中。
复制过程完成后,原来的Eden区和Survivor区中的对象将被清空,用于下一次对象的分配。
垃圾回收器选择:
不同的JVM实现和版本可能选择不同的垃圾回收器作为Minor GC的默认实现。
例如,在Java 8之前的HotSpot JVM中,Serial和Parallel垃圾回收器是常用的Minor GC实现。
在Java 8及之后的版本中,G1(Garbage-First)垃圾回收器成为了一种流行的选择,因为它提供了更好的性能和可预测性。
Full GC默认处理机制
Full GC处理整个堆内存(包括新生代和老年代)以及方法区(在Java 8之前为永久代PermGen,之后为元空间Metaspace)中的垃圾回收。以下是Full GC默认的处理机制:
- 标记清除或标记整理算法:
Full GC通常使用标记清除或标记整理算法。标记清除算法首先标记出堆内存中的存活对象,然后清除未标记的对象。
标记整理算法在标记存活对象后,还会对存活对象进行整理,以消除内存碎片。
需要注意的是,不同的垃圾回收器可能会选择不同的算法。例如,CMS(Concurrent Mark-Sweep)垃圾回收器使用标记清除算法,而G1垃圾回收器则使用标记整理算法。
垃圾回收器选择:
与Minor GC类似,Full GC的默认实现也取决于JVM的具体实现和版本。
在Java 8之前的HotSpot JVM中,CMS垃圾回收器是一种流行的Full GC实现,因为它旨在减少停顿时间。
在Java 8及之后的版本中,G1垃圾回收器成为了默认的Full GC实现,因为它提供了更好的性能和可预测性。
触发条件:
Full GC的触发条件包括老年代空间不足、方法区空间不足、显式调用System.gc()方法(尽管这只是一个建议,JVM不一定会立即执行Full GC)等。
当触发Full GC时,JVM会暂停所有应用程序线程(Stop-the-World),以对整个堆内存进行垃圾回收。
5. Java中常见垃圾收集器的执行过程
-
Serial收集器
Serial收集器是一个单线程的垃圾收集器,它在进行垃圾收集时,会暂停所有的应用线程(Stop-The-World,简称STW),直到垃圾收集工作完成。这个过程主要分为以下几个阶段:
标记阶段:从GC Roots出发,标记所有可达的对象。
清除阶段:遍历堆内存,清除所有未标记的对象。
由于Serial收集器是单线程的,因此它的垃圾收集效率相对较低,适用于内存较小、对性能要求不高的应用。 -
Parallel收集器
Parallel收集器是一个多线程的垃圾收集器,它使用多个线程并行地进行垃圾收集,以提高垃圾收集的效率。Parallel收集器的执行过程与Serial收集器类似,但它是多线程并行的。
年轻代收集:使用并行复制算法,将存活的对象从一个Survivor区复制到另一个Survivor区或老年代。
老年代收集:使用并行标记-压缩算法,标记所有可达的对象,并压缩堆内存中的空闲空间。
Parallel收集器适用于多线程、对吞吐量有较高要求的应用。 -
CMS(Concurrent Mark Sweep)收集器
CMS收集器是一个以最小化停顿时间为目标的垃圾收集器。它采用并发标记和并发清除的方式进行垃圾回收,以减少应用程序的停顿时间。
CMS收集器的执行过程如下:
初始标记阶段:STW,标记GC Roots直接可达的对象。 并发标记阶段:与应用线程并发执行,标记所有可达的对象。
重新标记阶段:STW,处理并发标记阶段产生的所有新引用,并标记这些新引用的对象。 并发清除阶段:与应用线程并发执行,清除所有未标记的对象。
CMS收集器的优点在于能够显著降低应用程序的停顿时间,但缺点是可能会占用较多的CPU资源,并且可能会产生内存碎片。 -
G1(Garbage-First)收集器
G1收集器是一个面向大内存、需要低停顿时间的应用的垃圾收集器。它将堆内存划分为多个大小相等的区域(Region),并优先处理垃圾最多的区域。G1收集器的执行过程如下:
并发标记阶段:与应用线程并发执行,标记所有可达的对象,并计算每个区域的垃圾比例。
混合回收阶段:根据设置的停顿时间和垃圾比例,选择部分年轻代区域和部分老年代区域进行回收。回收时,将存活的对象复制到其他区域,并更新指向已回收区域的对象引用。
并发整理阶段:在必要时,对老年代进行并发整理,以减少内存碎片。
G1收集器的优点在于能够动态调整堆内存的使用,降低停顿时间,并提供可预测的垃圾回收性能。但缺点是配置和调优相对复杂。 -
ZGC和Shenandoah收集器
ZGC和Shenandoah收集器是Java后续版本中引入的低延迟垃圾收集器。它们采用了一些先进的技术,如染色指针、内存多重映射等,以实现极低的停顿时间。
ZGC收集器
ZGC收集器基于Region内存布局,不设分代,使用读屏障、染色指针和内存多重映射等技术来实现标记-整理算法。它的停顿时间可以控制在几毫秒以内。
Shenandoah收集器
(注意:在某些版本的JVM中可能已经被移除或替换为其他收集器)
Shenandoah收集器也采用了类似的并发标记和整理技术,旨在提供低延迟的垃圾回收性能。