有哪些 Java 内存管理最佳实践?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/627784/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 17:08:00  来源:igfitidea点击:

What are some Java memory management best practices?

javamemory-management

提问by Ascalonian

I am taking over some applications from a previous developer. When I run the applications through Eclipse, I see the memory usage and the heap size increase a lot. Upon further investigation, I see that they were creating an object over-and-over in a loop as well as other things.

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

I started to go through and do some clean up. But the more I went through, the more questions I had like "will this actually do anything?"

我开始通过并做一些清理。但我经历的越多,我的问题就越多,比如“这真的有什么作用吗?”

For example, instead of declaring a variable outside the loop mentioned above and just setting its value in the loop... they created the object in the loop. What I mean is:

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

for(int i=0; i < arrayOfStuff.size(); i++) {
    String something = (String) arrayOfStuff.get(i);
    ...
}

versus

相对

String something = null;
for(int i=0; i < arrayOfStuff.size(); i++) {
    something = (String) arrayOfStuff.get(i);
}

Am I incorrect to say that the bottom loop is better? Perhaps I am wrong.

我说底部循环更好是错误的吗?也许我错了。

Also, what about after the second loop above, I set "something" back to null? Would that clear out some memory?

另外,在上面的第二个循环之后,我将“某物”设置回 null 怎么样?这会清除一些记忆吗?

In either case, what are some good memory management best practices I could follow that will help keep my memory usage low in my applications?

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

Update:

更新:

I appreciate everyones feedback so far. However, I was not really asking about the above loops (although by your advice I did go back to the first loop). I am trying to get some best practices that I can keep an eye out for. Something on the lines of "when you are done using a Collection, clear it out". I just really need to make sure not as much memory is being taken up by these applications.

到目前为止,我感谢大家的反馈。但是,我并没有真正询问上述循环(尽管根据您的建议,我确实回到了第一个循环)。我正在尝试获得一些我可以留意的最佳实践。关于“使用完集合后,将其清除”之类的内容。我真的需要确保这些应用程序不会占用那么多内存。

采纳答案by cherouvim

Don't try to outsmart the VM. The first loop is the suggested best practice, both for performance and maintainability. Setting the reference back to null after the loop will not guarantee immediate memory release. The GC will do its job best when you use the minimum scope possible.

不要试图超越虚拟机。第一个循环是建议的最佳实践,无论是性能还是可维护性。在循环之后将引用设置回 null 将不能保证立即释放内存。当您使用尽可能小的范围时,GC 将最好地完成它的工作。

Books which cover these things in detail (from the user's perspective) are Effective Java 2and Implementation Patterns.

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

If you care to find out more about performance and the inners of the VM you need to see talks or read books from Brian Goetz.

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

回答by Michael Myers

Those two loops are equivalent except for the scope of something; see this questionfor details.

除了 ; 的范围外,这两个循环是等效的something。有关详细信息,请参阅此问题

General best practices? Umm, let's see: don't store large amounts of data in static variables unless you have a good reason. Remove large objects from collections when you're done with them. And oh yes, "Measure, don't guess." Use a profiler to see where the memory is being allocated.

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

回答by Jean Barmash

The two loops will use basically the same amount of memory, any difference would be negligible. "String something" only creates a reference to an object, not a new object in itself and thus any additional memory used is small. Plus, compiler / combined with JVM will likely optimize the generated code anyway.

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

For memory management practices, you should really try to profile your memory better to understand where the bottlenecks actually are. Look especially for static references that point to a big chunk of memory, since that will never get collected.

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

You can also look at Weak References , and other specialized memory management classes.

您还可以查看 Weak References 和其他专门的内存管理类。

Lastly, keep in mind, that if an application takes up memory, there might be a reason for it....

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

UpdateThe key to memory management is data structures, as well as how much performance you need / when. The tradeoff is often between memory and CPU cycles.

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

For example, a lot of memory can be occupied by caching, which is specifically there to improve performance since you are trying to avoid an expensive operation.

例如,缓存可能会占用大量内存,因为您试图避免昂贵的操作,因此缓存专门用于提高性能。

So think through your data structures and make sure you don't keep things in memory for longer than you have to. If it's a web app, avoid storing a lot of data into the session variable, avoid having static references to huge pools of memory, etc.

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

回答by Horcrux7

The first loop is better. Because

第一个循环更好。因为

  • the variable something will be clear faster (theoretical)
  • the program is better to read.
  • 变量某些东西会更快地清晰(理论上)
  • 该程序更好地阅读。

But from point of memory this is irrelevant.

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

If you have memory problems then you should profile where it is consumed.

如果您有内存问题,那么您应该分析它的消耗位置。

回答by Kees de Kooter

There are no objects created in both of your code samples. You merely set an object reference to a string that is already in the arrayOfStuff. So memorywise there is no difference.

在您的两个代码示例中都没有创建对象。您只需将对象引用设置为已经存在于 arrayOfStuff 中的字符串。所以记忆方面没有区别。

回答by siddhadev

Well, the first loop is actually better, because the scope of something is smaller. Regarding memory management - it makes not a big difference.

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

Most Java memory problems come when you store objects in a collection, but forget to remove them. Otherwise the GC makes his job quite good.

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

回答by workmad3

The first example is fine. There isn't any memory allocation going on there, other than a stack variable allocation and deallocation each time through the loop (very cheap and quick).

第一个例子很好。除了每次通过循环进行堆栈变量分配和解除分配(非常便宜和快速)之外,那里没有任何内存分配。

The reason is that all that is being 'allocated' is a reference, which is a 4 byte stack variable (on most 32 bit systems anyway). A stack variable is 'allocated' by adding to a memory address representing the top of the stack, and so is very quick and cheap.

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

What you need to be careful of is for loops like:

您需要注意的是 for 循环,例如:

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

as that is actually doing memory allocatiton.

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

回答by u7867

The JVM is best at freeing short-lived objects. Try not to allocate objects you don't need. But you can't optimize the memory usage until you understand your workload, the object lifetime, and the object sizes. A profiler can tell you this.

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

Finally, the #1 thing you must avoid doing: never use Finalizers. Finalizers interfere with garbage collection, since the object can't be just freed but must be queued for finalization, which may or may not occur. It's best to never use finalizers.

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

As for the memory usage you're seeing in Eclipse, it's not necessarily relevant. The GC will do its job based on how much free memory there is. If you have lots of free memory you might not see a single GC before the app is shut down. If you find your app running out of memory then only a real profiler can tell you where the leaks or inefficiencies are.

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

回答by McDowell

If you haven't already, I suggest installing the Eclipse Test & Performance Tools Platform(TPTP). If you want to dump and inspect the heap, check out the SDK jmap and jhattools. Also see Monitoring and Managing Java SE 6 Platform Applications.

如果您还没有安装,我建议您安装Eclipse 测试和性能工具平台(TPTP)。如果要转储和检查堆,请查看 SDK jmap 和 jhat工具。另请参阅监视和管理 Java SE 6 平台应用程序

回答by Ronald Blaschke

In my opinion, you should avoid micro-optimizations like these. They cost a lot of brain cycles, but most of the time have little impact.

在我看来,你应该避免像这样的微优化。它们花费了大量的大脑周期,但大多数时候影响不大。

Your application probably has a few central data structures. Those are the ones you should be worried about. For example, if you fill them preallocate them with a good estimate of the size, to avoid repeated resizing of the underlying structure. This especially applies to StringBuffer, ArrayList, HashMapand the like. Design your access to those structures well, so you don't have to copy a lot.

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

Use the proper algorithms to access the data structures. At the lowest level, like the loop you mentioned, use Iterators, or at least avoid calling .size()all the time. (Yes, you're asking the list every time around for it's size, which most of the time doesn't change.) BTW, I've often seen a similar mistake with Maps. People iterate over the keySet()and geteach value, instead of just iterating over the entrySet()in the first place. The memory manager will thank you for the extra CPU cycles.

使用适当的算法来访问数据结构。在最底层,就像你提到的循环一样,使用Iterators,或者至少避免一直调用.size()。(是的,您每次都在询问列表的大小,大部分时间都不会改变。)顺便说一句,我经常看到与Maps类似的错误。人们遍历keySet()get每一个值,而不是仅仅遍历entrySet()摆在首位。内存管理器会感谢您提供额外的 CPU 周期。