知道垃圾收集(GC)如何在Java中工作有什么好处?满足作为软件工程师的智力好奇心是一个有效的原因,而且了解GC如何帮助您编写更好的Java应用程序。

这是对我的一个非常个人和主观的意见,但我相信一个精通GC的人往往是一个更好的Java开发人员。如果您对GC流程感兴趣,那意味着您有开发特定尺寸应用程序的经验。如果您仔细考虑了选择正确的GC算法,那意味着您完全了解您开发的应用程序的功能。当然,这可能不是一个好的开发者的常见标准。然而,当我说,理解GC是一个伟大的Java开发人员的要求时,很少有人反对。 

这是“ 成为Java GC专家 ”系列文章中的第一篇。我将在介绍GC介绍,下一篇文章将介绍从NHN分析GC状态和GC调优示例。

在学习GC之前,您应该知道一个术语。这个词是“ 停止世界”无论您选择哪种GC算法,都会发生世界。Stop-the-world意味着JVM正在停止运行应用程序以执行GC。当世界发生时,除了GC所需的线程之外的每个线程都将停止其任务。只有GC任务完成后,中断的任务才会恢复。GC调整通常意味着减少这个世界时间。

世代垃圾收集 

Java没有明确指定内存并将其删除在程序代码中。有些人将相关对象设置为null或使用System.gc()方法显式删除内存。将其设置为null并不是一件大事,但是调用System.gc()方法会严重影响系统性能,不能执行。(幸运的是,我还没有看到NHN中的任何开发人员称这个方法。)

在Java中,由于开发人员未明确删除程序代码中的内存,垃圾收集器会发现不必要的(垃圾)对象并将其删除。这个垃圾收集器是基于以下两个假设创建的。(将它们称为假设或先决条件,而不是假设是更正确的。) 

 

  • 大多数对象很快就不可达。
  • 从老对象到年轻对象的引用仅存在于少数。

 

这些假设被称为弱代数假说所以为了保持这个假设的优势,它在物理上被分为两代一代 - 在HotSpot VM中。

年轻一代:大多数新创建的对象位于此处。由于大多数对象很快就不可达,许多对象都是在年轻一代中创建的,然后消失。当对象从这个区域消失时,我们说“发生小的GC ”。 

老一辈:这些没有变得无法实现的,从年轻一代幸存下来的物品被复制在这里。它通常比年轻一代大。由于GC尺寸较大,因此GC的发生频率低于年轻一代。当对象从老一代消失时,我们说发生了一个“ 主要的GC ”(或“ 全面GC ”)。 

我们来看看这个图表。

图1:GC区域和数据流。

图1:GC区域和数据流。

上图中永久代也被称为“ 方法区 ”,它存储类或内部字符串。所以,这个地区绝对不是从老一辈那里幸存下来的永久性的物品。此区域可能会出现GC。这里发生的GC仍被视为主要的GC。 

有人可能会想:

如果老一代的物体需要引用年轻一代的物体怎么办?

为了处理这些情况,在旧的一代有一个叫做“ 卡表 ”的东西,这是一个512字节的块每当旧代中的对象引用年轻一代中的对象时,它被记录在该表中。当为年轻一代执行GC时,仅搜索该卡表以确定其是否受GC的限制,而不是检查旧代中所有对象的引用。这张卡表是用写屏障管理的这种写入障碍是允许较小的GC更快的性能的设备。虽然由于这个原因而发生了一些开销,但总体GC时间减少了。 

图2:卡表结构。

图2:卡表结构。

年轻一代的组成

为了了解GC,让我们先了解一下年轻一代,首次创建对象。年轻一代分为3个空间。 

 

  • 一个  伊甸园空间
  • 两个  幸存者空间

 

总共有3个空间,其中两个是幸存者空间。每个空间的执行顺序如下:

 

  1. 大多数新创建的对象位于Eden空间中。
  2. 在Eden空间中的一个GC之后,幸存的物体被移动到其中一个Survivor空间。 
  3. 在Eden空间中的GC之后,物体被堆积到Survivor空间中,其中存在其他幸存的物体。
  4. 幸存的空间一旦满了,幸存的物体就会移动到另一个幸存者的空间。然后,已满的幸存者空间将被改变为完全没有数据的状态。
  5. 在这些步骤中幸存下来的物体已经重复了很多次,被移动到了老一代。

 

通过检查这些步骤可以看到,其中一个Survivor空格必须保持为空。如果在这两个Survivor空间中存在的数据,或两者兼有空间的使用是0,然后把它看作一个标志,什么是错的与您的系统

通过小型GC堆积成旧的数据的过程可以显示如下图所示:

图3:GC之前和之后。

图3:GC之前和之后。

请注意,在HotSpot VM中,有两种技术用于更快的内存分配。一个称为“ bump-the-pointer ”,另一个称为“ TLAB(Thread-Local Allocation Buffers)”。 

Bump-the-pointer技术跟踪分配给Eden空间的最后一个对象。该对象将位于伊甸园的顶部。并且如果之后创建了一个对象,它只检查对象的大小是否适合于Eden空间。如果所述对象似乎正确,它将被放置在Eden空间中,并且新对象在顶部。因此,当创建新对象时,只需要检查最后添加的对象,这样可以更快地进行内存分配。然而,如果我们考虑一个多线程的环境,这是一个不同的故事。为了保存Eden空间中线程安全使用的对象,将发生不可避免的锁定,并且由于锁定争用而导致性能下降。 TLAB是HotSpot VM中此问题的解决方案。 这允许每个线程具有对应于其自己的共享的其Eden空间的一小部分。由于每个线程只能访问自己的TLAB,所以即使是bump-the-pointer技术也将允许没有锁的内存分配。

这是对年轻一代的GC的快速概述。你不一定要记住我刚刚提到的两种技术。你不会不知道他们去监狱。但请记住,在伊甸园空间中首次创建物体后,长寿命的物体通过幸存者空间移动到老一代。

古代的GC

当数据已满时,旧版本基本上执行GC。执行程序因GC类型而异,因此如果您知道不同类型的GC,将会更容易理解。 

根据JDK 7,有5种GC类型。 

 

  1. 串行GC
  2. 并行GC
  3. 并联旧GC(平行压缩GC)
  4. 并发Mark&Sweep GC(或“CMS”)
  5. 垃圾第一(G1)GC

 

其中,串行GC不能在操作服务器上使用当GC台式机上只有一个CPU内核时,就会创建此GC类型。使用此串行GC将显着降低应用程序性能。 

现在让我们了解每种GC类型。

串行GC(-XX:+ UseSerialGC)

年轻一代的GC使用我们在前一段解释的类型。旧的GC使用一种称为“ mark-sweep-compact ” 的算法

 

  1. 该算法的第一步是标记老一代的幸存对象。
  2. 然后,它从前面检查堆,只留下幸存的人(扫)。
  3. 在最后一步,它从前面的堆填充对象,使对象被连续堆叠,并将堆分成两部分:一个对象,一个没有对象(compact)。

 

串行GC适用于小内存和少量CPU内核。

并行GC(-XX:+ UseParallelGC)

图4:串行GC和并行GC之间的差异。

图4:串行GC和并行GC之间的差异。

从图中可以看出串行GC和并行GC之间的区别。虽然串行GC仅使用一个线程来处理GC,但并行GC使用多个线程来处理GC,因此更快。当有足够的内存和大量的内核时,这个GC很有用。它也被称为“ 吞吐量GC”。 

并行旧GC(-XX:+ UseParallelOldGC)

自JDK 5更新以来,支持Parallel GC。与平行GC相比,唯一的区别是古代的GC算法。它通过三个步骤:标记 - 总结 - 压实总结步骤针对GC先前执行的区域分别识别存活对象,因此与标记扫描紧凑算法的扫描步骤不同。它经历了一些更复杂的步骤。

CMS GC(-XX:+ UseConcMarkSweepGC)

图5:串行GC和CMS GC。

图5:串行GC和CMS GC。

从图中可以看出,并发的Mark-Sweep GC比我迄今为止所解释的任何其他GC类型复杂得多。早期的初步标记步骤很简单。搜索距离类加载器最近的对象中的幸存对象。所以暂停时间很短。并发标记步骤中,跟踪并检查刚被确认的幸存对象所引用的对象。这一步的区别在于其它线程同时被处理。备注步骤中,检查在并行标记步骤中引用新添加或停止的对象。最后,在同步扫描步骤中,进行垃圾收集过程。垃圾收集在其他线程仍在处理时进行。由于以这种方式执行该GC类型,因此GC的暂停时间非常短。CMS GC也称为低延迟GC, 当所有应用程序的响应时间至关重要时使用。 

虽然这种GC类型具有短暂的世界时间的优点,但它也具有以下缺点。

 

  • 它比其他GC类型使用更多的内存和CPU。
  • 默认情况下不提供压缩步骤。

 

使用此类型之前,您需要仔细检查。另外,如果由于许多内存碎片而需要执行压缩任务,那么停止世界的时间可能比任何其他GC类型都要长。您需要检查压实任务执行的频率和时间。

G1 GC

最后,让我们先了解一下垃圾(G1)GC。 

图6:G1 GC的布局。

图6:G1 GC的布局。

如果您想了解G1 GC,请忘记一切您对年轻一代和老一代的了解。如图所示,一个对象被分配给每个网格,然后执行一个GC。然后,一旦一个区域已满,将对象分配给另一个区域,然后执行GC。在这种GC类型中,无法找到数据从年轻一代的三个空间移动到旧一代的步骤。这种类型是为了取代CMS GC,长期以来引起了很多问题和投诉。

G1 GC的最大优点是其性能它比我们迄今为止讨论的任何其他GC类型快。但是在JDK 6中,这被称为早期访问,只能用于测试。它正式包含在JDK 7中。在我个人看来,我们需要经历漫长的测试期(至少1年),然后NHN可以在实际服务中使用JDK7,所以你可能应该等一下。另外,在JDK 6应用G1之后,我听到几次JVM崩溃发生。请稍候,直到它更稳定。

我将在下一期讨论GC调整问题,但我想提前问一件事情。如果在应用程序中创建的所有对象的大小和类型相同,我们公司使用的所有WAS选项都可以相同。但由WAS创建的对象的大小和使用寿命因服务而异,并且设备类型也不尽相同。换句话说,只是因为某个服务使用GC选项“A”,并不意味着相同的选项将为不同的服务带来最好的结果。通过不断调整和监控,有必要为每个设备和每个GC选项找到WAS线程,WAS实例的最佳值。这不是来自我的个人经验,而是来自于为JavaOne 2010制作Oracle JVM的工程师的讨论。

在这个问题上,我们只看了一下Java的GC。请期待我们的下一个问题,我将在此讨论如何监控Java GC状态并调整GC。 

我想注意的是,我提到2011年12月发布的名为“ Java性能 ”的新书亚马逊,也可以从Safari在线查看,如果该公司提供了一个帐户),以及“ Java中的内存管理HotSpotTM虚拟机 “,由Oracle网站提供的白皮书。(本书与“ Java性能调优 ”不同)