回答
主要原因还是 CMS 自身存在一些局限性。
一、内存碎片问题
由于 CMS采用的是标记-清除算法,所以它不会对内存进行压缩或整理。这意味着在垃圾回收后,堆内存中可能会产生大量的内存碎片。随着时间的推移,JVM 可能会因为内存碎片问题无法找到足够大的连续内存块来为新对象分配内存,导致大对象分配失败。此时,JVM 可能需要触发一次Full GC,而 CMS 默认采用 Serial Old 收集器来执行 Full GC,而 Serial Old 收集器是单线程收集器,回收性能低下,这就会导致长时间的 STW。
当然,针对这个情况,CMS 也提供了一些参数来调优:
-XX:+UseCMSCompactAtFullCollection
:在进行Full GC之前进行一次内存整理
二、浮动垃圾问题
由于 CMS 的并发清除阶段是与应用线程同时进行的,应用程序在垃圾回收过程中会继续分配新对象。而这些新对象在本次 GC 周期中可能没有被标记为垃圾,因此这些对象会残留在内存中,形成所谓的“浮动垃圾”。浮动垃圾无法被本次 GC 回收,需要等待下一次GC才能处理。这意味着 CMS 可能在垃圾回收后,仍然会留下部分垃圾未清理干净。
当然,浮动垃圾本身不会有特别大的问题,等待下次 GC 回收即可。