什么是Java内存管理最佳实践?

时间:2009-03-09 20:00:52

标签: java memory-management

我正在接管前一位开发人员的一些申请。当我通过Eclipse运行应用程序时,我看到内存使用情况和堆大小增加了很多。经过进一步调查,我发现他们正在循环中创建一个对象以及其他东西。

我开始经历一些清理工作。但是我经历的越多,我就越想问“这实际上会做什么吗?”

例如,不是在上面提到的循环之外声明变量而只是在循环中设置它的值......它们在循环中创建了对象。我的意思是:

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);
}

我是不是说底部循环更好?也许我错了。

另外,在上面的第二个循环之后,我将“something”设置为null?这会清除一些记忆吗?

在任何一种情况下,我可以遵循哪些良好的内存管理最佳实践,这将有助于在我的应用程序中保持较低的内存使用率?

更新

到目前为止,我感谢每个人的反馈。但是,我并没有真正询问上面的循环(尽管根据你的建议,我确实回到了第一个循环)。我正在努力获得一些我可以留意的最佳实践。 “当你完成使用Collection时,清除它”的界限。我真的需要确保这些应用程序不会占用太多内存。

13 个答案:

答案 0 :(得分:37)

不要试图超越虚拟机。第一个循环是建议的最佳实践,包括性能和可维护性。在循环之后将引用设置回null将不保证立即释放内存。当您使用最小范围时,GC将发挥最佳作用。

详细介绍这些内容的书籍(从用户的角度来看)是Effective Java 2Implementation Patterns

如果您想了解有关性能和VM内部的更多信息,您需要查看来自Brian Goetz的讲座或阅读书籍。

答案 1 :(得分:9)

something的范围外,这两个循环是等效的;有关详细信息,请参阅this question

一般最佳做法?嗯,让我们看看:除非你有充分的理由,否则不要在静态变量中存储大量数据。完成后,从集合中删除大对象。哦,是的,“措施,不要猜。”使用分析器查看内存的分配位置。

答案 2 :(得分:8)

两个代码示例中都没有创建对象。您只需将对象引用设置为arrayOfStuff中已有的字符串。记忆力没有区别。

答案 3 :(得分:5)

两个循环将使用基本相同的内存量,任何差异都可以忽略不计。 “String something”仅创建对象的引用,而不是自身的新对象,因此使用的任何额外内存都很小。此外,编译器/与JVM结合可能会优化生成的代码。

对于内存管理实践,您应该尝试更好地分析内存,以了解实际存在的瓶颈。特别注意指向大块内存的静态引用,因为它永远不会被收集。

您还可以查看弱引用和其他专门的内存管理类。

最后,请记住,如果应用程序占用内存,可能有原因....

更新内存管理的关键是数据结构,以及您需要/何时需要多少性能。权衡通常在内存和CPU周期之间进行。

例如,缓存可以占用大量内存,特别是为了提高性能,因为您试图避免昂贵的操作。

因此,请仔细考虑您的数据结构,并确保您不会将内容保留在内存中的时间超过必须的时间。如果它是一个Web应用程序,请避免将大量数据存储到会话变量中,避免对大量内存池进行静态引用等。

答案 4 :(得分:5)

JVM最擅长释放短期对象。尽量不要分配不需要的对象。但在了解工作负载,对象生存期和对象大小之前,无法优化内存使用情况。分析器可以告诉你这个。

最后,你必须避免做的第一件事:永远不要使用终结器。终结器会干扰垃圾收集,因为对象不能只是被释放,而是必须排队等待最终确定,这可能会也可能不会发生。最好不要使用终结器。

至于你在Eclipse中看到的内存使用情况,它并不一定相关。 GC将根据可用内存量来完成其工作。如果您有大量可用内存,则在关闭应用程序之前可能看不到单个GC。如果您发现您的应用程序内存不足,那么只有真正的分析器可以告诉您泄漏或效率低下的位置。

答案 5 :(得分:4)

第一个循环更好。因为

  • 变量更清晰(理论上)
  • 该程序最好阅读。

但是从记忆的角度来看,这是无关紧要的。

如果你有内存问题,那么你应该分析它的消费地点。

答案 6 :(得分:4)

在我看来,你应该避免像这样的微优化。它们耗费了大量的脑循环,但大部分时间都没有什么影响。

您的应用程序可能有一些中央数据结构。那些是你应该担心的。例如,如果填充它们,则使用良好的大小估计来预分配它们,以避免重复调整底层结构的大小。这尤其适用于StringBufferArrayListHashMap等。设计好您对这些结构的访问权限,这样您就不必复制很多。

使用适当的算法访问数据结构。在最低级别,如您提到的循环,使用Iterator,或者至少避免一直调用.size()。 (是的,你每次都要询问列表的大小,大部分时间都不会改变。)顺便说一句,我经常看到Map的类似错误。人们迭代keySet()get每个值,而不是首先迭代entrySet()。内存管理器会感谢你额外的CPU周期。

答案 7 :(得分:2)

正如上面提到的一张海报,使用分析器来测量程序某些部分的内存(和/或cpu)使用情况,而不是试图猜测它。您可能会对所发现的内容感到惊讶!

此外还有一个额外的好处。您将更了解您的编程语言和应用程序。

我使用VisualVM进行性能分析并大力推荐它。它带有jdk / jre发行版。

答案 8 :(得分:1)

嗯,第一个循环实际上更好,因为某些东西的范围更小。关于内存管理 - 它没有太大区别。

当您将对象存储在集合中但忘记删除它们时,大多数Java内存问题都会出现。否则GC会使他的工作非常好。

答案 9 :(得分:1)

第一个例子很好。除了每次循环的堆栈变量分配和释放(非常便宜和快速)之外,没有任何内存分配。

原因是所有被“分配”的都是一个引用,它是一个4字节的堆栈变量(无论如何在大多数32位系统上)。堆栈变量通过添加到表示堆栈顶部的内存地址来“分配”,因此非常快速且便宜。

你需要注意的是:

这样的循环
for (int i = 0; i < some_large_num; i++)
{
   String something = new String();
   //do stuff with something
}

因为那实际上正在进行内存分配。

答案 10 :(得分:1)

如果您还没有,我建议安装Eclipse Test & Performance Tools Platform(TPTP)。如果要转储并检查堆,请查看SDK jmap and jhat工具。另请参阅Monitoring and Managing Java SE 6 Platform Applications

答案 11 :(得分:0)

“我不正确地说底部循环更好吗?”,答案是否定的,不仅更好,在同样的情况下是必要的......变量定义(不是内容),是在内存堆中进行的,并且是有限的,在第一个例子中,每个循环在这个内存中创建一个实例,如果“arrayOfStuff”的大小很大,可能会出现“Out of memory error:java heap space”....

答案 12 :(得分:0)

据我了解你所看到的,底部循环并不是更好。原因是,即使您尝试重用单个引用(例如),事实是对象(Ex - arrayOfStuff.get(i))仍然从List(arrayOfStuff)引用。要使对象符合收集条件,不应从任何地方引用它们。如果您确定此后的List的生命周期,您可以决定在单独的循环中从中删除/释放对象。

可以从静态角度进行优化(即,不从任何其他线程对此List进行修改),最好避免重复调用size()。也就是说,如果你不期望改变大小,那么为什么要一次又一次地计算它呢?毕竟它不是array.length,它是list.size()。