FGC 对系统影响是什么?JVM 是如何进行垃圾回收的?

FGC 对系统的具体影响表现在哪些方面,会产生什么后果,有什么隐患,如何优化?JVM 是如何进行垃圾回收的,和其他方式相比有什么区别,优势是什么?

【特别说明】此问题属于「BOSS 钓鱼」专题,提问者正在招聘高薪岗位,如果你使用真实身份发布回答,BOSS 将可直接查看你的在线简历,并选择是否与你主动开聊。

回答·43
最热
最新
  • 谈一点不一样的看法。首先纠正一下提问者的逻辑错误,FGC 不能叫影响系统,它被设计的目的是解决系统内存不足的问题,所以它相当于环卫工人,你能说环卫工人影响城市吗?!满足 jvm 设计规则 FGC 开始执行,执行后街道变得干净。至于当今城市人口众多和素质下降导致街边垃圾堆积如山,一位环卫工人来不及打扫,你能说是环卫工人的问题么。解决方案就是:分布管理,城市分区,打扫工作下发到区级环卫单位,各自打扫片区卫生,城市又变得干净。
  • FGC,即 fullgc(另有说法 majorGC,因为每一次 majorgc 都伴随着 minorgc,所以说 majorgc 就是 fullgc),无论虚拟机采用何种垃圾收集器,FGC 都不可避免的需要或长或短的 STW(stoptheworld),在此期间,用户线程必须等待 gc 处理,这也就是为什么 jvm 优化需要减少 STW 的时间。 对象产生的过程:首先,大多数对象(Class 对象是在方法区)都是在堆内,而堆又被分为新生代,老年代,永久代(即方法区),其中新生代又被分为一个 Eden 区和两个 survivor 区(称为 from 和 to),大多数对象(大对象直接在老年代创建)首先在 Eden 区创建,直到 Eden 区满了就会发生一次 minorgc,清理掉新生代没有引用的对象,还存活的对象就会全部被移到一个 survivor 区(from 区),然后继续在 Eden 区创建对象,当 Eden 区再次满的时候,又会发生一次 minorgc,清理新生代没有引用的对象,然后将 Eden 区和 from 区还存活的对象全部移到 to 区,然后 from 和 to 交换名称(即 to 变成原来的 from,from 变成原来的 to,总之保证 minorgc 后 to 区永远是空的),然后继续在 Eden 区创建对象,有三种情况,对象会晋升到老年代:1.当 to 区被填满后,还存活的对象就会晋升到老年代;2.大对象直接晋升到老年代;3.新生代达到 XX:MaxTenuringThreshold 配置的年龄(默认 15)晋升到老年代。当老年代连续可用空间(因为老年代一般采用标记整理算法,所以会产生空间碎片)不够新晋升的对象需要的空间时,就会发生 FGC(实际上如果 HandlePromotionFailure 被设置成允许的话,会发生分配担保) jvm 进行垃圾回收的过程:首先 gc 线程会从 gcroot(比如方法区中的静态对象,常量对象,虚拟机栈中的引用对象以及本地方法栈引用的对象)开始向下搜索所有引用到的对象,所有搜索到的对象被标记为可达,然后没有被标记的对象,gc 首先会判断这些没有被标记的对象是否重写了 finallize 或者该方法是否被执行了,如果重写了该方法并且该方法还没有执行过。则会把对象放入 F-Queue 队列里面,随后由虚拟机自动创建的一个低优先级的线程去执行队列里对象的 finalize 方法。如果没有重写或者 finalize 方法已经执行过了,则等待 gc 回收。
  • FGC 会触发 STW,用户线程全部停止工作,垃圾回收线程进行垃圾回收。FGC 会影响吞吐及系统响应速度,严重时会产生 OOM 导致系统宕机。可通过灵活调整新老年代比例、新生代 Eden 区和 survivor 区比例使程序运行过程中产生的垃圾对象尽量在年轻代回收,还可通过设置参数达到一定年龄阈值的对象提早进入老年代,减少 YGC 移动对象的消耗,设置大对象阈值等方式,提高年轻代利用率。选择合适的垃圾收集器等方式进行优化。JVM 进行垃圾回收主要垃圾对象标记、垃圾对象清理、内存整理几个阶段。JVM 采用分代回收,分代回收大大提高了垃圾回收效率。由于分代的特性使得不同的分代使用不同的垃圾回收算法,所以新老年代使用不同的垃圾收集器。
  • 我看大部分优质回答都很好,都很细节,不过太过于细节,容易一叶障目,特别是大部分关于回收过程都是基于新生代和老年代物理分割的 gc 框架,这已经是老一代的 gc 框架了(对比 g1)。 分析问题我们要分析本质,要正反两面。FGC 对系统的影响,往好了说,清除无用对象,为系统提供更多可用内存空间;坏了说,gc 的部分执行过程会导致用户线程挂起,影响整体吞吐量。因此,结合业务场景,因地制宜的选择合适的 gc 收集器,并合理的配置调优。如根据机器的内存,4g 内存和 40g 内存当然选择不同的收集器,前者通常采用 parScavenge+cms,后者选择 g1。 至于执行过程,无非是初始标记,并发标记,最终标记,回收。这个过程不是固定的,也不是标准的,但最终目的是大同小异的。未来的 gc 框架,会随着 cpu、内存的升级而更新换代,但也玩不出更大的花来,要么追求吞吐量优先,如执行 mr 任务;要么追求极致的系统响应,最小延迟,如 web 应用;亦或者嵌入式系统,追求最小内存占用。
  • FGC 会导致 STW,所有的线程会暂停以便进行垃圾回收,如果属于频繁交互的应用那对用户来说会造成卡顿,如果属于数据处理类的系统会导致任务的处理时间延长。在 FGC 期间,如果有大量请求过来,会产生请求堆积,严重的话导致服务器宕机。jvm 垃圾回收主要是在堆上,通过可达性分析确定哪些对象需要进行回收。堆又被分为老年代和新生代,新生代又进一步分为 eden 和 survivor ,而 servivor 又分了两块。新生代采用的垃圾回收算法是复制,老年代有标记整理跟标记清除,后者内存回收之后会产生内存碎片。
  • FGC 会导致 Java 所有线程停止下来(stoptheworld),使正常作业的系统产生短时间的停顿。频繁的 FGC 容易导致 CPU 飙升,导致操作系统出现卡顿现象。优化点是,一个是加大虚拟机堆内存,另外一个是排查是否发生内存泄露。
  • 当一个对象不再被引用时,该对象变为垃圾等待垃圾回收器回收。分代收集法,分为新生代和老年代,其中新生代分为 Eden 和 survivor(Survivor0,Survivor1),内存比例为 8:1:1。当一个新生代对象躲过 15 次以上的 minor GC,会变为老年代,或者是一个大对象直接变为老年代。而 FGC 是在用来储存老年代的垃圾对象内存不足时触发的。FGC 会造成程序卡顿、进一步导致超时。
  • FGC 即 Full GC 即整堆收集,顾名思义是针对整个堆及方法区的垃圾收集。首先由于垃圾回收范围广,自然比 Young GC 或 Old GC 等针对单个分代的回收耗时要长,同时 FGC 时无论采用哪种收集器都会涉及到 STW,即停掉所有用户线程,这会大大降低系统吞吐量,延长系统响应时间,如果问题不能及时解决,频繁的 gc 极有可能导致 oom,最终导致服务不可用。在用户层面给用户最直观的感受就是系统变得“卡顿”或操作无响应,影响用户体验,失去用户粘性。        对于 FGC 的不同情况可以采取不同的优化方式: 1、查看老年代或者方法区是否内存设置过小,如果过小可以适当调大。同时看下使用的收集器是否是 CMS,如果不是可以尝试换成 CMS 收集器,因为目前主流收集器只有 CMS 可以发生只单独收集老年代的行为,及时的对老年代进行回收一定程度上可以减少 FGC 的发生 2、检查是否有显式手动调用垃圾回收的不规范代码如 System.gc(),这种代码建议直接干掉 3、是否存在长期存活的大对象,检查是否属于内存泄漏问题(对象可达但是实际上无用),如果是正常代码建议换种方式优化等等 目前 jvm 的垃圾收集是分代收集策略 设计者一般把堆至少分为新生代和老年代两个区域。 新生代中大部分存的是一些朝生夕灭的一些“短命”对象,当新生代中的 eden 区分配满的时候会发生 young GC,部分存活对象会迁移到 survivor 空间,并且对象年龄设置为 1,每经过一次 young GC,年龄就+1,当到一定程度(默认 15 岁),可以通过参数-XXMaxTenuringThreshold 设置),就将会晋升年老代;另外虚拟机可以设置 PretenureSizeThreshold 参数,令大于这个设置值的对象直接在老年代分配。 这样做的目的是避免在 Eden 区及两个 Survivor 区之间发生大量的内存复制(新生代采用复制算法收集内存)。 当准备要触发一次 young GC 时,如果发现统计数据说之前 young GC 的平均晋升大小比目前 old gen 剩余的空间大,则不会触发 young GC 而是转为触发 full GC,或者,如果有 perm gen 的话,要在 perm gen 分配空间但已经没有足够空间时,也要触发一次 full GC(老年代采用标记整理算法收集内存)
  • FGC 频繁(比如几十分钟就会执行一次),它会导致工作线程频繁被停止,让系统看起来一直有卡顿现象,也会使得程序的整体性能变差,FGC 耗时增加,卡顿时间也会随之增加,尤其对于高并发服务,可能导致 FGC 期间比较多的超时问题,可用性降低。 JVM 采用分带收集算法进行垃圾回收。 新生代,存活的对象比较少,一般选择复制算法;老年代,存活的对象比较多,一般选择标记-整理-清除算法。
  • FGC 会暂停所有用户线程,也就是说在 FGC 期间,系统不会处理任何请求,系统吞吐量降低 FGC 耗时过长时会导致系统卡顿、主从切换(长时间的 FGC 导致无法及时处理或发送心跳) 优化 FGC 的手段分为三个方法 1、避免创建朝生夕死的大对象(大对象会直接分配到老年代)、及时释放需要手动释放的资源 2、在选择垃圾收集器时,如果选择 CMS 或者 G1 等并发垃圾收集器,降低垃圾收集器启动的阈值(在 JVM 参数中可指定) 3、升级硬件 优化 JVM 主要有三个方法 1、通过压测观察系统所用内存,预先分配内存 2、根据系统应用场景合理选择垃圾收集器,吞吐量优先选择串行垃圾收集器,响应时间优先选择并发垃圾收集器 3、解决系统中 OOM 问题 JVM 垃圾收集器分为两种,串行和并发 串行垃圾收集器 暂停用户线程->回收垃圾(年轻代复制算法,老年代标记压缩算法) -> 唤醒用户线程 并发垃圾收集器 初始标记(标记所有根节点(GC Roots) -> 并发标记 -> 重新标记 -> 回收垃圾 G1 以及 CMS 在初始标记以及重新标记期间会触发 STW CMS 回收垃圾是并发的,而 G1 是会触发 STW 的 CMS 主要针对老年代,使用标记清除算法,会产生垃圾虽破 G1 针对整个堆内存,年轻代使用复制算法,老年代使用标记压缩算法 CMS 与 G1 都是并发垃圾收集器,在垃圾收集器期间如果内存还是不够用,会导致 FGC 在 JVM 上运行程序,通常是自动垃圾回收的,不需要开发者手动释放,提高开发效率,但是由于需要 JVM 标记垃圾,相比于 C++手动释放垃圾花的时间更长,系统吞吐量不如 C++,但是开发效率较高,不容易产生内存泄漏 C++程序需要用户手动释放内存,开发效率较低且容易因为疏忽导致内存泄漏,但系统吞吐量高