什么是一些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度来看,这是无关紧要的。

如果你有内存问题,那么你应该在哪里消费。

在我看来,你应该避免像这样的微观优化。 他们花费了大量的大脑周期,但大部分时间影响不大。

你的应用程序可能有一些中央数据结构。 那些是你应该担心的。 例如,如果你填充他们预先分配他们的大小的一个很好的估计,以避免重复调整底层结构。 这特别适用于StringBufferArrayListHashMap等等。 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()。