在Java中理论上是否可以使用“结构”数组?

时间:2012-02-21 23:40:04

标签: java memory jvm memory-efficient

有些情况需要内存有效存储大量对象。要在Java中执行此操作,您必须使用多个原始数组(请参阅下面的原因)或大字节数组,这会产生转换的CPU开销。

示例:您有class Point { float x; float y;}。现在,您希望将N个点存储在一个数组中,该数组在浮点数上至少需要N * 8个字节,在32位JVM上需要N * 4个字节作为参考。所以至少1/3是垃圾(这里不计入正常的对象开销)。但是如果你将它存储在两个浮点数组中,那么一切都会好的。

我的问题:为什么Java没有优化引用数组的内存使用量?我的意思是为什么不直接将对象嵌入到数组中,就像在C ++中完成一样?

E.g。标记类Point final应该足以让JVM查看Point类的最大数据长度。或者这会反对the specification?在处理大型n维矩阵等时,这也会节省大量内存

更新

我想知道JVM理论上可以对它进行优化(例如在场景后面)以及在哪些条件下 - 我不能以某种方式强制JVM。我认为结论的第二点是它完全无法轻易完成的原因。

结束JVM需要知道的内容:

  1. 该类需要最终让JVM猜测一个数组条目的长度
  2. 数组需要只读。当然,您可以更改Point p = arr[i]; p.setX(i)之类的值,但不能通过inlineArr[i] = new Point()写入数组。或者JVM必须引入复制语义,这将违背“Java方式”。见aroth的回答
  3. 如何初始化数组(调用默认构造函数或将成员初始化为默认值)

3 个答案:

答案 0 :(得分:3)

Java没有提供这样做的方法,因为它不是语言级别的选择。 C,C ++等公开了这样做的方法,因为它们是系统级编程语言,您需要了解系统级功能并根据您使用的特定体系结构做出决策。

在Java中,您的目标是JVM。 JVM没有说明这是否是允许的(我假设这是真的;我没有彻底梳理JLS以证明我就在这里)。我们的想法是,当您编写Java代码时,您相信JIT可以做出明智的决策。这是参考类型可以折叠成阵列等的地方。所以这里的“Java方式”是你无法指定它是否发生,但是如果JIT可以进行优化并提高性能,那么就可以而且。

我不确定这个优化是否已实现,但我知道类似的是:例如,用new分配的对象在概念上位于“堆”上,但是如果JVM注意到(通过一种称为转义分析的技术,该对象是方法本地的,它可以在堆栈上分配对象的字段,甚至直接在CPU寄存器中分配,完全消除“堆分配”开销而不会发生语言变化。

更新更新的问题

如果问题是“这可以做到这一点”,我认为答案是肯定的。有一些极端情况(例如空指针),但您应该能够解决它们。对于空引用,JVM可以说服自己永远不会有null元素,或者如前所述保持位向量。这两种技术都可能以逃逸分析为基础,表明数组引用永远不会离开该方法,因为我可以看到,如果您尝试例如,例如,簿记会变得棘手。将其存储在对象字段中。

答案 1 :(得分:1)

您描述的场景可能会节省内存(虽然在实践中我不确定它是否会这样做),但实际上将对象放入数组时可能会增加相当多的计算开销。请注意,当您执行new Point()时,您创建的对象将在堆上动态分配。因此,如果通过调用Point分配100个new Point()实例,则无法保证它们的位置在内存中是连续的(实际上它们很可能不会分配给连续的内存块)。

那么Point实例如何实际进入“压缩”数组呢?在我看来,Java必须将Point中的每个字段显式复制到为该数组分配的连续内存块中。对于具有许多字段的对象类型,这可能会变得昂贵。不仅如此,原始Point实例仍在占用堆上的空间以及数组内部。因此,除非它立即被垃圾收集(我想任何引用都可以重写以指向放在数组中的副本,从而理论上允许立即对原始实例进行垃圾收集)你实际上使用的存储量比你要多如果你刚刚在数组中存储了引用。

此外,如果你有多个“压缩”数组和一个可变对象类型怎么办?将对象插入数组必然会将该对象的字段复制到数组中。所以,如果你做了类似的事情:

Point p = new Point(0, 0);
Point[] compressedA = {p};  //assuming 'p' is "optimally" stored as {0,0}
Point[] compressedB = {p};  //assuming 'p' is "optimally" stored as {0,0}

compressedA[0].setX(5)  
compressedB[0].setX(1)  

System.out.println(p.x);
System.out.println(compressedA[0].x);
System.out.println(compressedB[0].x);

......你会得到:

0
5
1

...即使逻辑上应该只有Point的单个实例。存储引用可避免此类问题,并且还意味着在多个阵列之间共享非平凡对象的任何情况下,您的总存储使用量可能更低,而不是每个阵列存储副本所有该对象的字段。

答案 2 :(得分:0)

这不等于提供如下的琐碎课程吗?

class Fixed {
   float hiddenArr[];
   Point pointArray(int position) {
      return new Point(hiddenArr[position*2], hiddenArr[position*2+1]);
   }
}

此外,可以在不使程序员明确声明他们喜欢的情况下实现这一点; JVM已经知道“值类型”(C ++中的POD类型);其中只包含其他普通旧数据类型的那些。我相信HotSpot在堆栈省略期间使用这些信息,没有理由不能为数组做这个吗?