JVM-GC垃圾回收案例

文章目录

GC垃圾回收

JVM的GC垃圾回收是自动内存管理机制,用于回收不再使用的对象,释放堆内存空间。

GC 的核心是判断对象是否"存活":使用可达性分析算法,从 GC Roots(如线程栈、本地变量表、静态变量等)出发,标记可达对象,不可达的即为垃圾,进行回收。

新生代垃圾回收(Minor GC)

触发条件:

  1. Eden 区满时触发(新对象优先分配在 Eden)。
  2. 如果 Survivor 区空间不足,也可能间接触发。

回收过程(基于Copying算法):

  1. 标记阶段:从 GC Roots 扫描新生代,标记存活对象(通常使用根节点枚举 + 引用链追踪)。
  2. 复制阶段:将 Eden 和 From 中的存活对象复制到 To ,复制后,交换 From 和 To 角色,清空 Eden 和旧 From。
  3. 年龄晋升:对象每经历一次 Minor GC,年龄 +1。达到阈值(默认 15,受-XX:MaxTenuringThreshold 控制)时,晋升到老年代。
  • 动态年龄判断:如果 Survivor 中相同年龄对象总大小 > Survivor 空间一半,该年龄及以上对象提前晋升。
  • 空间分配担保:如果老年代空间不足以容纳晋升对象,Minor GC 可能失败,转为 Full GC。

特点

  • STW 时间短(毫秒级),因为新生代小、对象少。
  • 频率高:新生代分配快,Eden 容易满。
  • 算法效率高:复制算法无碎片,但浪费空间(Survivor 区只用一半)。

老年代垃圾回收(Major GC)

触发条件:Major GC 不是像 Minor GC 那样由 Eden 区满直接触发,而是基于老年代空间使用情况

  • 老年代空间不足:当老年代使用率达到阈值(默认约 92%,可调),无法容纳从新生代晋升的对象或大对象时触发。
  • 空间分配担保失败:在 Minor GC 前,JVM 检查老年代可用空间是否足够容纳新生代所有对象(最坏情况)。如果不足,触发 Major GC(或直接 Full GC)。
  • Metaspace 满:虽不直接属于老年代,但 Metaspace(JDK 8+ 的类元数据区)满时可能连带触发 Full GC,包括 Major GC。
  • 显式调用:通过 System.gc() 建议触发(非强制,JVM 可忽略)。

如果 Major GC 后空间仍不足,可能导致 OutOfMemoryError(OOM)

回收过程:

Major GC 的过程取决于垃圾收集器。经典过程基于标记-清除-整理(Mark-Sweep-Compact)算法,分为几个阶段。

  • 初始标记(Initial Mark):从 GC Roots(线程栈、静态变量等)标记直接引用的老年代对象。STW,时间短(毫秒级)。
  • 并发标记(Concurrent Mark)(可选,在并发收集器中):用户线程继续运行,同时多线程追踪引用链,标记所有可达对象。可能产生"浮动垃圾"(新垃圾未标记)。
  • 重新标记(Remark):修正并发标记期间用户线程修改的引用。STW,短暂停。
  • 清除(Sweep):清除不可达对象,回收空间。可能并发或 STW。
  • 整理(Compact):将存活对象移动到一端,压缩空间,避免碎片(碎片化会导致分配效率低)。STW,耗时较长。

特点:

  • 频率低:老年代对象存活时间长,不像新生代那样频繁满。
  • 耗时长:老年代通常占堆的 2/3,对象多,标记链长。STW 可达秒级,影响应用吞吐量和响应时间。
  • 性能影响:高负载下频繁 Major GC 可能导致应用卡顿。碎片问题在非整理算法中突出。
  • 与 Minor GC 的关系:Major GC 常伴随 Minor GC(Full GC 时),但可独立发生。晋升对象是 Major GC 的"输入"。
方面 Minor GC Major GC Full GC
回收范围 新生代 老年代 整个堆(新生代 + 老年代 + Metaspace)
触发 Eden 满 老年代满或担保失败 老年代/Metaspace 满或并发失败
频率 极低(通常包含 Major)
STW 时间 短(毫秒) 中等(秒级) 长(秒以上)
算法 Copying Mark-Sweep-Compact(多变) 组合(取决于收集器)
典型问题 无(但晋升多导致 Major) 碎片、长暂停 应用卡顿、OOM
优化重点 新生代大小 并发收集器、阈值调整 避免触发,监控整体堆

GC调优

GC调优这种问题肯定是具体场景具体分析,但是在面试中不要讲太细,大方向说清楚就行,不需要涉及具体的垃圾收集器比如 CMS 调什么参数,G1 调什么参数之类的。

总体原则

GC 调优的核心思路就是尽可能的使对象在年轻代被回收,减少对象进入老年代。

核心指标

场景/问题 症状 调优策略 注意事项
频繁 Minor GC Eden 快速满,高分配率 增大新生代(-Xmn),优化代码减少临时对象 勿过大,导致老年代小
频繁 Full GC 老年代快速满 增大老年代,降低晋升阈值,修复内存泄漏 检查担保失败日志
长 STW 暂停 Major/Full GC 时间长 切换并发收集器(如 G1),减小堆大小 测试下调暂停目标
内存碎片 CMS 后分配失败 用 Mark-Compact 收集器,或定期 Full GC CMS 碎片用-XX:CMSFullGCsBeforeCompaction
高 CPU 使用 GC 线程过多 减少 -XX:ParallelGCThreads,优化算法 平衡线程与应用线程
OOM 堆/Metaspace 耗尽 增大 -Xmx/-XX:MaxMetaspaceSize,dump 分析 设置 -XX:+HeapDumpOnOutOfMemoryError

GC常见调优策略

a、各代大小优化

java 复制代码
-Xms、 -Xmx:	通常设置为相同的值,避免运行时要不断扩展JVM内存,这个值决定了JVM heap所能使用的最大内存。
-Xmn: 			决定了新生代空间的大小,新生代Eden、S0、S1三个区域的比率可以通过-XX:SurvivorRatio来控制
-XX:MaxTenuringThreshold:		   控制对象在经过多少次minor GC之后进入老年代,此参数只有在Serial 串行GC时有效。默认值为15次
-XX:PermSize、-XX:MaxPermSize:	   用来控制方法区的大小,通常设置为相同的值。

b、、避免新生代大小设置过大或过小

  1. 新生代设置过大,会带来两个问题:一是新生代minor GC 垃圾回收时间大幅度增加二是老年代变小,可能导致Full GC频繁执行
  2. 当新生代设置过小时,会产生两种比较明显的现象一是minor GC次数频繁二是可能导致 minor GC对象直接进入老年代,当老年代内存不足时,会触发Full GC。

c、合理设置对象在新生代存活的周期

新生代存活周期的值决定了新生代对象在经过多少次Minor GC后进入老年代。因此这个值要根据自己的应用来调优,Jvm参数上这个值对应的为-XX:MaxTenuringThreshold,默认值为15次。

d、减少GC开销的措施

java 复制代码
1. 不要显式调用System.gc()
    此函数建议JVM进行主GC,虽然只是建议而非一定,但很多情况下它会触发主GC,从而增加主GC的频率,也即增加了间歇性停顿的次数。大大的影响系统性能。

2. 尽量减少临时对象的使用。
    临时对象在跳出函数调用后,会成为垃圾,少用临时变量就相当于减少了垃圾的产生,从而延长了出现上述第二个触发条件出现的时间,减少了主GC的机会。

3.对象不用时最好显式置为Null。
    一般而言,为Null的对象都会被作为垃圾处理,所以将不用的对象显式地设为Null,有利于GC收集器判定垃圾,从而提高了GC的效率。
    
4. 尽量使用StringBuffer,而不用String来累加字符串
    由于String是固定长的字符串对象,累加String对象时,并非在一个String对象中扩增,而是重新创建新的String对象,而StringBuffer是可变长的,它在原有基础上进行扩增,不会产生中间对象。
    
5. 能用基本类型如Int,Long,就不用Integer,Long对象
    基本类型变量占用的内存资源比相应对象占用的少得多,如果没有必要,最好使用基本变量。
    
6. 尽量少用静态对象变量
    静态变量属于全局变量,不会被GC回收,它们会一直占用内存。

实战-针对16C64G服务器配置JVM

配置原则

  • 内存:JVM 堆(Heap)不超过物理内存的 50-75%(留 25-50% 给 OS、Metaspace、线程栈、Off-Heap 等)。对于 64GB,推荐堆 32-48GB。过大堆导致 GC 暂停长;过小导致频繁 GC。
  • CPU:16 核支持多线程 GC,设置 GC 线程数为核数的 50-75%(8-12 线程),避免 GC 抢占应用线程。
  • GC:默认 G1(JDK 9+),适合大堆、低暂停。如果低延迟优先,用 ZGC;吞吐量优先,用 Parallel。

推荐 JVM 启动参数

假设应用是典型 Web 服务(高并发、低延迟)

堆内存

  • -Xms32g -Xmx32g:初始堆/最大堆 32GB。

策略:从 16GB 起步,根据监控增至 48GB(如果老年代增长慢),50%-75%。

  • -Xmn10GB:新生代默认堆的1/3,新生代:老年代 = 2:1
  • -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m:Metaspace 初始/最大(类元数据)。策略:如果类加载多,增大到 1GB。

垃圾收集器

  • -XX:+UseG1GC: 推荐默认收集器,适合 16C 64G,自动并行/并发。
  • -XX:MaxGCPauseMillis=100:目标最大暂停 100ms。策略:Web 应用设 50-200ms;批处理可忽略。
  • -XX:InitiatingHeapOccupancyPercent=45:堆 45% 满触发 Mixed GC。策略:降低到 30-50% 提前回收。
  • 替代:低延迟用 -XX:+UseZGC(暂停 <10ms),加 -XX:ZCollectionInterval=10(每 10s 回收);吞吐用 -XX:+UseParallelGC。

线程与并行

  • -XX:ParallelGCThreads=8:并行 GC 线程(16 核的 50%)。策略:高负载可增至 12;
  • -XX:ThreadStackSize=1m:每个线程栈 1MB。策略:如果线程多(>1000),减小到 512k 节省内存。

性能优化

  • -XX:+UseLargePages:启用大页内存(需 OS 配置,如 Linux hugepages)。策略:提升 TLB 命中,减少内存访问开销。
  • -XX:+AlwaysPreTouch:启动时预触堆,减少首次访问延迟。
  • -XX:ReservedCodeCacheSize=256m:代码缓存(JIT 编译)。策略:如果方法多,增大到 512m。

监控与日志

  • -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log:详细 GC 日志。
  • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/dump.hprof:OOM 时 dump 堆,便于分析。
场景类型 推荐堆大小 GC 收集器 关键参数调整 理由
低内存实时 16-24GB (25%) ZGC -XX:ZCollectionInterval=5 -XX:+UseLargePages 超低延迟,内存敏感;固定间隔回收
高并发 Web 32-40GB (50%) G1 或 ZGC -XX:MaxGCPauseMillis=50 -XX:ParallelGCThreads=10 1. 预留32GB给操作系统、DirectBuffer、线程栈、Metaspace等 2. -Xms = -Xmx 避免运行时动态扩容,减少GC停顿 3. 50%是生产环境的黄金比例
批处理/大数据 40-48GB (60%) Parallel -XX:GCTimeRatio=19 -XX:ParallelGCThreads=12 高吞吐,允许长暂停;最大化 CPU
内存密集 48GB G1 -XX:NewRatio=3 -XX:PretenureSizeThreshold=10m 大对象直接老年代,减少晋升
相关推荐
焦糖玛奇朵婷2 小时前
实测扭蛋机小程序:开发简单,互动有趣
java·大数据·程序人生·小程序·软件需求
Nan_Shu_6142 小时前
学习: 尚硅谷Java项目之小谷充电宝(3)
java·后端·学习
WJSKad12352 小时前
【DepthPro】实战教程:单目深度估计算法详解与应用
算法
wzqllwy2 小时前
8 大经典排序算法(Java 实现):原理 + Demo + 核心分析
java·算法·排序算法
智能工业品检测-奇妙智能2 小时前
AIFlowy如何实现与现有Spring Boot项目的无缝集成?
java·spring boot·后端
We་ct2 小时前
LeetCode 77. 组合:DFS回溯+剪枝,高效求解组合问题
开发语言·前端·算法·leetcode·typescript·深度优先·剪枝
從南走到北2 小时前
JAVA无人共享无人健身房物联网结合系统源码支持小程序+公众号+APP+H5
java·物联网·小程序
Nuopiane2 小时前
MyPal3(3)
java·开发语言
重生之我是Java开发战士2 小时前
【递归、搜索与回溯】二叉树中的深度优先搜索:布尔二叉树,求根节点到叶节点数字之和,二叉树剪枝,验证二叉搜索树,第K小的元素,二叉树的所有路径
算法·深度优先·剪枝