转载

再谈Java GC

GC是影响JVM性能的重要因素,不同jvm厂商、版本实现了不同垃圾收集器和算法,各有优缺点。本文就jvm内存划分做进一步补充说明并聊聊各种常见的垃圾收集器,它们的基本算法和使用场景以及一些GC调优的方法。

GC的基本原理与概念

结合jvm内存划分我们知道GC通常是运行在堆内存上的,有时方法区也会运行GC。要进行垃圾回收,我们首先要知道哪些对象可以被回收,这里主要有 引用计数法可达性分析 两种方法标记对象是否需要回收。

  • 引用计数法。堆上对象引用没增加一次计数加1,否则减1。相比于可达性分析这种方法的吞吐量更高,但是java并没有采用引用计数法,因为他没办法发现循环引用问题
  • 可达性分析。简单说把栈上的引用作为GC Roots,根据他们的依赖对象构建出对象关系依赖图。对于不可达的对象和标记为软引用、弱引用的对象进行回收标记处理。这种方法可以打破循环引用但是运算逻辑更复杂,系统资源占用更多。

对于方法区的GC复杂一点,首先Bootstrap Classloader加载的类通常是不会被回收的,其他普通加载器加载的类的回收要求加载该类的加载器被回收了才能进行,对于大量使用动态类型的应用要注意方法区内存避免方法区OOM。

具体清理对象的时候通常有三种算法,复制、标记清除、标记整理:

  • 复制(Copying):将内存空间分为两份分别为from、to,只使用其中的一份from。当GC发生时将所有不需要清除的对象复制到to区域,这样不存在内存碎片,但是需要维护原对象引用值变化的开销,同时预留的两份空间降低了内存使用率。
  • 标记清除(Mark-Sweep):标记然后进行清除,这个过程不可避免的会产生内存碎片。
  • 标记整理(Mark-Compact):在标记清除中存在内存碎片问题,而标记整理算法多一步将清理后的内存做对齐压缩,消除减少内存碎片。

常见垃圾收集过程

再谈Java GC 垃圾收集过程

上面算法不是单独运行在虚拟机上的,通常他们都会配合使用。对于垃圾收集通常会将内存分为 Eden , Survivor , Tenured 区如上图所示。通常新生代(New Generation)指Eden和Survivor,老年代(Old Generation)指Tenured。

  • 新java对象通常创建在Eden区,当达到阀值时触发 minor GC 被引用存活下来的对象将被复制到一个Survivor区,并且生命值加1
  • 经过minor GC后Eden空闲,再次minor GC会将存活下的对象复制到另外一个Survivor区并且生命值加1,生命值达到阈值后除非晋升(Promotion)改对象被移到老年代
  • 老年代通常运行标记清除或标记整理法,称为 major GC 。达到某个阈值对整个内存进行整理的成为 Full GC

常见的垃圾收集器

  • Serial GC。最古老著名的GC,单线程,会触发 Stop-The-World 转态,但它实现简单高效实用很多场景,运行在新生代。(Serial Old是老年代收集器采用标记整理算法)。 -XX:+UseSerialGC
  • ParNew GC。运行在新生代,Serial GC的多线程版本,可以配合CMS GC使用 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
  • CMS(Concurrent Mark Sweep) GC。基于标记清除(Mark-Sweep)算法,并发,可以和工作线程同时执行减少时延,通常用于web领域。但是由于标记清除产生内存碎片长时间会引发Full GC导致长时间停顿,另外并发也会占用工作线程资源。
  • Parallel GC。保证吞吐量优先,并行运行成分利用CPU资源,实现上比Serial复杂很多。新生代叫做Parallel Scavenge,老年代叫Parallel Old。
  • G1。G1是目前看最先进的垃圾收集器,JDK 9以后默认选项。G1仍有年代划分,但并不是线性的,而是多个region类似棋盘。region之间使用复制法,内部使用标记整理(Mark-Compact)法。G1也提供了停顿时间控制选项。

另外还有一些正在研发中各有特点的GC,如 ZGC Oracle开源的超级GC,支持Tbytes级别的超大堆,而且大部分情况下GC延时低于10ms。 Epsilon GC 只有开销但是不做GC,用来做性能测试。

GC 调优思路

GC优化是JVM优化的一个场景,通常JVM优化的很多方面最终都会落到GC优化上。遇到所有优化的场景一定要明确要优化的目标参数是什么。GC中通常关注三个方面,内存占用(footprint)、延时(latency)、吞吐量(throughput)。有时不合理的GC策略也能可能会造成OOM。

面对GC调优通常需要确定确实有调优的必要,然后使用jstat查看GC相关状态,开启GC日志并分析定位问题。分析选择的垃圾回收器是否符合应用的表现特征,是minor gc过长还是major gc过长,尝试使用CMS,G1 这种低延迟的垃圾回收器。

原文  https://www.timqi.com/2019/04/10/talk-about-java-gc-again/
正文到此结束
Loading...