《JVM》专题学习记录
JVM内存模型和类加载1、JDK(Java Development Kit): JDK 是 Java 开发的核心工具包包含 JRE 和开发、调试、监控 Java 程序所需的工具。2、JRE(Java Runtime Environment): JRE 是 运行 Java 程序的最小环境包含 JVM 和运行 Java 程序所需的 核心类库(如 java.lang、java.util 等)。3、JVMJava Virtual Machine): JVM 是 Java 程序运行的核心负责将编译后的 字节码.class 文件 解释/编译为特定操作系统和硬件的机器码实现“一次编写到处运行”跨平台性。JDK JRE JVMJDK JRE 开发工具、JRE JVM 核心类库、JVM 执行引擎 内存管理 其他运行时支持Java文件编译与执行流程1、Java编译器(javac)将.java源代码文件编译成字节码文件(.class),字节码文件包含平台无关的指令JVM能够理解并执行这些指令。2、JVM通过类加载器ClassLoader加载 .class 文件。3、字节码被JVM解释执行或者通过JIT即时编译器转化为本地机器代码。将字节码转换为机器码从而提高程序的执行效率4、JVM根据需求进行垃圾回收自动管理内存以及回收不再使用的对象。类加载流程1、加载Loading通过类加载器ClassLoader查找 .class 文件。将字节码转换为 Class? 对象存储在方法区Method Area。2、链接Linking验证Verification检查字节码是否符合 JVM 规范如魔数、方法栈溢出。准备Preparation为类的静态变量分配内存并赋默认值如 int 初始化为 0。解析Resolution将符号引用如 java/lang/System转换为直接引用内存地址。3、初始化Initialization执行类的静态代码块static {}和静态变量赋值。内存模型1、方法区线程共享类信息(类变量)、常量池、静态变量(static)、方法信息(修饰符、方法名、返回值、参数等)常量池JDK 7 之前位于方法区(永久代)JDK8后移至堆内存(元空间)。主要储存动态解析后的常量、运行生成的字面量。2、堆线程共享储存的是实例对象。3、虚拟机栈 线程隔离又名堆栈主管程序运行生命周期和线程同步线程结束栈内存就释放了。不存在垃圾回收问题。虚拟机栈中执行每个方法的时候都会创建一个栈桢用于存储局部变量表操作数栈动态链接方法出口等信息。虚拟机栈储存的是8大基本类型 对象引用 实例方法。4、本地方法栈线程隔离本地接口库里调用的方法就是java里面native关键字修饰的方法。5、程序计数器线程隔离(记录当前方法正在执行的位置)每个线程启动是都会创建一个程序计数器保存的是正在执行的jvm指令程序计数器总是指向下一条将被执行指令的地址。生命周期与线程的生命周期保持一致。垃圾回收算法标记清除标记阶段 清除阶段。在标记阶段首先通过根节点(GC Roots)标记所有从根节点开始的对象未被标记的对象就是未被引用的垃圾对象。然后在清除阶段清除所有未被标记的对象。适用场合存活对象较多的情况下比较高效适用于年老代即旧生代缺点容易产生内存碎片再来一个比较大的对象时典型情况该对象的大小大于空闲表中的每一块儿大小但是小于其中两块儿的和会提前触发垃圾回收扫描了整个空间两次第一次标记存活对象第二次清除没有标记的对象复制算法现在的商业虚拟机都采用这种收集算法来回收新生代。适用场合存活对象较少的情况下比较高效扫描了整个空间一次标记存活对象并复制移动适用于年轻代即新生代基本上98%的对象是朝生夕死的存活下来的会很少缺点需要一块儿空的内存空间需要复制移动对象标记整理标记-压缩算法是一种老年代的回收算法它在标记-清除算法的基础上做了一些优化。标记存活对象 压缩存活对象 清除剩余区域。首先也需要从根节点开始对所有可达对象做一次标记但之后它并不简单地清理未标记的对象而是将所有的存活对象压缩到内存的一端。之后清理边界外所有的空间。这种方法既避免了碎片的产生又不需要两块相同的内存空间因此其性价比比较高。实不止整理算法里面移动对象更新引用需要STW清除算法和复制算法中的标记清除都需要STW只不过时间短分代收集算法步骤 1对象分配优先分配在 Eden 区新对象默认在 Eden 区分配(若 Eden 空间不足触发 Minor GC)步骤 22.1:Minor GC年轻代回收标记存活对象从 GC Roots 出发标记 Eden 和当前使用的 Survivor 区如 S0中的存活对象。2.2:复制到空闲 Survivor 区如 S1存活对象被复制到空闲的 Survivor 区S1年龄Age1。Eden 和已使用的 Survivor 区S0被清空。交换 Survivor 区角色下次 Minor GC 时S1 变为“已使用区”S0 变为“空闲区”。2.3:对象晋升当对象年龄达到阈值默认 15通过 -XX:MaxTenuringThreshold 调整晋升到老年代。Survivor 区空间不足时部分对象会提前晋升。步骤 3: Major GC/Full GC老年代回收触发条件老年代空间不足如大对象直接进入老年代或 Minor GC 后存活对象过多。显式调用 System.gc()不推荐可能被 JVM 忽略。回收算法标记-清除CMSConcurrent Mark-Sweep使用并发执行以减少停顿。标记-整理Serial Old、Parallel Old 使用解决碎片问题但 STW 时间长。垃圾收集器吞吐量优先Parallel Scavenge Parallel Old。低延迟优先ParNew CMSJDK 8 及之前或 G1JDK 9。超大堆内存G1 或 ZGC/ShenandoahJDK 11。简单应用Serial Serial Old。以下是整理后的表格内容收集器分代算法目标适用场景Serial年轻代 老年代复制 标记-整理简单低开销单核客户端、嵌入式系统ParNew年轻代复制多线程多核优化配合 CMS 的 Web 服务Parallel Scavenge年轻代复制多线程高吞吐量后台计算、批处理任务Serial Old老年代标记-整理后备方案小内存或客户端Parallel Old老年代标记-整理多线程高吞吐量与 Parallel Scavenge 组合CMS老年代标记-清除并发低延迟响应敏感的 Web 服务G1全堆Region 分区 复制平衡吞吐与延迟大堆内存、需可控停顿时间的应用1、Serial 收集器(单线程)定位低开销的 基础型收集器。分代策略年轻代复制算法Stop-The-World, STW、老年代标记-整理算法Serial Old。特点单线程执行全程 STW。适用于客户端应用或单核环境如嵌入式系统。参数-XX:UseSerialGC。优点实现简单无线程交互开销。缺点STW 时间长不适用多核或大堆场景。2、ParNew 收集器(多线程)定位专为年轻代设计。分代策略年轻代多线程复制算法并行 GC。老年代需配合 CMS 或 Serial Old。特点多线程并行回收减少年轻代 STW 时间。与 CMS 收集器兼容JDK 9 前。参数-XX:UseParNewGC。优点在多核环境下提升年轻代回收效率。缺点老年代仍需配合其他收集器如 CMS。3.Parallel Scavenge 收集器(多线程)定位高吞吐量优先 的年轻代收集器。分代策略年轻代多线程复制算法。老年代Parallel Old标记-整理算法。特点关注吞吐量吞吐量 用户代码时间 / (用户代码时间 GC时间)。支持自适应调节策略-XX:UseAdaptiveSizePolicy。参数-XX:UseParallelGC年轻代和 -XX:UseParallelOldGC老年代。优点适合后台计算型应用如批处理。缺点单次 STW 时间较长延迟敏感场景不适用。4.Serial Old 收集器定位Serial 收集器的老年代版本标记-整理算法。适用场景与 Parallel Scavenge 搭配JDK 5 前。作为 CMS 收集器的后备方案当 Concurrent Mode Failure 时。参数-XX:UseSerialGC自动启用 Serial Old。优点简单可靠内存碎片少。缺点STW 时间长仅适合小内存或客户端应用。5.Parallel Old 收集器定位Parallel Scavenge 的老年代版本多线程标记-整理算法。分代策略与 Parallel Scavenge 组合形成 吞吐量优先的全堆回收方案。特点多线程并行回收老年代。适合大堆内存和高吞吐需求。参数-XX:UseParallelOldGC。优点全堆并行回收提升吞吐量。缺点STW 时间仍较长不适合低延迟场景。6.CMSConcurrent Mark-Sweep收集器定位低延迟优先 的老年代收集器使用标记-清除算法。分代策略年轻代需配合 ParNew 或 Serial。老年代并发标记-清除。工作流程初始标记STW标记 GC Roots 直接关联的对象。并发标记遍历对象图与用户线程并发执行。重新标记STW修正并发标记期间变动的引用。并发清除回收垃圾对象与用户线程并发。参数-XX:UseConcMarkSweepGC。优点减少 STW 时间适合响应敏感应用如 Web 服务。缺点内存碎片问题需 Full GC 时触发压缩。对 CPU 资源敏感并发阶段占用线程。CMS工作流程的解释这是因为CMS要在垃圾回收时尽可能减少对用户线程的影响。初始标记只是标记一下GC Roots直接关联的对象速度很快所以停顿时间短。并发标记阶段垃圾回收线程和用户线程一起运行它会从初始标记的对象开始遍历整个对象图这个过程耗时但不暂停用户线程。不过并发标记期间用户线程可能会修改对象引用导致有些标记结果不准确所以需要重新标记阶段这个阶段会暂停用户线程修正并发标记期间的变动停顿时间比初始标记稍长但比并发标记短很多。最后并发清除阶段垃圾回收线程和用户线程并发执行清理掉标记阶段判断为垃圾的对象这个过程不暂停用户线程。7.G1Garbage-First收集器定位平衡吞吐与延迟 的全堆收集器JDK 9 默认收集器。核心设计将堆划分为多个 Region默认 2048 个每个 Region 可以是 Eden、Survivor 或 Old。优先回收垃圾比例高的 RegionGarbage-First 名称来源。工作流程年轻代回收复制算法STW 并行回收。并发标记类似 CMS标记全堆存活对象。混合回收回收部分 Old Region 和所有年轻代 Region。Full GC备用当回收速度跟不上分配速度时触发。参数-XX:UseG1GC。优点可预测的停顿时间通过 -XX:MaxGCPauseMillis 设置目标。高效处理大堆内存如 数十GB。缺点内存占用较高记录 Region 的引用关系。小堆场景性能可能不如其他收集器。JVM参数内存相关参数-Xms初始堆大小JVM 启动时分配的堆内存 -Xms2g初始堆 2GB-Xmx最大堆大小堆内存上限 -Xmx4g堆最大 4GB-Xmn年轻代大小Young Generation -Xmn1g年轻代 1GB-XX:NewRatio老年代与年轻代的比例默认值 2即老年代:年轻代2:1 -XX:NewRatio3比例 3:1-XX:SurvivorRatioEden 区与 Survivor 区的比例默认值 8即 Eden:S0:S18:1:1 -XX:SurvivorRatio6比例 6:1:1-XX:MetaspaceSize元空间初始大小Metaspace取代 Java 8 之前的-XX:PermSize -XX:MetaspaceSize256m-XX:MaxMetaspaceSize元空间最大大小默认无限制但受物理内存限制 -XX:MaxMetaspaceSize512m-XX:MaxDirectMemorySize直接内存NIO 的 Direct Buffer最大大小 -XX:MaxDirectMemorySize1g垃圾回收GC相关参数-XX:UseSerialGC使用 Serial串行垃圾回收器单线程适合客户端或小内存应用-XX:UseParallelGC使用 Parallel Scavenge并行回收器吞吐量优先-XX:UseConcMarkSweepGC**使用 CMS并发标记清除**回收器低停顿已废弃Java 14 后移除-XX:UseG1GC使用 G1Garbage-First回收器平衡吞吐与延迟Java 9 后默认-XX:UseZGC使用 ZGC低延迟回收器Java 11 实验性Java 15 正式-XX:UseShenandoahGC使用 Shenandoah 回收器低延迟需单独启用GC 调优参数-XX:MaxGCPauseMillis 期望的最大 GC 停顿时间G1/ZGC 等回收器参考此值调整策略-XX:G1HeapRegionSize G1 回收器的 Region 大小需为 2 的幂范围 1MB~32MB-XX:InitiatingHeapOccupancyPercent G1 触发并发标记周期的堆占用阈值百分比-XX:ParallelGCThreads 并行 GC 的线程数默认与 CPU 核数相关-XX:ConcGCThreads 并发 GC 的线程数如 CMS、G1 的并发阶段性能优化参数-XX:TieredCompilation 启用分层编译C1 C2 编译器结合Java 8 默认开启-XX:CompileThreshold 方法调用次数阈值触发 JIT 编译默认 1500-XX:UseStringDeduplication 开启字符串去重G1 回收器特有减少重复字符串内存占用-XX:AggressiveOpts 启用激进优化JVM 内部实验性优化不同版本效果不同-XX:ReservedCodeCacheSize JIT 编译后的代码缓存区大小诊断与监控参数-XX:HeapDumpOnOutOfMemoryError内存溢出时自动生成堆转储文件HEAP DUMP-XX:HeapDumpPath指定堆转储文件路径-XX:PrintGCDetails打印详细的 GC 日志-Xloggc: 将 GC 日志输出到文件-XX:PrintFlagsFinal 打印所有 JVM 参数的最终值用于确认参数是否生效-XX:FlightRecorder 启用 Java Flight RecorderJFR性能分析工具-XX:StartFlightRecording 启动 JFR 记录需指定参数JVM命令jps-l:列出当前系统中所有 Java 进程的 PID进程 ID和主类名。-l 参数会显示完整的包名或 JAR 路径。jinfo:用途查看或动态修改 JVM 参数部分参数支持动态修改。常用场景检查 JVM 的运行时参数或临时调整参数如开启 GC 日志。示例# 查看某个JVM参数的值如最大堆内存 $ jinfo-flagMaxHeapSize12345//12345 是目标 Java 进程的 PID。-XX:MaxHeapSize2147483648# 动态开启GC日志打印 $ jinfo-flagPrintGCDetails12345//-flag 用于查看或修改参数 表示启用- 表示禁用。jstat:用途监控 JVM 的运行时状态包括类加载、GC、JIT 编译等统计信息。常用场景分析内存泄漏、GC 频率、内存分区使用情况。# 每隔1秒输出一次GC统计共输出5次 $ jstat-gcutil1234510005S0S1EOMCCSYGCYGCTFGCFGCTGCT0.0099.9968.4345.3295.1290.11150.25030.4500.700...后续输出省略-gcutil 显示各内存区域使用百分比。S0/S1Survivor 区EEden 区O老年代M元空间YGCYoung GC 次数FGCFull GC 次数。如果发现 FGC 频繁增加可能提示内存泄漏或堆大小不足。jstack:用途生成 Java 进程的线程快照包含所有线程的调用栈。常用场景分析线程死锁、高 CPU 占用、线程阻塞等问题。# 生成线程快照并保存到文件 $ jstack12345thread_dump.txt # 查找死锁在输出中搜索deadlock或BLOCKED $ jstack12345|grep-A10deadlockjmap:1、生成堆内存快照Heap Dump用于后续通过 MATMemory Analyzer Tool、VisualVM 等工具分析内存泄漏。jmap-dump:formatb,fileapp_heap.hprof1234512345是目标Java进程的PID。 生成的文件 app_heap.hprof 可用MAT或VisualVM分析。 注意生成HeapDump可能导致应用短暂停顿尤其是大堆内存时。2、查看堆内存各区域使用情况如 Eden、Survivor、Old Gen 等内存分区的使用量。jmap-heapPID3、统计存活对象信息查看类实例数量、占用内存大小。jmap-histo:livePID4、查看类加载器信息显示已加载的类及其来源。jmap-clstatsPID假设一个 Java 应用频繁 Full GC怀疑内存泄漏1、用jps找到PIDjps-l12345com.example.MyApp2、jstat观察GC趋势若 OUOld Gen 使用率持续上升进入下一步。jstat-gcutil12345100010// 1000是1000毫秒 10次3、用jmap生成Heap Dump文件jmap-dump:formatb,fileleak.hprof123454、用MAT分析leak.hprof文件查找占用内存最大的对象。检查对象的引用链定位未释放的原因如静态集合类缓存未清理。JVM常用工具1、VisualVM用途集成监控、线程分析、堆内存快照分析、CPU 采样等。场景开发环境实时监控分析内存泄漏、线程阻塞。使用直接运行 jvisualvm 命令。2、JConsole用途监控堆内存、线程、类加载、MBean 等。场景快速查看 JVM 基础指标内存、GC、CPU。使用运行 jconsole 命令连接目标进程。3、MATMemory Analyzer Tool用途分析堆内存快照定位内存泄漏。场景分析 jmap 生成的 .hprof 文件。下载Eclipse MAT 官网4、Arthas阿里开源用途动态诊断工具支持热修复、方法调用监控、火焰图生成。场景生产环境在线排查问题无需重启应用。