为什么短时间和长寿命对象在垃圾收集方面有所不同?

时间:2010-04-12 07:41:47

标签: java garbage-collection

我经常读到,在Sun JVM中,短期对象(“相对较新的对象”)可以比长寿命对象(“相对较旧的对象”)更有效地进行垃圾收集

  • 为什么会这样?
  • 这是特定于Sun JVM还是由一般垃圾收集原则产生的?

8 个答案:

答案 0 :(得分:22)

大多数Java应用程序创建Java对象,然后相当快地丢弃它们,例如。在方法中创建一些对象然后在退出方法后,所有对象都会死亡。大多数应用程序都采用这种方式,大多数人倾向于以这种方式编写应用程序。 Java堆大致分为3个部分,永久性,旧(长寿命)生成和年轻(短期生成)生成。年轻人进一步分为S1,S2和伊甸园。这些只是堆。

大多数物品是在年轻人中创造的。这里的想法是,由于物体的死亡率很高,我们很快创造它们,使用它们然后丢弃它们。速度至关重要。当您创建对象时,年轻的gen会填满,直到发生较小的GC。在次要GC中,所有活着的对象都从eden复制并说S2到S1。然后,'指针'在eden和S2上休息。

每个副本都会使对象老化。默认情况下,如果一个对象存活32个副本即。 32个小型GC,然后GC表明它将会存在更长时间。因此,它的作用是通过将其转移到老一代来保持它。老一代只是一个很大的空间。当旧的gen填满时,旧的GC会发生完整的GC或主要的GC。由于没有其他空间可供复制,因此GC必须紧凑。这比次要GC慢很多,这就是为什么我们要避免更频繁地这样做。

您可以使用

调整tenuring参数
java -XX:MaxTenuringThreshold=16 

如果你知道你有很多长寿命的物体。您可以使用

打印应用的各个年龄段
java -XX:-PrintTenuringDistribution

答案 1 :(得分:10)

(参见上面对更一般GC的解释..这就解答为什么新的GC比旧的便宜)。

可以更快地清除伊甸园的原因很简单:算法与在伊甸园空间中存活GC的对象数量成正比,与整个堆中的活动对象数量不成比例。即:如果您在伊甸园中的平均物体死亡率为99%(即:99%的物体不能在GC中存活,这不是异常),您只需要查看并复制1%。对于“旧”GC,需要标记/扫描整个堆中的所有活动对象。这要贵得多。

答案 2 :(得分:4)

这是分代垃圾回收。它如今被广泛使用。点击此处了解详情:(wiki)

基本上,GC假定新对象比旧对象更容易无法访问。

答案 3 :(得分:4)

这种现象“大多数物体都会年轻”。许多对象在方法内创建,并且从不存储在字段中。因此,只要该方法退出这些对象“死亡”,因此可以在下一个收集周期进行收集。

以下是一个例子:

public String concatenate(int[] arr) { 
  StringBuilder sb = new StringBuilder();
  for(int i = 0; i < arr.length; ++i)
    sb.append(i > 0 ? "," : "").append(arr[i]);
  return sb.toString();
}

方法返回后,sb对象将变为垃圾。

通过将对象空间拆分为两个(或更多)基于年龄的区域,GC可以更高效:GC不经常扫描整个堆,而是经常只扫描托儿所(年轻物体区域) - 这显然,完全堆扫描花费的时间少得多。较旧的对象区域扫描频率较低。

答案 4 :(得分:2)

更有效地管理年轻对象(不仅收集;对年轻对象的访问也更快),因为它们被分配在特殊区域(“年轻一代”)。这个特殊区域效率更高,因为它是“一次性”收集的(所有线程都停止了),收集器和应用程序代码都不必处理来自另一个的并发访问。

在这里,权衡是在收集“有效区域”时停止“世界”。这可能会引起明显的停顿。 JVM通过保持有效区域足够小来保持低暂停时间。换句话说,如果有一个有效管理的区域,那么该区域必须很小。

适用于许多程序和编程语言的一种非常常见的启发式方法是,许多对象都非常短暂,并且大多数写入访问都发生在年轻对象(最近创建的对象)中。编写不能以这种方式工作的应用程序代码是可能的,但这些启发式算法在“大多数应用程序”中“大部分都是真的”。因此,将年轻物体存储在有效管理的区域中是有意义的。这就是JVM GC的作用,这就是为什么这个有效区域被称为“年轻一代”。

请注意,有些系统可以“高效”处理整个内存。 GC必须运行时,应用程序将“冻结”几秒钟。这对于长期计算是无害的,但对交互性是有害的,这就是为什么大多数现代GC编程环境使用具有有限大小的年轻代的代际GC。

答案 5 :(得分:1)

这是基于观察到物体的寿命随着年龄的增长而上升。因此,一旦达到特定年龄,将对象移动到频率较低的池中是有意义的。

这不是程序使用内存的基本属性。您可以编写一个病毒程序,将所有物体保持很长时间(并且所有物体的时间长度相同),但这不会偶然发生。

答案 6 :(得分:1)

JVM(通常)使用分代垃圾收集器。这种收集器根据其中对象的年龄将堆内存分成几个池。这里的推理是基于观察到大多数对象都是短暂的,所以如果你在一个带有“年轻”对象的内存区域上进行垃圾收集,你可以回收相对于你在“旧”垃圾收集时进行垃圾收集的相对更多的内存。 “对象。

在Hotspot JVM中,新对象在所谓的Eden区域中分配。当这个区域填满时,JVM将扫过伊甸园区域(这不会占用太多时间,因为它不是那么大)。仍然活着的物体被移动到幸存者区域,剩下的物体被丢弃,为下一代腾出了伊甸园。当Eden集合不足时,垃圾收集器会转移到老一代(这需要更多工作)。

答案 7 :(得分:1)

所有GC都表现得那样。基本思想是您尝试减少每次运行GC时需要检查的对象数量,因为这是一项非常昂贵的操作。因此,如果您有数百万个对象,但只需要检查一些,这比检查所有对象要好。此外,GC的一个功能在您手中:临时对象(任何人都无法访问),在GC运行期间没有任何成本(好吧,我们现在暂时忽略finalize()方法)。只有幸存的对象需要花费CPU时间。接下来,观察到很多物体都是短暂的。

因此,对象是在一个狭小的空间中创建的(称为“Eden”或“young gen”)。过了一会儿,所有可以到达的对象都被复制(=昂贵)从这个空间出来,然后空间被声明为空(因此Java有效地忘记了所有无法到达的对象,所以他们没有成本,因为他们没有必须被复制)。随着时间的推移,长寿命对象被移动到“较旧”的空间,较旧的空间不经常扫描以减少GC开销(例如,每N次运行,GC将运行旧空间而不是伊甸园空间)。

比较:如果在C / C ++中分配对象,则需要为每个对象调用free()加析构函数。这就是为什么GC比传统的手动内存管理更快的原因之一。

当然,这是一个相当简化的外观。今天,在GC上工作处于编译器设计的水平(即由极少数人完成)。 GCs采取各种技巧,使整个过程高效,不明显。有关指示,请参阅Wikipedia article