对 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?.
最近在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?.