在J2SE 5.0,垃圾收集的默认值:垃圾收集器、堆大小以及JVM的类型(客户端还是服务器)都会根据应用运行的硬件平台和操作系统自动选择。相比之前设置命令行参数的方式,自动选择很好的匹配了不同类型的应用系统。
另外,并行收集器增加了一种新的动态优化收集算法。在这种方法中,用户指定渴望的行为,垃圾收集器动态的调整堆区域的大小力图实现所需的行为。依赖于平台的默认选择和垃圾收集器自动调整所需的行为称谓工效学。工效学的目标是提供很好的性能,同时只需要很少的命令行参数优化。
自动选择收集器、堆大小和VM类型
拥有下面特性的认为是服务器类型的机器
- 2个或更多的物理CPU,并且
- 2G或更多的物理内存
这个服务器类型机器的定义适用于除了运行windwos操作系统的32位平台外的所有平台。
一个机器如果不是服务器类型的,默认的设置是:
- client类型的JVM
- 串行收集器(serial garbage collector)
- 初始4M的堆
- 最大64M的堆
在服务器类型的机器上,JVM通常都是server
JVM,除非你显示的指定-client参数。运行server JVM的服务器类型的机器默认的收集器是并行收集器(parallel
collector),否则默认是串行收集器。使用平行收集器在服务器类型机器上运行的JVM(client或者server),默认的初始和最大堆如下:
- 初始堆是1/64的物理内存,最大1GB。
(注意:最小初始值是32MB,因为服务器类型的机器至少拥有2GB内存,它的1/64是32MB) - 最大堆是1/4的物理内存,最大1GB
除此之外,非服务器级的机器使用相同的默认大小(4MB初始大小,最大64MB)。默认值通常可以用命令行参数改写。有关的参数在第八章给出。
基于行为的并行收集器优化
在J2SE
5.0中,并行收集器加入了一种新的基于应用系统期望垃圾收集行为的优化方法。
命令行参数用于指定最大暂停时间和应用程序吞吐量所期望的行为。
最大暂停时间目标
最大暂停时间目标通过如下参数指定:
引用:
-XX:MaxGCPauseMillis=n
这应解读为对于平行收集器的一个提示,期望暂停时间是n毫秒或者更少。并行收集器调整堆大小和其他垃圾收集器相关的参数力图保持垃圾收集暂停时间短于n毫秒。这些调整可能导致垃圾收集器降低整体应用的吞吐量,有时期望的暂停时间也实现不了。最大暂停时间目标分别应用在每个代。特别的,如果目标没达到,会减少代的大小尝试实现目标。默认情况下没有最大暂停时间的设置。
吞吐量目标
吞吐量目标在垃圾收集消耗的时间和非垃圾收集消耗的时间(归为应用时间)方面进行测量。这个目标通过如下参数指定:
引用:
-XX:GCTimeRatio=n
垃圾收集时间与应用时间的比例是
引用:
1 / (1 + n)
例如 -XX:GCTimeRatio=19
设置了一个垃圾收集占总时间5%的目标。默认的目标是1%(即n=99)。垃圾收集花费的时间是所有代的总和。如果吞吐量目标没有达到,会增加代的大小以便让应用系统在两次垃圾收集之间可以运行的时间更长。大的代需要更长的时间填满。
占用空间目标
如果吞吐量目标和最大暂停时间目标实现了,垃圾收集器会减少堆的大小直到其中之一(总是吞吐量目标)不能实现。然后解决没满足的目标。
目标的优先级
并行收集器优先努力实现最大暂停时间目标。只有当这个目标达到后才解决吞吐量目标。同样的,在前两个目标达成后才会关心占用空间目标。
第六章 推荐
在上一章描述的工效学引导的自动的垃圾收集器、虚拟机、和堆大小的选择对于很大一部分的应用是合理的。因此,选择和配置垃圾收集器的初次推荐是什么都不做!就是说,不用指定特别的垃圾收集器,以及其他。让系统基于应用运行的平台和操作系统自动选择。然后测试你的应用。如果性能是可接受的,也就是拥有足够的吞吐量和足够短的暂停时间,你的事就做完了。你不需要排查故障或者更改垃圾收集设置。
另一方面,如果你的应用似乎存在垃圾收集相关的性能问题,最简单的你首先要做的是依据应用程序和平台特性想想默认选择哪个垃圾收集器。或者,先选择一个,然后看看性能是否能接受。
你可以用第七章描述的那些工具测量和分析性能。基于测量结果,考虑修改参数,比如堆大小或者垃圾收集行为。第八章列出了一些常用的命令行参数。注意:最好的性能优化方法是先测量,后调整。使用与你的代码真实运行相关的测试进行测量。并且,防止过度优化,因为应用程序的数据集,硬件,等等–甚至垃圾收集实现!–可能会随时间改变。
这一章提供了选择垃圾收集器和设置堆大小的信息。然后提供了一些优化并行垃圾收集器的建议,然后给出了关于OutOfMemoryError的处理建议。
选择一个不同的垃圾收集器
第四章说到对于每个收集器建议使用的场景。第五章描述了什么样的平台默认会自动选择串行或并行收集器。如果你的应用程序或环境特性期望一个相比默认不同的垃圾收集器,通过下面命令行参数明确的指定它:
代码:
–XX:+UseSerialGC
–XX:+UseParallelGC
–XX:+UseParallelOldGC
–XX:+UseConcMarkSweepGC
堆大小
第五章说明了默认的初始和最大堆大小。这些大小对于大部分应用来说是何时的,但是如果你正在分析的性能问题(参见第七章)或者是OutOfMemoryError(本章后面会讨论)表明某个代或整个堆的大小存在问题,你可以通过第八章指定的命令行参数调整这些大小。例如,在非服务器类型的机器上,默认的最大堆大小64MB通常太小了,你可以通过-Xmx参数指定一个更大的。除非你遇到了长暂停时间的问题,否则应分配尽可能多的内存到堆。吞吐量与可用内存成比例变化。拥有足够的内存是影响垃圾收集性能最重要的因素。
决定总共分配多少内存给整个堆后你可以考虑每个代的大小。第二重要影响垃圾收集性能是的分配给年轻代的比例。除非你发现过多的年老代收集或暂停时间,应分配足够多的内存给年轻代。然而,如果你使用的是串行收集器,不要分配多余一半的堆内存到年轻代。
当你使用某种并行收集器时,优先指定期望的行为,而不是确切的堆大小值。然后让收集器自动动态的调整堆大小以便实现这些行为,下面会讲到。
并行收集器的优化策略
如果垃圾收集器选择(自动或明确指定)一个并行收集器或并行压缩收集器,然后指定一个吞吐量目标(参见第五章),通常对于你的应用程序就足够了。不要指定堆的最大值,除非你明确的知道需要的堆大小比默认要大。堆会自动增长或收缩到一定大小以支持选定的吞吐量目标。可以预期,在堆初始化和应用程序行为发生变化时可能发生一些堆大小的震荡。
如果堆大小增长到最大值,在大部分情况下这意味着基于这个最大值吞吐量目标无法达到。设置最大堆内存到接近物理内存并且不会导致交换应用程序的值。再次执行应用程序。如果吞吐量目标依然没有达到,则说明对于在这个平台上可用的内存来讲设置的目标太高了。
如果吞吐量目标可以达到,但是暂停时间太长,选择一个最大暂停时间目标。选择最大暂停时间目标可能意味着吞吐量目标达不到,所以要选择一个可接受的妥协的值。
垃圾收集器在满足互相矛盾的目标时可能导致堆大小来回震荡,直到应用程序达到一个稳定状态。迫使满足吞吐量目标(它需要大堆)与最大暂停时间目标和最小占用目标(这两个目标需要更小的堆)相矛盾。
遇到OutOfMemoryError时做什么
应用程序终端显示的java.lang.OutOfMemoryError是很多程序员都需要解决的通用问题。这个错误在没有空间分配对象时抛出。意思是,垃圾收集器不能再制造一些空间来容纳一个新对象,并且堆空间不能再扩展。OutOfMemoryError错误不一定意味着内存泄露。它可能是一个简单的配置错误,例如为应用程序配置不足的内存(或默认值没有设置)。
诊断OutOfMemoryError错误的第一步是检查完整的错误信息。在异常信息中,补充信息在“java.lang.OutOfMemoryError”之后提供。这里提供一些常见的可能的补充信息,以及他们是什么意思,针对他们该做些什么。
Java
heap
space
这表明对象不能在堆中分配。这个问题可能仅仅是一个配置问题。例如,通过命令行参数-Xmx指定的(或者默认的)最大堆内存对于应用来讲是不够的,你就会遇到这个错误。它也可能是一种迹象,不再需要的对象由于应用程序无意的引用着而不能被垃圾收集器回收。HAT工具(参见第七章)可以用来查看所有可引用到的对象,以及理解这些对象是如何被引用的。另一个可能导致这个错误的原因可能是应用过多的使用终结器(finalizers)以至于执行终结的线程跟不上加入终结队列的速度。jconsole管理工具可以用来监测等待终结的对象数量。
PermGen
space
这表明持久代满了。如前所述,这个区域是JVM用来存放元数据信息的。如果应用程序加载了大量的类,则需要增加持久代大小。你可以通过这个命令行参数–XX:MaxPermSize=n实现,这里的n指大小。
Requested
array size exceeds VM
limit
这表明应用程序企图分配的数组比堆还大。例如,应用程序尝试分配一个512MB的数组,但是堆最大才256MB,就会出现这个错误。大部分情况下,这个错误要不就是因为堆太小,要不就是因为应用程序计算数组的大小时发生错误引起了一个bug。
一些在第七章描述的工具可以被用于诊断OutOfMemoryError问题。最有用的一些工具室Heap
Analysis
Tool(HAT)、jconsole管理工具、附加-histo参数的jmap工具。