JAVA garbage collector

原文地址

在2014年对于有两件事对大多数开发着来说仍然是个迷-垃圾回收和理解异性。关于后者我懂得不多,所以我想谈谈前者。特别是JAVA8在这个领域已经做了很大的改变和提高,特别是移除了永久代和一些新的令人兴奋的变化。

当我们谈论垃圾回收,我们中的大多数都知道这个概念并且在每天你的编程过程中使用它。即使如此,对于垃圾回收有很多我们不清楚的地方。其中最大的错误概念是JVM只有一个垃圾回收器,而事实上JVM提供了4中不同的各有优缺点的回收器。JVM并不会自动来选择,需要依赖你自己在吞吐量和程序响应速度两方面去抉择。

大部分堆中的对象都是朝生夕死的,将堆分成不同的段进行分代管理是4种垃圾收集的共同点。接下来我回直接来描述他们的不同点和优缺点。

串行收集器(The Serial Collector)

串行收集器时最简单的一个,你基本上不会使用它,它主要时在单线程并且内存很小的环境下使用,收集器在工作时会暂停应用线程,因此它不适合使用在服务器环境中。 如何使用:-XX:+UseSerialGC

并行/吞吐量收集器(The Parallel/Throughput collector)

接下来是并行收集器。这是JVM默认的收集器。正如它的名字,它最大的优点使用多线程去扫描和压缩堆。问题是并行收集器在执行新生代GC和完整GC时会停止应用线程。并行收集最适合可以忍受应用暂停并且尝试优化收集器的CPU使用率。

同步标记清除收集器(The CMS Collector)

CMS(concurrent-mark-sweep)它的算法是使用多个同步线程扫描堆进行标记和对不使用对象进行清理。这个算法在两种情况下会暂停应用线程:初始化标记根节点和当同步算法运行后对在这段时间堆中的改变进行再标记。 使用此收集器最大的问题是遇到保证失败,这是因为老年代预留的空间不足以存放新生代升级过来的对象。如果收集时需要升级新生代到老年代,但是没有足够的空间去存放这些对象时,就会触发全GC,造成应用程序暂停,这是CMS收集器极力避免的。为了确保这种情况不会发生,你需要增大老年代或者整个堆的大小并且分配更多的线程给收集器,以便让对象回收的速度大于分配。 与并行收集器相比,该算法会占用更多的CPU,以便分配更多的线程来做扫描和收集。对于很多长时间运行的服务应用来说,这种算法的好处时减少停顿时间。

如何使用:-XX:+USeParNewGC

假设你的应用内存小于4G,并且你可以分配更多的CPU资源来避免应用停顿你可以选择CMS,如果应用内存大于4G推荐你使用最新的算法-G1收集器。

G1收集器(The G1 Collector)

G1收集器首次被介绍是在JDK7u4,它被设计用来处理大于4GB内存的应用程序,G1收集器使用多个线程来扫描每一个独立的区域,区域的大小是1MB-32MB。G1收集器首先扫描出包含最多垃圾对象的区域进行标记。 策略会避免产生堆耗尽导致后台线程没有完成对未使用对象的扫描,从而减少STW出现的情况。另一个好处是在不STW的情况下压缩移动堆。 在过去几年中,大堆已经成为一个相当有争议的领域,许多开发人员从每个机器模型的单个JVM迁移到更多的微服务,组件化架构,每个机器具有多个JVM。这是由许多因素驱动的,包括隔离不同应用程序部分的需求,简化部署并避免将应用程序类重新加载到内存中所需的成本。 即使如此,对JVM而言,最大的驱动因素之一就是希望避免那些长期“停止世界”的停顿(在大型集合中可能需要很多秒钟)发生在大堆中。Docker等容器技术也加速了这一点,使您能够在相同的物理机器上轻松部署多个应用程序。

如何使用:–XX:+UseG1GC

JAVA8与G1收集器

另一个美丽的优化是在java8u20里G1收集器的字符串不重复。当字符串占据了我们堆的大部分空间,新的优化可以使G1收集器唯一标示那些不知出现一次的字符串,使他们都指向同一个char数组,以避免相同字符串的多个副本在堆内无效率地驻留。

如何使用:-XX:+UseStringDeduplication

JAVA8和永久代

Java8的一个最大的改变就是移除了堆中的永久代(存储类的元数据,字符串和静态变量),这个在老的java版本中要求开发者对那些加载大量类的应用针对这个区域进行优化和调整。这多年来成为了OutOfMemory异常的源头,所以JVM本身对其进行处理时非常好的补充。当然,这样也不会减少开发者希望将自己的应用解耦到多个JVM的浪潮