关于Hotspot TM Java TM虚拟机中的
垃圾收集的常见问题
本文档描述了 Java( tm ) HotSpot ( tm ) 虚拟机的行为。但是,此行为不是 VM 规范的一部分,并且在未来的版本中可能会发生变化。此外,这里描述的 行为是通用行为,并不适用于所有 Java 应用程序的执行。
1.HotSpot(tm) 中的分代收集器是如何实现的?
HotSpot 中的默认收集器有两代:年轻代和老年代。大多数分配都是在年轻一代中完成的。年轻代针对生命周期相对于集合之间的间隔较短的对象进行了优化。在年轻代中多次收集的对象被移动到终身代。年轻代通常更小,并且被更频繁地收集。终身代通常较大且收集频率较低。
年轻代收集器是一个复制收集器。年轻代分为 3 个空间:eden-space、to-space 和 from-space。分配是从 eden-space 和 from-space 完成的。当这些已满时,年轻一代的收集就完成了。期望大多数对象都是垃圾,并且任何幸存的对象都可以复制到空间。如果存活对象的数量超过了 to-space 的容量,剩余的对象将被复制到老年代。可以选择并行收集年轻代。
使用标记-扫描-紧凑收集来收集终身代。有一个选项可以同时收集终身代。
-XX:MaxNewSize 的相关性是什么?-XX:NewSize 和 -XX:MaxNewSize 之间的差异会在哪里增长,Eden 或 Survivor Spaces?
年轻代由一个策略设置,该策略从下到上由 NewSize 限制大小,从上到下由 MaxNewSize 限制大小。随着年轻一代从 NewSize 增长到 MaxNewSize,伊甸园和幸存者空间都在增长。
是否所有 eden-space 对象都移入了幸存者空间,以便在一次次 gc 之后,eden-space 是空的?
是的。如果 eden 中的所有活动对象都无法放入幸存者空间,则剩余的活动对象将被提升到老年代。
当使用 -XX: TargetSurvivorRatio =90 时,这会留下百分之十的空间用于从伊甸园中移动对象吗?
不。这意味着选择了一个 任期阈值,以便根据上次次要收集中清除的内容的年龄,应该使用近 90% 的幸存者大小。从幸存者空间或伊甸园中清除的实际数量可能或多或少。
TargetSurvivorRatio 通常不会产生很大的影响。
如果 eden-space 中的对象需要的空间多于 to-survivor 空间中可用的空间,eden-space 对象是否会优先于 from-survivor 空间对象?幸存者太空物体的年龄如何影响晋升?
在这里,来自伊甸园的东西和来自幸存者空间的东西没有区别。次要收集完成后,伊甸园和来自幸存者的空间都是空的。如果 to-survivor 空间被填满,任何剩余的对象都将直接提升到老年代,无论它们的年龄或来源如何。
在 NewSize 和 NewRatio 之间哪个选项优先?
在 jdk 1.4.1 及更高版本中,两者都没有严格的优先级。使用 NewSize 的最大值和使用 NewRatio 计算的大小。公式是
min (MaxNewSize, max (NewSize, heap/(NewRatio+1)))
2.永久代的规模应该如何确定?
永久代用于保存 VM 本身的反射,例如类对象和方法对象。这些反射对象被直接分配到永久代,并且它的大小独立于其他代。通常,可以忽略这一代的大小,因为默认大小已经足够了。但是,加载许多类的程序可能需要更大的永久代。
3.如何判断永久代是否已满?
从 1.4.2 开始 -XX:+PrintGCDetails 将打印有关在每次垃圾回收时收集的堆的所有部分的信息。完整的收藏
[Full GC [Tenured: 30437K->33739K(280576K), 0.7050569 secs] 106231K->33739K(362112K), [Perm : 2919K->2919K(16384K)], 0.7052334 secs]
这个例子表明,永久代收集的很少(从收集前使用的 2919K 变为收集后使用的 2919K),永久代的当前大小为 16384K。
4.如何增加永久代的大小?
使用命令行选项 -XX:MaxPermSize=<desired size>
5.我如何知道正在加载或卸载哪些类?
使用命令行选项 -XX:+ TraceClassloading和 -XX:+ TraceClassUnloading
6.年轻一代的最佳尺寸是多少?
年轻代的大小应该足够大,以便短期对象有机会在下一次年轻代收集之前死亡。这是一个权衡,因为更大的年轻代将允许对象有更多时间死亡,但也可能需要更长的时间来收集。试验年轻代的大小以优化年轻代收集时间或 应用程序吞吐量。
7.如果我的应用程序有中长寿命的对象,我该怎么办?
在年轻代集合中存活的对象具有复制成本(年轻代集合的算法的一部分是复制任何存活的对象)。中期或长期对象可能会被复制多次。使用 -XX 选项MaxTenuringThreshold 来确定复制成本。使用 -XX:MaxTenuringThreshold=0 将在年轻代集合中幸存的对象立即移动到终身代。如果这提高了应用程序的性能,那么长期对象的复制就很重要。请注意,吞吐量收集器不使用 MaxTenuringThreshold 参数。
8.垃圾收集何时开始?
在默认垃圾收集器中,当代已满时(即,无法从该代进行进一步分配时)会收集该代。吞吐量收集器也是如此。并发低暂停收集器在老年代的占用率达到指定值(默认为 68%)时开始收集。增量低暂停收集器在每次年轻代收集期间收集一部分老年代。集合也可以由应用程序显式启动。
9.System.gc() 做什么类型的集合?
执行垃圾收集的显式请求会执行完整收集(年轻代和老生代)。完整的收集总是在应用程序在收集期间暂停的情况下完成。
10.什么是并发标记扫描 (CMS) 收集器?
Concurrent Mark Sweep (CMS) 收集器(也称为并发低暂停收集器)收集tenured generation。它试图通过与应用程序线程同时进行大部分垃圾收集工作来 最小化由于垃圾收集而导致的暂停。
11.为什么碎片是并发低暂停收集器的 潜在问题?
通常并发低暂停收集器不会复制或压缩活动对象。垃圾收集是在不移动活动对象的情况下完成的。如果碎片成为问题,请分配更大的堆。在 1.4.2 中,如果老年代的碎片成为一个问题,老年代的压缩将被完成,尽管不是同时进行。在 1.4.1 中,如果启用了UseCMSCompactAtFullCollection选项,则会发生压缩 。
-XX:+UseCMSCompactAtFullCollection
12.并发低暂停收集器的阶段是什么?
收集涉及六个阶段:
阶段 1(初始检查点)涉及停止所有 Java 线程,标记所有可从根直接访问的对象,并重新启动 Java 线程。
阶段 2(并发标记)从标记的对象开始扫描,并传递标记从根可到达的所有对象。mutator在下面 的并发阶段 2、3 和 5 期间执行,并且在这些阶段期间在 CMS 生成中分配的任何对象(包括提升的对象)都会立即标记为活动的。
阶段 3:在并发标记阶段,mutator 可能正在修改对象。自并发标记阶段开始以来已修改的任何对象(并且在该阶段随后未扫描)都必须重新扫描。 阶段 3(并发预清理)扫描已同时修改的对象。由于持续的mutator 活动,可能会多次扫描修改过的卡片。
第 4 阶段(最终检查点)是一个停止世界的阶段。随着突变体的停止,最终的标记是通过扫描从根可以到达的对象并扫描任何修改过的对象来完成的。请注意,在此阶段之后,可能会有已标记但不再存在的对象。此类对象将在当前集合中存活,但将在下一个集合中收集。
阶段 5(并发扫描)收集死对象。死对象的集合将对象的空间添加到空闲列表以供以后分配。此时可能会发生死对象的合并。请注意,活动对象不会移动。
阶段 6(重置)清除数据结构,为下一次收集做准备。
13.VM 是否分配大型 int 数组供自己使用?
JVM 确实分配大 int[] 的一个地方是当它填满 内存的各个碎片部分以使垃圾收集器看起来完整时。例如,在 GC 之前每个线程本地分配缓冲区的未使用部分,或者在运行 JVMPI 对象分配事件时的所有年轻代。
14.我可以看到有多少线程分配缓冲区未使用吗?
有一个标志, -XX:+ PrintTLAB,它将跟踪 TLAB 上的所有操作。 特别是,它打印像
重置 TLAB:线程:0x0002d7d0 大小:8KB 未使用:76B 总碎片 0.004499
每次 TLAB 填充一个 int[]。在这种情况下,未使用的尾随 76B 将未被使用。
这是一个 TLAB 已填满并将分配一个新 TLAB 的示例。这里的垃圾量比较少。在为垃圾收集做准备时,可能会产生更多的浪费。对于在垃圾收集之前显示的 TLAB 输出,例如
重置 TLAB:线程:0x0002d840 大小:8KB 未使用:7276B 总碎片 0.004580
[全 GC 10424K->591K(15688K),0.1222677 秒]
我们正在填充 8192 字节 TLAB 的 7276 字节。(“总碎片”是由TLAB 引起的碎片的累积计算。)默认情况下,在 SPARC 服务器上调整 TLAB 的大小,或者如果您使用 -XX:+ ResizeTLAB 标志,因此如果您正在运行,您很可能会得到大的TLAB那个JVM。请注意,我们不会在集合之前“浪费”填充器的空间,因为集合将恢复填充器对象占用的空间。
15.NewRatio 的默认值会随着编译器的变化而变化吗?
在 SPARC 上,-XX:NewRatio 默认为 -client 为 8,-server 为 2,因此年轻代与老年代的比率将为 1::8,而年轻代将是堆的 1/9 -客户端和 1::2 或 1/3 的堆与 -server。
16.什么是并行垃圾收集器 (-XX:+UseParallelGC)?
新的并行垃圾收集器类似于默认垃圾收集器中的年轻代收集器,但使用多个线程进行收集。默认情况下,在具有 N个 CPU的主机上,并行垃圾收集器在收集中使用 N 个垃圾收集器线程。垃圾收集器线程的数量可以通过命令行选项来控制(见下文)。在具有单个 CPU 的主机上,即使已请求并行垃圾收集器,也会使用默认垃圾收集器。在具有 2 个cpu 的主机上,并行垃圾收集器的性能通常与默认垃圾收集器一样好,并且在具有超过 2 个 cpu 的主机上可以预期减少年轻代垃圾收集器的暂停时间。
这个新的并行垃圾收集器可以通过使用命令行产品标志 -XX:+UseParallelGC 来启用。可以使用 ParallelGCThreads 命令行选项 (-XX:ParallelGCThreads=<desired number>) 控制垃圾收集器线程的数量。此收集器不能与并发低暂停收集器一起使用。
17.什么是并行年轻代收集器(-XX:+UseParNewGC)?
并行年轻代收集器在意图上类似于并行垃圾收集器(-XX:+UseParallelGC),但在实现上有所不同。因此,以上对并行垃圾收集器 (-XX:+UseParallelGC) 的大部分描述同样适用于并行年轻代收集器。与并行垃圾收集器 (-XX:+UseParallelGC) 不同,此并行年轻代收集器可以与收集终身代的并发低暂停收集器一起使用。
18.我应该使用哪个并行收集器?
尽管意图相似,但收集器在实现的某些细节上有所不同,这使得并行垃圾收集器更适合某些应用程序,而并行年轻代收集器更适合其他应用程序。两者都应该尝试确定哪个可能更适合特定应用程序。
此外,并行年轻代收集器 (-XX:+UseParNewGC) 与并发低暂停收集器集成,而并行垃圾收集器 (-XX:+UseParallelGC) 则没有。即使不使用并发低暂停收集器,也会产生与此集成相关的一些成本。相反,并行垃圾收集器 (-XX:+UseParallelGC) 可以与自适应大小 (-XX:+UseAdaptiveSizePolicy) 一起使用,而并行年轻代收集器 (-XX:+UseParNewGC) 则不能。
19.为什么并发低暂停 (CMS) 收集器的启动速度很慢?
使用 CMS (+XX:UseConcMarkSweepGC),您有时需要将最小和最大堆大小设置为相同的值(或至少设置一个较大的最小值),因为 CMS 有时会花时间提前增长其堆。烫发一代也可能如此。使用选项 -XX 尝试更大的 perm 生成大小:PermSize =<initial size> -XX:MaxPermSize=<maximum size>。
20.什么年轻代收集器与并发低暂停收集器一起使用?
默认情况下,低暂停收集器使用默认的单线程年轻代复制收集器。如果您指定 +XX:UseParNewGC 将使用复制收集器的并行版本。
21.为什么低暂停收集器有时会比默认收集器做更多的收集?
如果您没有看到使用默认收集器的主要收集,但看到使用并发低暂停收集器的许多主要收集,您可能会看到某种类型的碎片问题。尝试使用更大的堆和并发低暂停收集器。
22.垃圾收集文档是否还有其他外部资源?
http://developer.java.sun.com/developer/technicalArticles/Programming/turbo/
23.对于并发低暂停收集器,NewRatio 的最小值是多少?
建议最小值 为4。
24.对象会直接分配到老年代吗?
在 1.4.1 中有两种情况,分配可能直接发生在老年代。
如果年轻代分配失败,并且对象是一个不包含任何对象引用的大数组,则可以直接将其分配到年老代。在某些特定情况下,此策略旨在通过从老年代分配来避免收集年轻代。
有一个标志(在 1.4.2 和更高版本中可用)l-XX: PretenureSizeThreshold =<byte size> 可以设置来限制年轻代中分配的大小。任何大于此的分配都不会在年轻代中尝试,因此将被分配到老年代之外。
1) 的阈值大小为 64k 字。PretenureSizeThreshold 的默认大小为 0,表示可以在年轻代中分配任何大小。
在 1.4.2 案例 1) 中,增量收集器 (-Xincgc) 的 64k 字阈值仍然有效。对于默认收集器和并发收集器(-XX:+UseConcMarkSweepGC),阈值已更改,因此仅当分配的大小大于整个年轻代(可用空间时它是空的)。据观察,在某些情况下,默认收集器和并发收集器的 1.4.1 策略仅导致完全收集(没有进行年轻代收集)。我们认为这足以提高门槛。
25.我应该增加客户端虚拟机中永久代的大小吗?
这将永远是一个判断电话。一般来说,增加一代的大小(这不仅适用于永久代)可以减少各种问题的发生率,但是,这可能会导致其他进程过度分页和/或垃圾收集或抛出-内存异常。
有两种故障模式需要考虑。
提高 MaxPermSize 时,以前行为良好的程序可能会因无休止的分页而死掉,这些程序用于垃圾收集以恢复永久代空间。对于永久代,这通常只发生在临时字符串的大量实习中。
另一种故障模式是必须为永久代保留地址空间,这将减少堆其余部分的可用空间(最大值 -Xmx 可能会太大)。这将导致配置为使用所有可用空间的程序在初始化时失败。
最近的 VM中的永久代默认值。
发布
v1.3.1_06
v1.4.1_01
v1.4.2
客户
永久大小
1M
4M
4M
服务器
永久大小
1M
4M
16M
客户
最大烫发尺寸
32M
64M
64M
服务器
最大烫发尺寸
64M
64M
64M
我应该汇集对象来帮助 GC 吗?我应该定期调用 System.gc() 吗?
这些问题的答案是否定的!
池化对象将导致它们的寿命超过必要的时间。我们强烈建议不要使用对象池。
不要调用 System.gc()。系统将确定何时适合进行垃圾收集,并且通常具有更好地启动垃圾收集所需的信息。如果您在垃圾收集方面遇到问题(暂停时间或频率),请考虑调整代的大小。
什么决定了何时刷新软引用的对象?
从 J2SE 1.3.1 中的 Java HotSpot VM 实现开始,软可访问对象将在最后一次被引用后保持活动一段时间。默认值是堆中每兆字节的生命周期一秒。该值可以使用 -XX:SoftRefLRUPolicyMSPerMB 标志进行调整,该标志接受表示每 MB 可用内存毫秒数的整数值。例如,要将值从 1 秒更改为 2.5 秒,请使用以下标志:
-XX:SoftRefLRUPolicyMSPerMB =2500
Java HotSpot Server VM 使用最大可能的堆大小(由 -Xmx 选项设置)来计算剩余的可用空间。
Java HotSpot Client VM 使用当前堆大小来计算可用空间。
这意味着服务器 VM 的总体趋势是增加堆而不是刷新软引用,因此 -Xmx 对软引用何时被垃圾收集具有显着影响。
另一方面,客户端虚拟机将更倾向于刷新软引用而不是增加堆。
上述行为适用于 Java HotSpot VM 的当前(J2SE 1.3.1 和 J2SE 1.4.x)版本。 请注意,不保证 -XX:SoftRefLRUPolicyMSPerMB 标志存在于任何给定版本中。
在 1.3.1 版之前,Java HotSpot VM 在找到软引用时会清除它们。
当我打开 -verbose:gc 时,我得到了很多完整的垃圾回收( GC ) 。GC 定期进行。我的应用程序从不调用 System.gc。我已经调整了堆,它没有任何区别,这是怎么回事?
如果您使用的是 RMI(远程方法调用),那么您可能会遇到分布式垃圾回收 (GC)。此外,一些应用程序正在添加显式 GC 的想法,认为这将使他们的应用程序更快。幸运的是,您可以使用 1.3 和 1.4 版本中提供的选项禁用此功能。尝试 -XX:+ DisableExplicitGC 和 -verbose:gc 看看这是否有帮助。
并发低暂停收集器似乎大部分时间都在进行完整收集。如何加快并发收集?
并发收集通常无法加速,但可以更早地启动。
当老年代中分配的空间百分比超过阈值时,并发收集开始运行。此阈值是根据并发收集器的一般经验计算得出的。如果正在发生完整收集,则可能需要更早地启动并发收集。命令行标志 CMSInitiatingOccupancyFraction 可用于设置开始收集的级别。其默认值约为 68%。调整值的命令行是
-XX:CMSInitiatingOccupancyFraction=<百分比>
并发收集器还会统计应用程序进入老年代的提升率,并根据提升率和老年代的可用空间来预测何时开始并发收集。尽管 CMSInitiatingOccupancyFraction 的使用必须保守以避免在应用程序的生命周期内完全收集,但基于预期提升的并发收集的启动适应应用程序不断变化的需求。用于计算提升率的统计信息基于最近的并发收集。在至少一个并发收集完成之前不会计算提升率,因此至少必须启动第一个并发收集,因为占用率已达到 CMSInitiatingOccupancyFraction 。将 CMSInitiatingOccupancyFraction 设置为 100 不会导致仅使用预期提升来启动并发收集,而是只会导致非并发收集发生,因为并发收集直到为时已晚才会启动。消除使用预期提升来启动并发收集集UseCMSInitiatingOccupancyOnly 为真。
-XX:+UseCMSInitiatingOccupancyOnly
有时,当一个完整的收集开始时,并发低暂停收集器即将完成并发收集的最后一部分。完整的集合看起来就像它再次完成了整个集合。这会发生吗?
默认情况下,完整集合使用与并发集合不同的集合算法(压缩)。因此,正在进行的并发收集所做的所有工作都会丢失。可以对此进行调整,以便完整收集将完成并发收集,尽管不是同时进行。一个完整的集合通常会进行压缩,因为无法同时完成它的集合通常是碎片问题的标志。在许多情况下,需要进行压缩,但可以通过将 CMSFullGCsBeforeCompaction的值设置为 1 来延迟 1 次完整收集 。
-XX:CMSFullGCsBeforeCompaction=1
当一个完整的收集开始时使用这个值,它将完成正在进行的并发收集。如果在正常并发收集完成之前发生另一个完整收集,则将完成压缩。
使用并发低暂停收集器,我如何知道还剩下多少浮动垃圾?
因为应用程序线程和 GC 线程同时运行,所以在收集开始时处于活动状态且 GC 线程已标记为活动的对象可能会在收集结束时死亡。这样的对象被称为浮动垃圾。如果在并发收集之后立即发生完全压缩收集,则可以推断出浮动垃圾的数量。堆大小的任何减少都是由于浮动垃圾。
并行收集器似乎使用与机器上的处理器一样多的垃圾收集器 (GC) 线程。如何请求更多或更少的 GC 线程?
GC线程数由选项控制
-XX:ParallelGCThreads=<number_of_GC_threads>
为什么并发低暂停收集器会出现碎片?
并发低暂停收集器通常不会在垃圾收集期间移动对象。当活动对象散布在收集结果留下的可用空间时,就会发生碎片。例外情况是发生非并发的完整收集时。在后一种情况下,应用程序在收集期间停止,活动对象被压缩到一代的一端,所有空闲空间都驻留在一个连续的块中。
吞吐量收集器应该使用哪些选项?
使用的正确选项取决于您的应用程序。以下是一些典型用途,但这些都不是最适合您的应用程序的。
服务器应用程序单独运行在具有 4GB 物理内存的大型多处理器服务器上。
#java -server -XX:+AggressiveHeap
两个应用程序实例在具有 4GB 物理内存的大型多处理器服务器上运行。每个 java 应用程序实例都通过最大和最小堆大小的显式规范分配总系统内存的一部分。
#java -server -XX:+AggressiveHeap -Xms1024m -Xmx1024m
不使用 AggressiveHeap 标志的示例
#java -server -XX:+UseParallelGC -XX:ParallelGCThreads=4 -Xms1024m -Xmx1024m
并发低暂停收集器应该使用哪些选项?
使用的正确选项取决于您的应用程序。以下是一些典型用途,但这些都不是最适合您的应用程序的。
在具有 1 GB 物理内存的处理器系统上运行的服务器应用程序。
#java -Xmx512m -Xms512m -XX:MaxNewSize=24m -XX:NewSize=24m -XX:+UseConcMarkSweepGC
在具有 1GB 物理内存的多处理器系统上运行的服务器应用程序 - 使用并行次要收集选项。
#java -Xmx512m -Xms512m -XX:MaxNewSize=24m -XX:NewSize=24m -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseConcMarkSweepGC
并发低暂停收集器的默认设置是什么?
并发低暂停收集器的默认堆大小与默认收集器相同。其他参数的设置如下所述。这些设置已被证明适用于具有大部分寿命很短的数据以及一些寿命很长的数据的应用程序。一些选项需要用尖括号 (<>) 括起来的计算,其中两个取决于机器上的 CPU 数量 (#cpus。)
# 启用并发低暂停收集器
-XX:+UseConcMarkSweepGC
# 使用并行线程
-XX:+UseParNewGC
-XX:ParallelGCThreads=<#cpus < 8 ? #cpus : 3 + ((5 * #cpus) / 8) >
-XX:+CMSParallelRemarkEnabled
# 为短暂的停顿调整年轻代的大小
-XX:NewSize=4m
-XX:MaxNewSize=< 4m * ParallelGCThreads >
# 提升所有活跃的年轻代对象
-XX: MaxTenuringThreshold = 0
-XX:幸存者比率=1024
还建议使用比默认收集器使用的堆大小大 20-30% 的堆大小。
增量低暂停收集器应该使用哪些选项?
使用的正确选项取决于您的应用程序。以下是一些典型用途,但这些都不是最适合您的应用程序的。
具有 1GB 物理内存的服务器应用程序。
#java -server -Xincgc -XX:NewSize=64m -XX:MaxNewSize=64m -Xms512m -Xmx512m
如果发生完全收集,则上面的应用程序表明没有足够快地增量收集终身代。
#java -server -Xincgc -XX:NewSize=24m -XX:MaxNewSize=24m -Xms512m -Xmx512m
草稿版本:2003 年 2 月 6 日
版权所有 © 2003 Sun Microsystems, Inc.。保留所有权利。