JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介

Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以在多种平台上不加修改地运行‌。

1.1 JVM 的基本组成

JVM 主要由以下几个部分组成:

类加载器‌:负责将 .class 文件加载到内存中。
‌执行引擎‌:负责解释执行字节码或通过即时编译(JIT)将其转换为机器码。
‌运行时数据区‌:包括堆、栈、方法区和程序计数器等,用于存储程序运行时的数据和状态。
‌本地接口‌:用于融合不同的编程语言,虽然使用较少,但在需要与硬件交互时仍然重要‌。

1.2 JVM 的运行流程

编译‌:Java 源代码通过 javac 编译器编译成字节码(.class文件)。
‌类加载‌:类加载器将字节码文件加载到 JVM 的内存中。
‌执行‌:执行引擎解释或编译字节码,并提交操作系统执行。
‌垃圾回收‌:自动管理内存,防止内存泄漏和溢出‌。

1.3 JVM 的跨平台特性

JVM 的跨平台特性使得 Java 程序可以在任何支持 JVM 的操作系统上运行,实现了“一次编写,到处运行”(Write Once, Run Anywhere, WORA)的目标。这是通过 JVM 屏蔽底层硬件和操作系统的差异,提供统一的字节码规范来实现的‌。

2、JVM 的内存结构

JVM 在执行 Java 程序的时候,为了便于管理,会把它所管理的内存划分为多个不同区域。

JVM 的内存结果如下图:

2.1 字节码文件(class 文件)

字节码文件(class 文件)是 Java 程序编译后生成的中间代码,这些中间代码将会被 JVM 解析并执行。字节码文件是 Java 源代码(.java)编译后生成的中间代码文件(.class),采用二进制格式存储,包含 JVM 可执行的指令集。与机器码不同,字节码是平台无关的中间表示,需由 JVM 解释或即时编译(JIT)为机器码执行。

2.2 类加载器(ClassLoader)

类加载器子系统负责把 class 文件转载到内存中,供虚拟机执行。

2.3 方法区(Method Area)

方法区用来存储被虚拟机加载的类信息、常量、静态变量、编译器编译后的代码等数据。在类加载器加载 class 文件的时候,这些信息将会被提起出来,并存储到方法区中。由于这个区域是所有线程共享的区域,因此,它被设计为线程安全的。方法区可以被看出 JVM 的一个规范,在 HotSpor 中,方法区是用 Perm 区来实现的方法区。

2.4 ‌堆(Heap)

堆是虚拟机启动的时候创建的被所有线程共享的区域。这块区域只要用来存储对象的实例,通过 new 操作创建出来的对象的实例都存储在堆空间中,因此,堆就成为垃圾回收器管理的重点区域。

2.5 程序计数器(Program Counter Register)

程序计数器用于记录当前线程执行字节码的指令地址(行号指示器),确保线程切换后恢复执行位置。程序计数器也是线程私有的资源,JVM 会给每个线程创建单独的程序计数器。它可以被看作是当前线程执行的字节码的行号提示器。解释器的工作原理就是通过改变这个计数器的值来确定下一条需要被执行的字节码指令,程序控制的流程(循环、分支、异常处理、程序恢复)都是通过这个计数器来完成的。

2.6 ‌虚拟机栈(Java Virtual Machine Stack)

 ‌虚拟机栈‌的作用‌是存储方法执行时的栈帧(局部变量表、操作数栈、动态链接、方法返回地址)。 ‌虚拟机栈是线程私有的区域,每当有新的线程创建时,就会给它分配一个栈空间,当线程结束后,栈空间就会被回收,因此,栈与线程拥有相同的生命周期。栈主要用来实现 Java 语言中方法的调用与执行,每个方法在被执行的时候,都会创建一个栈帧用来存储这个方法的局部变量、操作数栈、动态链接、方法返回地址等信息。当进行方法调用时,通过压栈与弹栈操作进行栈空间的分配与释放。当一个方法被调用的时候,会压入一个新的栈帧到这个线程的栈中,当方法调用结束后,就会弹出这个栈帧,从而回收掉调用这个方法使用的栈空间。

2.7 ‌本地方法栈(Native Method Stack)

本地方法栈与虚拟机栈的作用是相似的,唯一不同的是虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的 Native(本地)方法服务。Native(本地)方法接口都会使用某种本地方法栈,当线程调用 Java 方法时,JVM 会创建一个新的栈帧并压入虚拟机栈。然而当它调用的是本地方法时,虚拟机栈保持不变,不会在线程的虚拟机栈中压入新的帧,而是简单地动态链接并直接调用指定的本地方法。如果某个虚拟机实现的本地方法接口使用的是 C++ 连接模型,那么它的本地方法栈就是 C++ 栈。

2.8 执行引擎

执行引擎是将字节码指令翻译为当前平台的本地机器指令,确保 Java 程序跨平台运行。充当“翻译器”角色,衔接字节码与底层操作系统与硬件。

2.9 垃圾回收器

垃圾回收器的主要作用是回收程序中不再使用的内存。

3、JVM 的堆空间

JVM 管理的最大内存区域,存储‌几乎所有对象实例和数组‌。堆是虚拟机启动的时候创建的被所有线程共享的区域。这块区域只要用来存储对象的实例,通过 new 操作创建出来的对象的实例都存储在堆空间中,因此,堆就成为垃圾回收器管理的重点区域。

堆空间在 JDK 8 之前与之后的结果如下图:

3.1 新生代(Young Generation)

‌作用‌:存放‌新创建的对象‌(约98%对象在此短暂存活)。
分区结构‌:Eden 区‌对象分配首选区域(占新生代80%)。Survivor区‌(S0/S1)保存 Minor GC 后存活的对象,通过复制算法避免碎片。
晋升机制‌:对象每经历一次Minor GC年龄+1,达到阈值(默认15次)则晋升至老年代。
垃圾回收‌:Minor GC‌:仅清理新生代,频率高、速度快。
调优参数‌:

-XX:NewRatio=2(老年代:新生代=2:1)
-XX:SurvivorRatio=8(Eden/S1=8:1:1)

3.2 老年代(Old Generation)‌

作用‌:存放‌长期存活对象‌(如缓存、单例对象)及‌大对象‌(超过 -XX:PretenureSizeThreshold 阈值)。
‌垃圾回收‌:Major GC/Full GC‌:清理整个堆(含老年代),耗时长且触发STW(Stop-The-World)。采用标记-清除或标记-整理算法解决碎片问题。
‌调优重点‌:避免频繁 Full GC 优化对象生命周期,减少大对象直分老年代。

3.3 元空间(Metaspace)‌

作用:替代 JDK 8 前的永久代(PermGen),存储‌类元数据‌(类结构、方法字节码、常量池)。
核心特性‌:使用‌本地内存(Native Memory)‌,不受堆参数(-Xmx)限制。默认无上限,按需扩展直至系统内存耗尽。
溢出风险‌:动态类加载过多时可能触发 OutOfMemoryError: Metaspace。
调优参数‌:-XX:MaxMetaspaceSize=256m(限制元空间最大值)。

4、垃圾回收

在 C/C++ 语言中,程序员需要自己管理内存的申请与释放,而在 Java 语言中,程序员从来不需要关心内存的管理,只管去申请内存而不用担心内存是否被释放,因为 JVM 提供了垃圾回收器来实现内存的回收。不同的虚拟机会提供不同的垃圾回收器。并且提供一系列参数供用户根据自己的应用需求来使用不同的类型的回收器。

Java 中的垃圾回收 GC(Garbage Collection,垃圾回收)是自动内存管理机制,旨在帮助开发者减少手动管理内存的需要,从而提高开发效率和代码的可维护性。G1(Garbage-First)垃圾回收器是 Java HotSpot 虚拟机从 Java 7 开始引入的一种服务器端垃圾回收器,旨在解决 Java 堆内存较大时的垃圾回收效率问题。

4.1 对象存活的判定

引用计数法‌(Java 未采用):因无法解决循环引用问题而被弃用。

四种引用类型‌:

‌强引用‌:对象不会被回收(如 Object obj = new Object())。
‌软引用‌:内存不足时回收(SoftReference)。
‌弱引用‌:GC 运行时立即回收(WeakReference)。
‌虚引用‌:仅用于跟踪对象回收状态(PhantomReference)。

4.2 主流垃圾回收算法

(1)标记-清除算法(Mark-Sweep)‌

‌实现:标记存活对象 → 清除未标记对象。
缺点‌:产生内存碎片,可能引发后续内存分配失败。

(2)复制回收算法(Copying Collector)‌

实现:将内存分为两区,存活对象复制到空闲区后清除原区。
优点‌:无碎片,高效。
缺点‌:牺牲 50% 内存空间。
应用场景‌:新生代回收(如 Eden 区到 Survivor 区)。

(3)标记-整理算法(Mark-Compact)‌

实现:标记存活对象 → 向内存一端移动 → 清理边界外空间。
优点‌:避免碎片。
缺点‌:对象移动开销大。
应用场景‌:老年代回收(如 CMS 的 Full GC 阶段)。

(4)分代收集算法(Generational Collection)‌

新生代‌:短生命周期对象,采用复制算法(Minor GC)。
老年代‌:长生命周期对象,采用标记-清除或标记-整理(Major GC/Full GC)。
依据‌:多数对象“朝生夕死”(弱分代假说)。

5、JVM 的常用参数与调优

JVM 有许多参数,了解这些参数对 JVM 调优是非常有帮助的。

JVM 的优化目标与原则如下:

‌核心目标‌:

降低延迟‌:减少GC停顿时间(STW),提升响应速度。
提高吞吐量‌:增加单位时间内的请求处理能力。
避免内存溢出‌:合理控制堆内存,预防 OOM。
资源平衡‌:优化CPU与内存利用率,避免资源浪费。

基本原则‌:

量化指标优先‌:明确目标(如MaxGCPauseMillis=200ms)再调优。
避免过度优化‌:优先解决架构/代码瓶颈,JVM调优作为补充。

5.1 内存管理参数‌

在做性能调优时,一般都需要指定堆内存的大小。用于指定最小和最大堆大小的参数分别是 -Xms <堆大小> [单位] 和 -Xmx <堆大小> [单位]。单位的取值为:“g”(GB),“m”(MB)或 “k”(KB)。在设置 JVM 内存的最大值与最小值的时候,建议把它们设置为相同的值,这样可以避免在运行时动态地调整堆内存的大小,从而节约了宝贵的 CPU 周期。

【示例】堆内存的优化配置。

java -Xms4g -Xmx4g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -jar app.jar

堆内存配置‌:

        -Xms<size>:初始堆大小(如-Xms4g)。

        -Xmx<size>:最大堆大小(‌必设‌:生产环境建议-Xms=-Xmx防扩容抖动)。

        -Xmn<size>:新生代大小(默认堆1/3,如-Xmn2g)。

分代比例调整‌:

        -XX:NewSize=N: 设置新生代空间的初始大小(如:-XX:NewSize=256m)。

        -XX:NewRatio=N:老年代/新生代比例(默认2,即1:2)。

        -XX:SurvivorRatio=N:Eden/Survivor比例(默认8,即Eden=8:1:1)。

‌元空间配置‌

        -XX:MetaspaceSize=<size>:初始元空间大小。

        -XX:MaxMetaspaceSize=<size>:最大元空间大小(防OOM)。

‌线程栈控制‌

        -Xss<size>:线程栈大小(默认1MB,高并发时可调小至256k)。

5.2 垃圾回收器参数‌

‌回收器选择‌:

        -XX:+UseG1GC:启用G1回收器(推荐大堆低延迟场景)。

        -XX:+UseParallelGC:启用并行回收器(高吞吐场景)。

        -XX:+UseZGC:启用ZGC(超低延迟,JDK15+)。

‌G1 专项优化‌:

        -XX:MaxGCPauseMillis=N:目标最大停顿时间(默认200ms)。

        -XX:G1HeapRegionSize=N:Region大小(通常4m-32m)。

        -XX:InitiatingHeapOccupancyPercent=N:触发Mixed GC的堆占用阈值(默认45%)。

‌通用 GC 参数‌:

        -XX:ParallelGCThreads=N:并行GC线程数(建议≤CPU核心数)。

        -XX:ConcGCThreads=N:并发GC线程数(G1/ZGC适用)。

5.3 监控与诊断参数‌

‌GC 日志配置‌:

        -Xloggc:<file>:GC日志输出路径。

        -XX:+PrintGCDetails:打印详细GC信息。

        -XX:+PrintGCDateStamps:添加时间戳。

‌内存快照与追踪‌:

        -XX:+HeapDumpOnOutOfMemoryError:OOM时自动生成堆转储。

        -XX:HeapDumpPath=<path>:堆转储文件路径。

        -XX:NativeMemoryTracking=detail:追踪本地内存使用。

【示例】生产环境推荐配置(G1示例)。

java -Xms8g -Xmx8g  
     -XX:+UseG1GC -XX:MaxGCPauseMillis=200 
     -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m 
     -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/heapdump.hprof 
     -Xloggc:/logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps 
     -jar app.jar

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容