文章目录
- GC垃圾回收
-
- [新生代垃圾回收(Minor GC)](#新生代垃圾回收(Minor GC))
- [老年代垃圾回收(Major GC)](#老年代垃圾回收(Major GC))
- GC调优
- 实战-针对16C64G服务器配置JVM
-
- 配置原则
- [推荐 JVM 启动参数](#推荐 JVM 启动参数)
GC垃圾回收
JVM的GC垃圾回收是自动内存管理机制,用于回收不再使用的对象,释放堆内存空间。
GC 的核心是判断对象是否"存活":使用可达性分析算法,从 GC Roots(如线程栈、本地变量表、静态变量等)出发,标记可达对象,不可达的即为垃圾,进行回收。
新生代垃圾回收(Minor GC)
触发条件:
- Eden 区满时触发(新对象优先分配在 Eden)。
- 如果 Survivor 区空间不足,也可能间接触发。
回收过程(基于Copying算法):
- 标记阶段:从 GC Roots 扫描新生代,标记存活对象(通常使用根节点枚举 + 引用链追踪)。
- 复制阶段:将 Eden 和 From 中的存活对象复制到 To ,复制后,交换 From 和 To 角色,清空 Eden 和旧 From。
- 年龄晋升:对象每经历一次 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、、避免新生代大小设置过大或过小
- 新生代设置过大,会带来两个问题:一是新生代minor GC 垃圾回收时间大幅度增加二是老年代变小,可能导致Full GC频繁执行
- 当新生代设置过小时,会产生两种比较明显的现象一是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 | 大对象直接老年代,减少晋升 |