Java中的内存管理 HotSpot™虚拟机

本文详细介绍了Java 5.0 HotSpot JVM中的内存管理,包括自动内存管理和垃圾收集器的工作原理。重点讲解了四种垃圾收集器,如串行、并行、并行压缩和CMS,以及新的人体工程学技术,自动选择和动态调整堆大小。此外,还提供了选择和配置垃圾收集器的建议及处理内存溢出的策略。

1.介绍

        Java™2平台标准版(J2SE™)的一个优点是它执行自动内存管理,从而使开发人员免受显式内存管理的复杂性。本文概述了Java HotSpot虚拟机(JVM)中的内存管理Sun的J2SE 5.0发布。它描述了可用于执行内存管理的垃圾收集器,以及给出了一些关于选择和配置收集器以及设置内存区域大小的建议这是收集器操作的。它还作为一种资源,列出了一些最常用的选项影响垃圾收集器的行为,并提供大量链接到更详细的文档。第2节是为不熟悉自动内存管理概念的读者准备的。它有一个简短的讨论这种管理相对于要求程序员显式地为数据释放空间的好处。第3节将概述一般垃圾收集概念、设计选择和性能指标。它还将一个常用的内存组织引入到称为代的不同区域基于对象的预期寿命。事实证明,这种代际分离在减少数量方面是有效的各种应用程序中的垃圾收集暂停时间和总体成本。本文的其余部分提供了特定于HotSpot JVM的信息。第4节介绍了四种垃圾收集器,包括J2SE 5.0更新6中新增的收集器,并记录该代他们都利用记忆组织。对于每个收集器,第4节总结了收集算法的类型使用并指定何时选择该收集器合适。第5节描述了J2SE 5.0发布中的一项新技术,它结合了(1)自动选择垃圾收集器、堆大小和HotSpot JVM(客户端或服务器)基于其上的平台和操作系统应用程序正在运行,(2)基于用户指定的期望行为进行动态垃圾收集调优。这种技术被称为人体工程学。第6节提供了选择和配置垃圾收集器的建议。它还提供了一些关于如何处理OutOfMemoryErrors的建议。第7节简要描述了一些可用的工具第8节列出了最常用的命令行与垃圾收集器选择和行为相关的选项。最后,第9节提供了更详细的链接本文涵盖的各种主题的文档

2.显式(手动)内存管理vs.自动内存管理

  • 内存管理是识别分配对象何时不再需要的过程(释放)这些对象使用的内存,并使其可用于后续分配。
  • 在一些编程语言中,内存管理是程序员的职责。
  • 任务的复杂性导致许多常见错误,可能导致意外或错误的程序行为和崩溃。
  • 开发人员经常花费大量的时间来调试和纠正这些错误。
  • 使用显式内存管理的程序中经常出现的一个问题是悬空引用。它是可能释放一个对象所使用的空间,而其他对象仍然有这个个引用。
  • 如果该对象使用该(悬空)引用尝试访问原始对象,但空间已被重新分配给一个新的对象,结果是不可预测的,而不是预期的。
  • 显式内存管理的另一个常见问题是空间泄漏。
  • 这些泄漏发生在内存不足时已分配,不再引用,但不释放。
  • 例如,如果您想释放一个,但是你犯了一个错误,就是释放了链表的第一个元素,也就是剩下的元素不再被引用,但它们超出了程序的范围,既不能使用也不能恢复。
  • 如果出现足够的泄漏,它们就会不断消耗内存,直到用尽所有可用内存。
  • 内存管理的一种替代方法,现在被广泛使用,特别是被大多数现代应用
  • 面向对象的语言,是由一个叫做垃圾收集器的程序自动管理的。
  • 自动内存管理可以增加接口的抽象和更可靠的代码。
  • 垃圾收集避免了悬空引用问题,因为对象仍然在某处被引用
  • 永远不会被垃圾收集,所以不会被认为是免费的。
  • 垃圾收集还解决了空间泄漏问题
  • 上面描述的问题,因为它自动释放所有不再引用的内存。

3.垃圾收集概念

垃圾收集器职责
(1)分配内存
(2)确保所有被引用的对象都在内存中,并且恢复在执行代码中不能从引用中访问的对象所占用的内存。

  • 被引用的对象被称为活动的。不再被引用的对象被认为是没用的,称为垃圾。
  • 查找和释放(也称为回收)这些对象使用空间的过程被称为垃圾收集。
  • 垃圾收集解决了许多(但不是所有)内存分配问题。
  • 例如,您可以创建对象无限期地并继续引用它们,直到没有更多可用内存为止。
  • 垃圾收集也是一种复杂的任务本身就需要时间和资源。
  • 用于组织内存和分配和释放空间的精确算法由垃圾收集器处理
  • 空间通常是从引用的大内存池中分配的作为堆。
  • 垃圾收集的时间由垃圾收集器决定。整个堆或它的一个子部分。
  • 当它被填满或当它达到一个阈值占用百分比时收集。
  • 完成分配请求的任务,包括在内存中找到一个特定大小的未使用内存块,是一个困难的问题。
  • 大多数动态内存分配算法的主要问题是如何避免碎片,同时保持分配和回收的效率。

理想的垃圾收集器特性

  • 垃圾收集器必须既安全又全面。
  • 也就是说,永远不能错误地释放实时数据,垃圾不应该在超过几个回收周期内无人认领。还希望垃圾收集器能够有效地运行,而不引入长时间的暂停应用程序未运行。
  • 然而,与大多数计算机相关的系统一样,两者之间经常存在权衡时间、空间和频率。
  • 例如,如果堆大小较小,收集将很快,但堆将被填满更快,因此需要更频繁的收集。
  • 相反,一个较大的堆将需要更长的时间来填充和这样收集的频率就会降低,但可能需要更长的时间。
  • 垃圾收集器的另一个可取的特性是碎片的限制。
  • 当垃圾对象被释放后,空闲空间可能会以小块的形式出现在不同的区域,这样就可能不会出现在任意一个连续区域中有足够的空间用于分配大对象。
  • 一种方法消除碎片称为压缩,下面将讨论各种垃圾收集器设计选择。
  • 可伸缩性也很重要。分配不应该成为多处理器系统上多线程应用程序的可伸缩性瓶颈,收集也不应该成为这样的瓶颈。

设计选择

  • 在设计或选择垃圾收集算法时,必须做出许多选择:

(1)串行vs并行
使用串行收集,一次只发生一件事。
例如,即使有多个cpu可用,只有一个用于执行收集的任务

当使用并行收集时,垃圾收集的任务被分成多个部分,这些子部分在不同的地方同时执行cpu。同时进行的操作使收集可以更快地完成,但代价是增加一些复杂性和潜在的碎片。

(2)并发vsStop-the-world

当执行stop-the-world垃圾收集时,应用程序的执行将在收集期间完全挂起。或者,一个或多个垃圾收集任务可以并发执行,也就是说,与应用程序同时执行。通常,并发垃圾收集器并发执行其大部分工作,但有时也必须进行一些短暂的暂停。Stop-the-world垃圾收集比并发收集更简单,因为堆是冻结的,并且在收集期间对象不会改变。它的缺点是,某些应用程序可能不希望暂停。相应地,当垃圾收集并发执行时,暂停时间会更短,但收集器必须格外小心,因为它操作的对象可能同时被应用程序更新。这给影响性能的并发收集器增加了一些开销,需要更大的堆大小。

(3)压缩vs非压缩vs复制

在垃圾收集器确定内存中哪些对象是活动的,哪些是垃圾后,它可以压缩内存,将所有活动对象移动到一起,并完全回收剩余的内存。压缩之后,在第一个空闲位置分配一个新对象是很容易和快速的。一个简单的指针可以用来跟踪对象分配的下一个可用位置。与压缩收集器相比,非压缩收集器会就地释放垃圾对象所利用的空间,也就是说,它不会像压缩收集器那样移动所有活动对象来创建一个大的回收区域。好处是更快地完成垃圾收集,但缺点是潜在的碎片化。通常,从堆中进行就地回收分配比从压缩堆分配代价更高。可能需要在堆中搜索足够大的连续内存区域来容纳新对象。第三种替代方法是复制收集器,它将活动对象复制(或疏散)到不同的内存区域。这样做的好处是,可以将源区域视为空区域,以便快速、轻松地进行后续分配,但缺点是复制需要额外的时间和可能需要的额外空间。

性能标准

  • 有几个指标被用来评估垃圾收集器的性能,包括:

(1)吞吐量——在长时间内考虑的不用于垃圾收集的总时间百分比。

(2)垃圾收集开销—吞吐量的倒数,即花费在垃圾收集上的总时间的百分比。

(3)暂停时间——在进行垃圾收集时停止应用程序执行的时间长度。

(4)收集频率——收集发生的频率,相对于应用程序执行。

(5)占用空间——大小的度量,例如堆大小。

(6)及时性——对象变为垃圾和内存变为可用之间的时间。

交互式应用程序可能需要较低的暂停时间,而总体执行时间对非交互式应用程序更重要。实时应用程序对垃圾收集暂停和在任何时期花费在收集器上的时间比例都要求很小的上限。小内存占用可能是运行在小型个人计算机或嵌入式系统中的应用程序的主要关注点。

分代回收

当使用一种称为分代收集的技术时,内存被划分为几代,即保存不同年龄对象的独立池。例如,最广泛使用的配置有两代:一代用于年轻对象,一代用于旧对象。

可以使用不同的算法在不同的代中执行垃圾收集,每种算法都是根据特定代的常见特征进行优化的。分代垃圾收集利用了以下观察结果(称为弱分代假设),涉及使用多种编程语言(包括Java编程语言)编写的应用程序:

(1)大多数分配的对象没有被引用(被认为是活的),也就是说,它们在年轻时就死去了。

(2)从老对象到年轻对象的引用很少存在。

年轻代回收相对频繁且高效、快速,因为年轻代空间通常较小,并且可能包含许多不再被引用的对象。

在一些年轻代回收中存活下来的对象最终会被提升到年老代。参见图1。这一代通常比年轻一代人数多,入住率增长更慢。因此,老一代的收集并不频繁,但是需要更长的时间来完成。

为年轻代选择的垃圾收集算法通常注重速度,因为年轻代收集是频繁的。另一方面,老一代通常由一个更有效的空间算法来管理,因为老一代占用了堆的大部分,老一代算法必须在较低的垃圾密度下工作。

4.J2SE 5.0 HotSpot JVM中的垃圾收集器

从J2SE 5.0更新6开始,Java HotSpot虚拟机包括四个垃圾收集器。本节描述集合的代和类型,并讨论为什么对象分配通常是快速和有效的。然后提供关于每个收集器的详细信息。

HotSpot Generations

Java HotSpot虚拟机中的内存被组织为三代:年轻一代、老一代和永久一代。大多数对象最初是在年轻代中分配的。老一代包含在年轻代收集中存活下来的对象,以及在老一代中直接分配的一些大对象。永久代保存JVM认为方便由垃圾收集器管理的对象,例如描述类和方法的对象,以及类和方法本身。

年轻代由一个称为Eden的区域和两个较小的survivor区组成,如图2所示。
大多数对象最初是在Eden中分配的。(如前所述,一些大对象可以在老一代中直接分配。)survivor区保存至少在年轻代收集中存活下来的对象,因此在被认为“足够老”而被提升到年老代之前有额外死亡机会的对象。在任何给定的时间,一个survivor区(图中标记为From)保存这样的对象,而另一个是空的,直到下一次收集时才使用(两个survivor区循环使用)。

垃圾收集类型

当年轻代填满时,只对这一代执行年轻代收集(有时称为小收集)。当旧代或永久代填满时,通常会完成所谓的完整收集(有时称为主要收集)。也就是说,所有的代都被收集。
通常,首先收集年轻代,使用专为这一代设计的收集算法,因为它通常是识别年轻代中垃圾的最有效算法。那么对于给定的收集器,下面所提到的老代收集算法将同时在老代和永久代上运行。如果发生压缩,则每个生成都单独压缩。

有时老年代太满,无法接受所有可能从年轻代提升到老年代的对象,如果年轻代先被收集。在这种情况下,除了CMS收集器,年轻代收集算法未运行。相反,在整个堆上使用老年代收集算法。(CMS老年代算法是一种特殊情况,因为它不能收集年轻代。)

快速分配

正如您将从下面的垃圾收集器描述中看到的,在许多情况下,有大量连续的内存块可用来分配对象。使用简单的“碰指针”技术,从这样的块进行分配是有效的。也就是说,总是跟踪先前分配的对象的末尾。当需要满足一个新的分配请求时,所需要做的就是检查对象是否适合生成的剩余部分,如果适合,则更新指针并初始化对象。

对于多线程应用程序,分配操作需要是多线程安全的。如果使用全局锁来确保这一点,那么在生成中分配将成为瓶颈并降低性能。相反,HotSpot JVM采用了一种称为线程本地分配缓冲区(TLABs)的技术。这通过给每个线程自己的缓冲区(即生成的一小部分)来分配,从而提高了多线程分配吞吐量。由于只能将一个线程分配到每个TLAB中,因此可以利用颠簸指针技术快速进行分配,而不需要任何锁定。只有在很少的情况下,当一个线程填满了它的TLAB并且需要获得一个新的TLAB时,才必须使用同步。采用了几种技术来减少由于使用TLABs而造成的空间浪费。例如,TLABs按分配器分配大小,平均浪费不到1%的Eden。TLABs的使用和使用bump-the-pointer技术的线性分配的结合使得每个分配都是高效的,只需要大约10条本机指令。

串行收集器

使用串行收集器,年轻的和年老的收集都以串行方式(使用单个CPU)完成,以一种停止的方式。也就是说,在进行收集时,应用程序执行将暂停。

使用串行收集器收集年轻代

图3演示了使用串行收集器的年轻代收集操作。Eden中的活动对象被复制到最初空的survivor区,在图中标记为to,除了那些太大而不适用于to空间的对象。这样的对象直接复制到老一代。被占用的survivor区(标记为From)中仍然相对年轻的活动对象也被复制到其他survivor区,而相对较老的对象被复制到老年代。注意:如果To空间满了,来自Eden或from的未被复制到它的活对象将被保留,无论它们存活了多少年轻代集合。根据定义,在活的物体被复制后,任何留在Eden或from空间的物体都不是活的,它们不需要检查。(这些垃圾对象在图中以X标记,但实际上收集器并不检查或标记这些对象。)

年轻代收集完成后,Eden和以前占用的survivor区都是空的,只有以前空的survivor区包含活动对象。此时,survivor区交换角色。见图4。

使用串行收集器的老一代收集

使用串行收集器,旧代和永久代通过标记-清除-压缩收集算法进行收集。在标记阶段,收集器标识哪些对象仍然活动。扫描阶段“扫描”所有代,识别垃圾。收集器然后执行滑动压缩,将活动对象滑动到老代空间的开头(永久代也类似),将任何空闲空间留在另一端的单个连续块中。看到图5。压缩使将来分配给旧代或永久代的任何分配都可以使用快速的碰指针技术。

何时使用串行收集器

串行收集器是大多数运行在客户机样式机器上且不需要低暂停时间的应用程序的首选收集器。在当今的硬件上,串行收集器可以有效地管理许多具有64MB堆和相对较短的最坏情况暂停(对于完整收集,暂停时间不到半秒)的重要应用程序。

串行收集器选择

在J2SE 5.0版本中,串行收集器被自动选择为非服务器类机器上的默认垃圾收集器,如第5节所述。在其他机器上,可以通过使用-XX:+UseSerialGC命令行选项显式请求串行收集器。

并行收集器

现在,许多Java应用程序运行在具有大量物理内存和多个cpu的机器上。并行收集器(也称为吞吐量收集器)的开发是为了利用可用的数据
而不是让大多数cpu空闲,而只有一个cpu做垃圾收集工作。

使用并行收集器的年轻代收集

并行收集器使用串行收集器所使用的年轻代收集算法的并行版本。它仍然是一个停止和复制收集器,但是并行地执行年轻代收集,使用许多cpu,减少了垃圾收集开销,从而增加了垃圾收集开销
应用程序吞吐量。图6说明了年轻一代的串行收集器和并行收集器之间的区别。

使用并行收集器的老一代收集

并行收集器的老一代垃圾收集使用相同的串行完成mark-sweep-compact收集算法完成。

何时使用并行收集器

可以从并行收集器中受益的应用程序是那些运行在具有多个CPU且没有暂停时间限制的机器上的应用程序,因为虽然不频繁,但可能很长,旧的代收集仍然会发生。并行收集器通常适合的应用程序示例包括那些执行批处理、计费、工资、科学计算等的应用程序。您可能想要考虑选择并行压缩收集器(下面将介绍)而不是并行收集器,因为前者执行所有代的并行收集,而不仅仅是年轻代。

并行收集器选择

在J2SE 5.0版本中,并行收集器被自动选择为服务器类机器(在第5节中定义)上的默认垃圾收集器。在其他机器上,可以通过使用-XX:+UseParallelGC命令行选项显式请求并行收集器。

并行压缩收集器

并行压缩收集器是在J2SE 5.0更新6中引入的。它与并行收集器的区别在于,它使用了一种新的算法来进行老一代垃圾收集。注意:最终,并行压缩收集器将取代并行收集器。

使用并行压缩收集器的年轻代收集

并行压缩收集器的年轻代垃圾收集使用与并行收集器的年轻代垃圾收集相同的算法。

使用并行压缩收集器的老一代收集

使用并行压缩收集器,旧代和永久代将以一种“STW”的方式进行收集,这种方式主要采用滑动压缩的并行方式。收集器使用三个阶段。首先,每一代在逻辑上被划分为固定大小的区域。在标记阶段,可以从应用程序代码直接访问的初始活动对象集被划分到垃圾收集线程中,然后并行标记所有活动对象。当对象被标识为活动对象时,其所在区域的数据将被更新为对象的大小和位置信息。摘要阶段操作的是区域,而不是对象。由于以前的集合的压缩,每一代的左边的某些部分通常会很密集,包含大部分活动对象。可以从如此密集的区域中回收的空间量不值得压缩它们的成本。所以总结阶段要做的第一件事就是检查区域的密度,从最左边的区域开始,直到它到达一个点,在这个点上,可以从一个区域和右边的区域中恢复的空间值得压缩这些区域的成本。在该点左边的区域被称为密集前缀,在这些区域中没有对象被移动。该点右侧的区域将被压缩,消除所有死空间。汇总阶段计算并存储每个压缩区域的实时数据的第一个字节的新位置。注意:摘要阶段目前作为串行阶段实现;并行化是可能的,但对性能的重要性不如标记和压缩阶段的并行化。在压缩阶段,垃圾收集线程使用汇总数据来标识需要填充的区域,并且线程可以独立地将数据复制到区域中。这就产生了一个在一端密集堆积的堆,在另一端有一个大的空块。

何时使用并行压缩收集器

与并行收集器一样,并行压缩收集器有利于在具有多个CPU的机器上运行的应用程序。此外,老年代收集的并行操作减少了暂停时间,并使并行压缩收集器比并行收集器更适合有暂停时间限制的应用程序。并行压缩收集器可能不适合运行在大型共享机器(如SunRays)上的应用程序,在这种机器上,没有一个应用程序应该长时间独占多个cpu。在这样的机器上,可以考虑减少用于垃圾收集的线程数(通过-XX:ParallelGCThreads=n命令行选项)或选择一个不同的收集器。

并行压缩收集器选择

如果您希望使用并行压缩收集器,您必须通过指定命令行选项-XX:+UseParallelOldGC来选择它。

Concurrent Mark-Sweep (CMS) 收集器

对于许多应用程序,吞吐量没有快速响应时间那么重要。年轻代收集通常不会导致长时间的暂停。然而,老一代收集虽然不常见,但可能会导致长时间的暂停,特别是在涉及大堆时。为了解决这个问题,HotSpot JVM包括一个称为并发标记-扫描(CMS)收集器的收集器,也称为低延迟收集器。

使用CMS收集器收集年轻代

CMS收集器与并行收集器相同的方式收集年轻代。

使用CMS收集器收集老年代

使用CMS收集器的老年代的大部分收集都是与应用程序的执行同时完成的。

CMS收集器的收集周期从一个短暂的暂停开始,称为初始标记,它标识从应用程序代码直接可到达的初始活动对象集。然后,在并发标记阶段,收集器标记所有可从该集合传递到的活动对象。因为在标记阶段进行时应用程序正在运行并更新引用字段,所以不能保证在并发标记阶段结束时标记所有活动对象。要处理此问题,应用程序再次停止进行第二次暂停(称为remark),该暂停通过重新访问在并发标记阶段中修改的任何对象来结束标记。因为标记暂停比初始标记更重要,多个线程并行运行以提高其效率。

在标记阶段结束时,保证堆中的所有活动对象都已被标记,因此后续的并发清除阶段将回收已标识的所有垃圾。图7说明了使用串行标记-清除-压缩收集器和CMS收集器的老一代收集之间的区别。

由于某些任务(比如在标记阶段重新访问对象)增加了收集器必须做的工作量,因此它的开销也会增加。对于大多数试图减少暂停时间的收集器来说,这是一种典型的折中。

CMS收集器是唯一的非压缩收集器。也就是说,在释放死对象占用的空间之后,它不会将活对象移动到老年代的一端。参见图8所示。

这节省了时间,但是由于空闲空间不是连续的,收集器不能再使用一个简单的指针来指示下一个对象可以分配到的空闲位置。相反,它现在需要使用一个空闲位置。也就是说,它创建一些列表将未分配的内存区域链接在一起,每次需要分配一个对象时,必须搜索适当的列表(基于所需的内存量),以找到一个足够大的区域来容纳该对象,老年代的分配比简单的“碰指针”技术更浪费性能(好比一个老奶奶在天安门广场打扫游客的垃圾,单线程执行可想而知有多慢!!!)。这也增加了年轻代收集的额外开销,因为在年轻代收集期间提升对象时,老年代中的大多数分配都发生了。

CMS收集器的另一个缺点是需要比其他收集器拥有更大堆的大小。如果允许应用程序在标记阶段运行,那么它可以继续分配内存,从而有可能继续分配到老年代。此外,尽管收集器保证在标记阶段识别所有活动对象,但一些对象可能在标记阶段成为垃圾,直到下一次老年代回收时才会回收。这样的对象被称为浮动垃圾。

最后,由于缺乏压缩,可能会出现碎片。为了处理碎片,CMS收集器跟踪执行的对象大小,估计未来的需求,并可能分割或连接空闲块以满足需求。

与其他收集器不同,CMS收集器不会在老年代满时启动老年代收集。相反,它试图尽早开始一个集合,以便在它发生之前完成。否则,CMS收集器将恢复到并行和串行收集器使用的更耗时的停止标记-清除-压缩算法。为了避免这种情况,CMS收集器根据有关以前收集时间的统计信息以及老年代被占用的速度来启动。如果老年代的占用率超过了所谓的初始占用率,CMS收集器也将开始启动一个收集。初始占用率的值由命令行选项设置-XX: cmsinitiatingcupancyfraction =n,其中n是老年代大小的百分比。缺省值默认是68。

总之,与并行收集器相比,CMS收集器减少了老年代暂停(有时非常显著),代价是稍长的年轻代暂停、吞吐量的一些减少和额外的堆大小需求。

增量方式

CMS收集器可以在以增量方式完成并发阶段的模式下使用。此模式旨在通过定期停止并发阶段以将处理返回给应用程序,从而减少长并发阶段的影响。收集器所做的工作被划分为小块的时间,这些时间在年轻代收集之间调度。当需要并发收集器提供的低暂停时间的应用程序运行在具有少量处理器(例如,1或2)的机器上时,此特性非常有用
第9节中提到的“用5.0 Java™虚拟机调优垃圾收集”一文。

何时使用CMS收集器

如果应用程序需要更短的垃圾收集暂停时间,并且能够在应用程序运行时与垃圾收集器共享处理器资源,则使用CMS收集器。(由于并发性,CMS收集器在收集周期中从应用程序中占用CPU周期。)
通常,具有相对较大的长期数据集(较大的老年代)的应用程序,并且运行在具有两个或更多处理器的机器上,倾向于使用该收集器。web服务器就是一个例子。对于任何暂停时间要求较低的应用程序,都应该考虑使用CMS收集器。对于在单个处理器上具有中等大小的老年代的交互式应用程序,它也可能提供良好的结果。

CMS收集器选择

如果您希望使用CMS收集器,您必须通过指定命令行选项-XX:+UseConcMarkSweepGC显式地选择它。如果您希望它以增量模式运行,也可以通过-XX:+CMSIncrementalMode选项启用该模式。

5.人机工程学——自动选择和行为调整

在J2SE 5.0版本中,垃圾收集器、堆大小和HotSpot虚拟机(客户机或服务器)的默认值是根据应用程序运行的平台和操作系统自动选择的。这些自动选择能更好地满足不同类型应用程序的需要,同时比以前的版本需要更少的命令行选项。

此外,还为并行垃圾收集器添加了一种动态调优收集的新方法。使用这种方法,用户可以指定所需的行为,垃圾收集器可以动态调优堆区域的大小,以实现所请求的行为。与平台相关的默认选择和使用所需行为的垃圾收集调优的组合称为人机工程学。人机工程学的目标是通过最少的命令行调优从JVM提供良好的性能。

自动选择收集器、堆大小和虚拟机

服务器类机器被定义为

  • 2个或更多物理处理器和2个或更多g的物理内存

服务器类机器的这个定义适用于所有平台,除了运行Windows操作系统版本的32位平台。

在非服务器类机器上,JVM、垃圾收集器和堆大小的默认值是

  • 客户端JVM,串行垃圾收集器,初始堆大小为4MB,最大堆大小为64MB

否则,将使用与非服务器类机器相同的默认大小(4MB初始堆大小和64MB最大堆大小)。默认值总是可以被命令行选项覆盖。相关选项见第8节。

基于行为的并行收集器调优

在J2SE 5.0版本中,根据应用程序与垃圾收集相关的期望行为,为并行垃圾收集器添加了一种新的调优方法。命令行选项用于根据最大暂停时间和应用程序吞吐量的目标指定所需的行为。

(1)最大暂停时间目标

最大暂停时间目标是通过命令行选项指定的:-XX:MaxGCPauseMillis=n

这被解释为并行收集器需要n毫秒或更少的暂停时间的提示。并行收集器将调整堆大小和其他与垃圾收集相关的参数,以使垃圾收集暂停时间小于n毫秒。这些调整可能会导致垃圾收集器降低应用程序的总体吞吐量,并且在某些情况下无法满足所需的暂停时间目标。

最大暂停时间目标分别应用于每个生成。通常情况下,如果目标没有实现,这一代就会为了达到目标而缩小规模。默认情况下没有设置最大暂停时间目标。

(2)吞吐量目标(吞吐量 = 运行用户代码时间/(运行用户代码时间+垃圾收集时间)

吞吐量目标是根据执行垃圾收集的时间和垃圾收集之外的时间(称为应用程序时间)来衡量的。目标由命令行选项指定:-XX:GCTimeRatio=n

垃圾收集时间与应用程序时间的比率为:1 / (1 + n)

例如-XX:GCTimeRatio=19设置了垃圾收集总时间的5%的目标。默认目标是1%(即n= 99)。垃圾收集花费的时间是所有代的总时间。
如果吞吐量目标没有得到满足,则会增加代的大小,以增加应用程序在收集之间运行的时间。更大的一代需要更多的时间来填满。

(3)封装目标

如果满足了吞吐量和最大暂停时间目标,垃圾收集器就会减小堆的大小,直到其中一个目标(总是吞吐量目标)无法满足为止。没有达到的目标就会被处理。

(4)设置清晰的目标和重点

并行垃圾收集器首先尝试满足最大暂停时间目标。只有在它被满足之后,他们才处理吞吐量目标。类似地,只有在前两个目标达到后才考虑内存占用目标。

6.推荐规范

上一节中描述的人机工程学中自动垃圾收集器、虚拟机和堆大小的选择,这些选择对于大部分应用程序都是合理的。因此,选择和配置垃圾收集器的最初建议是什么都不做!也就是说,不要指定特定垃圾收集器的用法等等。让系统根据应用程序运行的平台和操作系统进行自动选择。然后测试您的应用程序。如果它的性能是可接受的,具有足够高的吞吐量和足够低的暂停时间,那么就完成了。您不需要排除故障或修改垃圾收集器选项。

另一方面,如果您的应用程序似乎存在与垃圾收集相关的性能问题,那么您首先可以做的最简单的事情是考虑默认选择的垃圾收集器是否合适,以给定您的应用程序和平台特征。如果不是,选择您认为合适的收集器,并查看性能是否变得可以接受。

您可以使用第7节中描述的工具来度量和分析性能。根据结果,您可以考虑修改选项,例如那些控制堆大小或垃圾收集行为的选项。第8节显示了一些最常见的指定选项。请注意:性能调优的最佳方法是先度量,然后调优。使用与代码实际使用情况相关的测试进行度量。另外,要注意过度优化,因为应用程序数据集、硬件等等——甚至是垃圾收集器实现!-可能会随着时间而改变。

本节提供有关选择垃圾收集器和指定堆大小的信息。然后,本文提供了对并行垃圾收集器进行调优的建议,并给出了一些关于如何处理内存不足错误的建议。

何时选择不同的垃圾收集器

第4节为每个收集器介绍了推荐使用该收集器的情况。第5节描述了默认情况下自动选择串行或并行收集器的平台。如果您的应用程序或环境特征允许使用不同于默认值的收集器,可以通过以下命令行选项之一显式请求收集器:

  • –XX:+UseSerialGC
  • –XX:+UseParallelGC
  • –XX:+UseParallelOldGC
  • –XX:+UseConcMarkSweepGC

堆大小

第5节给出了默认的初始堆大小和最大堆大小,这些初始的大小可能适合很多程序,但如果分析了一个性能问题(见第7节)或OutOfMemoryError内存不足错误(将在本节稍后讨论)指出特定代或整个堆大小的问题,你可以通过第8节中指定的命令行选项来修改大小。
例如,默认的最大值在非服务器类机器上,64MB的堆大小通常太小,因此可以通过-Xmx指定更大的堆大小选择。
除非您遇到过长暂停时间的问题,否则请尝试为堆授予尽可能多的内存。吞吐量与可用的内存量成正比。拥有足够的可用内存是影响垃圾收集性能的最重要因素。

在确定可以提供给总堆的内存总量之后,可以考虑调整不同代的大小。影响垃圾收集性能的第二大影响因素是用于年轻代的堆的比例。除非您发现过多的老代收集或暂停时间的问题,否则将大量的内存授予年轻代。但是,当您使用串行收集器时,不要授予年轻代超过堆总大小的一半。

在使用并行垃圾收集器时,最好指定所需的行为,而不是确切的堆大小值。让收集器自动、动态地修改堆大小,以实现该行为,如下所述。

并行收集器的调优策略

如果选择的垃圾收集器(自动或显式地)是并行收集器或并行压缩收集器,那么可以继续指定一个吞吐量目标(参见第5节),该目标足以满足您的应用程序。
不要为堆选择最大值,除非您知道需要一个大于默认最大堆大小的堆。堆将增长或收缩到支持所选吞吐量目标的大小。在初始化期间和在应用程序行为更改期间,可以预料到堆大小会出现一些波动。

如果堆增长到它的最大值,在大多数情况下,这意味着吞吐量目标不能在该最大大小内得到满足。将最大大小设置为接近平台上的总物理内存的值,但不会导致应用程序的交换。再次执行应用程序。如果吞吐量目标仍然没有达到,那么应用程序时间的目标对于平台上的可用内存来说太高了。

如果吞吐量目标可以满足,但暂停时间太长,请选择最大暂停时间目标。选择最大暂停时间目标可能意味着您的吞吐量目标不会得到满足,因此选择对应用程序来说是可接受的折中值。

当垃圾收集器试图满足竞争目标时,即使应用程序已经达到稳定状态,堆的大小也会发生波动。实现吞吐量目标(可能需要更大的堆)的压力与最大暂停时间和最小内存占用(可能都需要更小的堆)的目标相竞争。

如何处理内存溢出错误

许多开发人员必须解决的一个常见问题是应用程序以java.lang.OutOfMemoryError终止。当没有足够的空间来分配对象时,就会抛出该错误。也就是说,垃圾收集不能提供任何可用空间来容纳新对象,堆也不能进一步扩展。OutOfMemoryError并不一定意味着内存泄漏。这个问题可能仅仅是一个配置问题,例如,如果指定的堆大小(如果没有指定,则为默认大小)对应用程序来说不够用。

诊断OutOfMemoryError的第一步是检查完整的错误消息。在异常消息中,进一步的信息在" java.lang.OutOfMemoryError "之后提供。以下是一些常见的例子,可以说明附加信息可能是什么,它可能意味着什么,以及如何处理它:

  • Java heap space

这表明无法在堆中分配对象。这个问题可能只是配置问题。例如,如果-Xmx命令行选项(或默认选择)指定的最大堆大小对应用程序来说不够,则可能会出现此错误。这也可能表明不再需要的对象不能被垃圾收集,因为应用程序无意中持有对它们的引用。HAT工具(见第7节)可用于查看所有可访问的对象,并理解哪些引用使每个对象保持存活。此错误的另一个潜在来源可能是应用程序过度使用终结器,以致调用终结器的线程无法跟上向队列添加终结器的速度。jconsole管理工具可用于监视挂起终结的对象数量。

  • PermGen space

这表明永久代已满。如前所述,这是堆中JVM存储元数据的区域。如果应用程序加载了大量的类,那么可能需要增加永久生成。可以通过指定命令行选项-XX:MaxPermSize=n来实现,其中n指定大小。

  • Requested array size exceeds VM limit

这表明应用程序试图分配一个大于堆大小的数组。例如,如果一个应用程序试图分配一个512MB的数组,但是最大堆大小是256MB,那么这个错误将被抛出。在大多数情况下,问题可能是堆大小太小,或者是一个bug导致应用程序试图创建一个数组,其大小被计算为错误的巨大。

第7节中描述的一些工具可以用于诊断OutOfMemoryError问题。对于这个任务,一些最有用的工具是堆分析工具(HAT)、jconsole管理工具和带有-histo选项的jmap工具。

7.评估垃圾收集性能的工具

可以使用各种诊断和监视工具来评估垃圾收集性能。本节提供其中一些的简要概述。有关更多信息,请参见其中的“工具和故障排除”链接部分9节。

  • -XX:+PrintGCDetails命令行选项

获取关于垃圾收集的初始信息最简单的方法之一是指定命令行选项-XX:+PrintGCDetails。对于每一次收集,都会输出一些信息,例如各个代的垃圾收集前后活动对象的大小、每个代的总可用空间以及收集所花费的时间长度。

  • -XX:+PrintGCTimeStamps命令行选项

除了使用命令行选项-XX:+PrintGCDetails时输出的信息之外,这将在每个收集的开始处输出一个时间戳。时间戳可以帮助您将垃圾收集日志与其他记录的事件关联起来。

  • jmap

jmap是一个命令行实用程序,包含在Solaris™操作环境和Java开发工具包(JDK™)的Linux(但不是Windows)版本中。它为运行中的JVM或核心文件打印与内存相关的统计信息。如果不使用任何命令行选项,则输出加载的共享对象的列表,类似于Solaris pmap实用程序输出。对于更具体的信息,可以使用-heap、-histo或-permstat选项。

heap选项用于获取信息,其中包括垃圾收集器的名称、特定于算法的详细信息(例如用于并行垃圾收集的线程数)、堆配置信息和堆使用情况摘要。

-histo选项可用于获取堆的类级直方图。对于每个类,它将打印堆中实例的数量、这些对象消耗的内存总量(以字节为单位)以及完全限定的类名。柱状图在试图理解如何使用堆时非常有用。

对于动态生成和加载大量类(例如Java Server Pages™和web容器)的应用程序来说,配置永久生成的大小非常重要。如果应用程序加载" too many "类,然后抛出OutOfMemoryError。jmap命令的-permstat选项可用于获取永久生成中对象的统计信息。

  • jstat

jstat实用程序使用HotSpot JVM中的内置工具来提供有关运行应用程序的性能和资源消耗的信息。该工具可用于诊断性能问题,特别是与堆大小和垃圾收集相关的问题。它的许多选项中有一些可以打印有关垃圾收集行为和各个代的容量和使用情况的统计信息。

  • HPROF:堆分析器

HPROF是JDK 5.0附带的一个简单的分析器代理。类的动态链接库JVM使用Java虚拟机工具接口(JVM TI)。它以ASCII或二进制格式将分析信息写入文件或套接字。这些信息可以由分析器前端工具进一步处理。

HPROF能够显示CPU使用情况、堆分配统计信息和监控争用配置文件。此外,它还可以输出完整的堆转储,并报告Java虚拟机中所有监视器和线程的状态。HPROF在分析性能、锁争用、内存泄漏和其他问题时非常有用。参见第9节获得HPROF文档的链接。

  • 堆分析工具

堆分析工具(HAT)有助于调试无意的对象保留。此术语用于描述不再需要的对象,但由于从活动对象通过某些路径进行的引用而保持活动的对象。HAT提供了一种方便的方法来浏览使用HPROF生成的堆快照中的对象拓扑。该工具允许许多查询,包括“向我显示从根集到这个对象的所有引用路径”。参见9节链接到HAT文档。

8.与垃圾收集相关的关键选项

可以使用许多命令行选项来选择垃圾收集器、指定堆或代大小、修改垃圾收集行为和获取垃圾收集统计信息。本节展示了一些最常用的选项。有关各种可用选项的更完整列表和详细信息,请参见第9节。注意:您指定的数字可以以" m "或" M "结尾," k "或" K "代表千字节," g "或" G "代表千兆字节。

  • 垃圾收集器选择
选项垃圾收集器选择
–XX:+UseSerialGCSerial
–XX:+UseParallelGCParallel
–XX:+UseParallelOldGCParallel compacting
–XX:+UseConcMarkSweepGCConcurrent mark–sweep (CMS)
  • 内存收集器的统计信息
选项描述
–XX:+PrintGC在每次垃圾收集时输出基本信息
–XX:+PrintGCDetails在每次垃圾收集时输出更详细的信息
–XX:+PrintGCTimeStamps在每个垃圾收集事件开始时输出时间戳。使用
-XX:+PrintGC或-XX:+PrintGCDetails显示每次垃圾收集何时开始

 

  • 堆和代大小
选项默认值描述
–Xmsn看第五节堆的初始大小,以字节为单位
–Xmxn看第五节堆的最大大小(以字节计)
–XX:MinHeapFreeRatio=minimum and –XX:MaxHeapFreeRatio=maximum40(最低)
70(最大)
自由空间占总堆大小的比例的目标范围。这些是每一代应用的。
例如,如果最小值是30,并且在一个代中空闲空间的百分比降到30%以下,则会扩展代的大小,以便有30%的空闲空间。类似地,如果maximum是60,并且空闲空间的百分比超过60%,则代的大小会收缩,从而只有60%的空闲空间。
–XX:NewSize=nPlatform–dependent新(年轻)代的默认初始大小,以字节为单位。
–XX:NewRatio=n客户机JVM上的2,
服务器JVM上的8
年轻一代和年老一代的比例。例如,如果n为3,则比例为1:3,Eden和survivor区的总大小是年轻代和年老代总大小的四分之一。
–XX:SurvivorRatio=n32每个survivor区与Eden区之间的比率。例如,如果n是7,则每个survivor区是年轻代的九分之一(而不是八分之一,因为有两个survivor区空间)。
–XX:MaxPermSize=nPlatform–dependent永久代生成的最大大小

 

  • 并行和并行压缩收集器的选项
选项默认值描述
–XX:ParallelGCThreads=n

可获取CPU个数

垃圾收集器线程数
–XX:MaxGCPauseMillis=n指示收集器希望暂停时间不超过n毫秒
–XX:GCTimeRatio=n99设定将总时间的1/(1+n)用于垃圾收集的目标的数

 

  • CMS收集器的选项
选项默认值描述
–XX:+CMSIncrementalMode禁用启用一种模式,其中并发阶段增量地完成,定期停止并发阶段以将处理器交还给应用程序
–XX:+CMSIncrementalPacing禁用根据应用程序行为,允许自动控制CMS收集器在放弃处理器之前所允许做的工作量
–XX:ParallelGCThreads=n可获取CPU个数并行年轻代收集和老代收集的并行部分的垃圾收集器线程数

 

9.需要更多信息;取得进一步资讯

(1)HotSpot垃圾收集和性能调优

  • Java HotSpot虚拟机中的垃圾回收

(http://www.devx.com/Java/Article/21977)

  • 使用5.0 Java[tm]虚拟机优化垃圾收集

(http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html)

(2)人类工程学

  • 服务器类机器检测

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/server-class.html)

  • 垃圾收集器的人体工程学

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/gc-ergonomics.html)

  • 人机工程学在5.0 Java™虚拟机

(http://java.sun.com/docs/hotspot/gc5.0/ergo5.html)

(3)可选项

  • Java™HotSpot VM选项

(http://java.sun.com/docs/hotspot/VMOptions.html)

  • Solaris和Linux选项

(http://java.sun.com/j2se/1.5.0/docs/tooldocs/solaris/java.html)

  • 窗口选项

(http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/java.html)

(4)工具和故障排除

  • Java™2平台,标准版5.0故障排除和诊断指南

(http://java.sun.com/j2se/1.5/pdf/jdk50_ts_guide.pdf)

  • HPROF: J2SE 5.0中的堆/CPU分析工具

(http://java.sun.com/developer/technicalArticles/Programming/HPROF.html)

  • 帽子:堆分析工具

(https://hat.dev.java.net/)

(5)Finalization

  • Finalization、线程和基于Java技术的内存模型

(http://devresource.hp.com/drc/resources/jmemmodel/index.jsp)

  • 如何处理Java Finalization 的内存保留问题

(http://www.devx.com/Java/Article/30192)

(6)其他

  • J2SE 5.0发布说明

(http://java.sun.com/j2se/1.5.0/relnotes.html)

  • Java™虚拟机

(http://java.sun.com/j2se/1.5.0/docs/guide/vm/index.html)

  • Sun Java™实时系统(Java RTS)

(http://java.sun.com/j2se/realtime/index.jsp)

  • 关于垃圾收集的一般书籍:垃圾收集:自动动态内存的算法,由Richard Jones和Rafael Lins管理,John Wiley & Sons, 1996年。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值