6

我们正在尝试调整一些 Oracle JVM 垃圾收集选项,一位开发人员试图使用-XX:PretenureSizeThreshold它来确保立即将大量对象放入 Tenured。我很确定假设数组大小等于或超过其中所有对象的总大小。

但是在 Java 中,对象数组不只是引用数组吗?即数组中的每个对象,以及数组对象本身,在内存中是分开的并且被垃圾收集器视为分开的?我认为如果有数百万个条目,数组对象仍然可以变得相当大,但如果每个对象都比引用大得多,它不应该接近它“包含”的对象的总大小。

我认为存在混淆,因为 AFAIK,在 C 中:

  1. struct可能有一个真正存储s的 s 数组struct
  2. 也可以有一个指向 s 的指针数组struct

我很确定 Java 总是使用 1. 来表示原始类型的数组,总是使用 2. 来表示对象数组,而 C 可以将任何一种用于任何类型......?

如果我使用ArrayList带有频繁append()s 的 an 怎么办(就像我们手头的情况一样)?只复制数组,而不复制数组中的对象?另外,当数组被复制时,即使旧数组在 Tenured 中,新数组也会在 Eden 中开始,对吗?

4

3 回答 3

5

但是在 Java 中,对象数组不只是引用数组吗?

只是参考。所有对象都在堆上分配,从不在数组或堆栈中分配(至少在正式情况下,优化器可能会尽可能使用堆栈分配,但这是透明的)。

如果每个对象都比参考大得多,它不应该接近它“包含”的对象的总大小。

是的,在 Java 中,每当您说“分配/存储对象”时,您的意思是引用(C 术语中的指针)。

如果我使用带有频繁 append() 的 ArrayList(就像我们手头的情况一样)怎么办?只复制数组,而不复制数组中的对象?

数组仅在需要调整大小时才被复制,即很少复制,摊销成本与插入次数成正比。引用的对象永远不会被复制。

此外,当数组被复制时,即使旧数组在 Tenured 中,新数组也会在 Eden 中开始,对吗?

是的!

于 2013-10-16T18:30:43.917 回答
2

用于-XX:PretenureSizeThreshold调优不太可能对您有所帮助。此参数仅适用于直接 Eden 分配,而大多数分配发生在 TLAB(线程本地分配缓冲区)中并被-XX:PretenureSizeThreshold忽略。

对于主动分配内存(几兆字节)的线程,TLAB 可能非常大。

您可以调整 TLAB 大小以减少这种影响,但这可能弊大于利。

于 2013-10-17T05:52:44.917 回答
2

但是在 Java 中,对象数组不只是引用数组吗?即数组中的每个对象,以及数组对象本身,在内存中是分开的并且被垃圾收集器视为分开的?

是的。

我认为存在混淆,因为 AFAIK,在 C 中:

  1. 有可能有一个真正存储结构的结构数组。
  2. 也可以有一个指向结构的指针数组。

我很确定 Java 总是使用 1. 来表示原始类型的数组,总是使用 2. 来表示对象数组,而 C 可以将任何一种用于任何类型......?

Java 和 C 一样,通常将原始类型的数组存储为具有这些类型元素的实际数组。所以一个int[]有 10 个元素的数组通常会为数组保留 10×4 字节,加上整个数组对象的开销。

但是,正如您所说,对象数组是引用数组。因此object[],10 个元素中的一个通常会占用 10×4 字节(或者在 64 位 CPU 上可能是 10×8 字节)用于数组,加上开销,以及每个非空元素引用的每个对象的空间。这在 C 中对应于指针数组。

(我使用术语“通常”,因为即使大多数 JVM 都是这样做的,但它们不需要以任何特定方式分配内存。)

另请注意,Java 没有真正的多维数组,如 C(或 C#)。Java 中的 Anint[][]实际上是一个一维数组,其中每个元素都是对其自身int[]子数组的引用。在 C 中,一个int[][]真的是一个二维整数数组(除了第一个维度之外的所有维度的长度必须在编译时知道)。

附录

另请注意,就像您说的那样,C 可以具有真正的结构数组,它们既不是原始类型也不是指针。Java 没有这种能力。

于 2013-10-16T22:12:53.173 回答