对 Java / Android 中现有对象的短期引用的开销

Overhead of short-lived references to existing objects in Java / Android

最近在Android看到一篇关于内存优化的文章,但我觉得我的问题比较笼统Java。我找不到这方面的任何信息,所以如果你能给我指出一个好的阅读资源,我将不胜感激。

我说的文章可以找到here

我的问题与以下两个片段有关:

非最佳版本:

List<Chunk> mTempChunks = new ArrayList<Chunk>();
for (int i = 0; i<10000; i++){
    mTempChunks.add(new Chunk(i));
}

for (int i = 0; i<mTempChunks.size(); i++){
    Chunk c = mTempChunks.get(i);
    Log.d(TAG,"Chunk data: " + c.getValue());
}

优化版本:

Chunk c;
int length = mTempChunks.size();
for (int i = 0; i<length; i++){
    c = mTempChunks.get(i);
    Log.d(TAG,"Chunk data: " + c.getValue());
}

该文章还包含以下几行(与第一个片段相关):

In the second loop of the code snippet above, we are creating a new chunk object for each iteration of the loop. So it will essentially create 10,000 objects of type ‘Chunk’ and occupy a lot of memory.

我想了解的是为什么会提到新对象的创建,因为我只能看到对堆上已经存在的对象 的引用的创建。我知道引用本身会花费 4-8 个字节,具体取决于系统,但在这种情况下它们很快就会超出范围,除此之外我看不到任何额外的开销。

也许是创建对现有对象的引用,当数量众多时被认为是昂贵的?

请告诉我我在这里遗漏了什么,以及这两个片段在内存消耗方面的真正区别是什么。

谢谢。

有两点不同:

非最优:

  • i < mTempChunks.size()
  • Chunk c = mTempChunks.get(i);

最优:

  • i < length
  • c = mTempChunks.get(i);

在非最优代码中,循环的每次迭代都会调用size()方法,并且一个新的reference指向一个Chunk对象被建造。在最优代码中,避免了重复调用size()的开销,回收了相同的引用

然而,那篇文章的作者建议在非最佳循环中创建10000个临时对象似乎是错误的。当然,创建了 10000 个临时对象 ,但在第一个而不是第二个循环中,没有办法避免这种情况。在第二个非最佳循环中,创建了 10000 references。所以在某种程度上它不是最佳的,尽管作者将树木误认为是森林。

更多参考资料:

1. Avoid Creating Unnecessary Objects.

2. Use Enhanced For Loop Syntax.

编辑:

有人指责我是骗子。对于那些说调用 size()no 开销的人,我只能引用 官方文档 :

3. Avoid Internal Getters/Setters.

编辑 2:

在我的回答中,我最初犯了一个错误,说 引用内存是在编译时在堆栈上分配的 。我现在意识到这种说法是错误的;这实际上是 C++ 中的工作方式,而不是 Java。 Java 的世界是 C++ 的颠倒:虽然用于引用的内存确实是在堆栈上分配的,但在 Java 中甚至发生在运行时。脑洞大开!

参考文献:

1. Runtime vs compile time memory allocation in java.

2. Where is allocated variable reference, in stack or in the heap?.

3. The Structure of the Java Virtual Machine - Frames.