什么是一些Java内存pipe理最佳实践?
我正在接pipe以前开发人员的一些应用程序。 当我通过Eclipse运行应用程序时,我发现内存使用量和堆大小都增加了很多。 经过进一步的调查,我发现他们正在循环创造一个对象,以及其他的东西。
我开始经过并做一些清理工作。 但是我经历的越多,问题就越多,“这实际上会做什么吗?”
例如,不是在上面提到的循环之外声明一个variables,而是在循环中设置它的值……他们在循环中创build了这个对象。 我的意思是:
for(int i=0; i < arrayOfStuff.size(); i++) { String something = (String) arrayOfStuff.get(i); ... }
与
String something = null; for(int i=0; i < arrayOfStuff.size(); i++) { something = (String) arrayOfStuff.get(i); }
我不正确的说,底部循环更好? 也许我错了。
另外,上面的第二个循环后,我把“东西”设置为空? 会清除一些记忆吗?
在这两种情况下,我可以遵循的一些好的内存pipe理最佳实践是什么将有助于在我的应用程序中保持低内存使用率?
更新:
我很欣赏到目前为止所有人的反馈。 但是,我并没有真正问上述循环(虽然通过你的build议,我回到了第一个循环)。 我正试图获得一些我可以留意的最佳实践。 有些内容是“当你使用一个集合,清除它”。 我真的需要确保这些应用程序没有占用太多内存。
不要试图超越虚拟机。 第一个循环是build议的最佳实践,无论是性能还是可维护性。 在循环之后将引用设置为空将不能保证立即释放内存。 当您尽可能使用最小范围时,GC将尽其最大的努力。
从用户的angular度详细讲述这些内容的书籍是有效的Java 2和实现模式 。
如果你想了解更多关于虚拟机的性能和内部信息,你需要看看Brian Goetz的演讲或阅读书籍。
除了something
的范围外,这两个循环是等价的。 看到这个问题的细节。
一般最佳做法? 嗯,让我们看看:除非你有很好的理由,否则不要在静态variables中存储大量的数据。 完成后,从集合中删除大对象。 哦,是的,“测量,不要猜测。” 使用分析器查看内存分配的位置。
在这两个代码示例中都没有创build对象。 您只需将对象引用设置为已经在arrayOfStuff中的string即可。 所以记忆方面没有区别。
这两个循环将使用基本相同的内存量,任何差异将可以忽略不计。 “string的东西”只创build一个对象的引用,而不是一个新的对象本身,因此任何额外的内存使用是小的。 另外,编译器/与JVM结合可能会优化生成的代码。
对于内存pipe理实践,你应该尝试更好地分析你的内存,以了解实际存在的瓶颈。 尤其要注意指向大块内存的静态引用,因为这将永远不会被收集。
你也可以看看弱引用,以及其他专门的内存pipe理类。
最后,请记住,如果应用程序占用内存,可能有一个原因….
更新内存pipe理的关键是数据结构,以及您需要/何时需要多less性能。 权衡通常在内存和CPU周期之间。
例如,大量的内存可以被caching占用,这是为了提高性能,因为你试图避免昂贵的操作。
所以仔细考虑一下你的数据结构,确保你不会把事情放在内存中的时间比你所需要的时间长。 如果它是一个Web应用程序,请避免将大量数据存储到会话variables中,避免对大量内存池进行静态引用等。
JVM最好是释放短暂的对象。 尽量不要分配你不需要的对象。 但是,除非您了解工作负载,对象生命周期和对象大小,否则无法优化内存使用情况。 一个分析器可以告诉你这个。
最后,你必须避免做的#1事情:从不使用终结器。 终结器会干扰垃圾回收,因为对象不能被释放,但必须排队等待终止,这可能会发生,也可能不会发生。 最好不要使用终结器。
至于你在Eclipse中看到的内存使用情况,这不一定相关。 GC将根据有多less空闲内存来完成工作。 如果你有很多空闲的内存,你可能在应用程序closures之前看不到一个GC。 如果你发现你的应用程序内存不足,那么只有一个真正的分析器可以告诉你泄漏或效率低下的地方。
第一个循环更好。 因为
- variables的东西会更快清楚(理论上)
- 该程序更好阅读。
但从记忆的angular度来看,这是无关紧要的。
如果你有内存问题,那么你应该在哪里消费。
在我看来,你应该避免像这样的微观优化。 他们花费了大量的大脑周期,但大部分时间影响不大。
你的应用程序可能有一些中央数据结构。 那些是你应该担心的。 例如,如果你填充他们预先分配他们的大小的一个很好的估计,以避免重复调整底层结构。 这特别适用于StringBuffer
, ArrayList
, HashMap
等等。 devise好你对这些结构的访问,所以你不必复制很多东西。
使用适当的algorithm来访问数据结构。 在最低级别,像你提到的循环,使用Iterator
,或者至less避免一直调用.size()
。 (是的,你每次都要问清单的大小,大部分时间不会改变。)顺便说一句,我经常看到与Map
相似的错误。 人们遍历keySet()
并get
每个值,而不是只是迭代entrySet()
。 内存pipe理器会感谢您额外的CPU周期。
正如上面提到的一个海报,使用profiler来测量程序某些部分的内存(和/或cpu)使用情况,而不是试图猜测它。 你可能会惊讶于你的发现!
还有一个额外的好处。 你会更了解你的编程语言和你的应用程序。
我使用VisualVM进行分析并大大推荐。 它带有jdk / jre分发。
那么,第一个循环实际上是更好的,因为东西的范围更小。 关于内存pipe理 – 这并没有太大的区别。
大多数Java内存问题出现在将对象存储在集合中时,但忘记删除它们。 否则,GC使他的工作相当好。
第一个例子很好。 这里没有任何内存分配,除了每次通过循环的堆栈variables分配和释放(非常便宜和快速)以外。
原因是所有被“分配”的是一个引用,它是一个4字节的堆栈variables(在大多数32位系统上)。 一个堆栈variables是通过添加一个代表堆栈顶部的内存地址来“分配”的,因此非常快速和便宜。
你需要注意的是循环如下:
for (int i = 0; i < some_large_num; i++) { String something = new String(); //do stuff with something }
因为这实际上是在做内存分配。
如果你还没有,我build议安装Eclipsetesting和性能工具平台 (TPTP)。 如果您想转储并检查堆,请查看SDK jmap和jhat工具。 另请参阅监视和pipe理Java SE 6平台应用程序 。
“我是不正确的,说底部循环更好?”,答案是否定的,不仅是更好的,同样的情况下是必要的…variables定义(而不是内容),是在内存堆中,是有限的,在第一个例子中,每个循环都在这个内存中创build一个实例,如果“arrayOfStuff”的大小很大,可能会发生“内存不足错误:java堆空间”….
从我所了解的你看,底部循环是没有更好的。 原因是即使你试图重用单个引用(Ex-something),实际上对象(Ex – arrayOfStuff.get(i))仍然是从List(arrayOfStuff)引用的。 为了使对象有资格收集,他们不应该从任何地方引用。 如果在此之后确定列表的生命,则可以决定在单独的循环内移除/释放对象。
可以从静态angular度进行优化(即,不会从其他任何线程对此列表进行修改),最好避免重复调用size()。 这就是说,如果你不希望大小发生变化,那么为什么一次又一次地计算呢? 毕竟它不是一个array.length,它是list.size()。