为什么java等待这么长时间来运行垃圾收集器?

gog*_*n13 49 java memory memory-management playframework

我正在使用Play构建一个Java Web应用程序!框架.我在playapps.net上托管它.在提供的内存消耗图表上,我一直困惑不已.这是一个示例:

堆内存

该图表来自一致但名义上的活动期.我没有做任何事情来触发内存的衰减,所以我认为这是因为垃圾收集器运行,因为它几乎达到了允许的内存消耗.

我的问题:

  • 它是公平的,我认为我的应用程序并不会有内存泄漏,因为它似乎是所有内存都被正确地被垃圾回收器回收它在运行时?
  • (从标题)为什么java等到最后一秒可能运行垃圾收集器?随着内存消耗增长到图表的前四分之一,我看到显着的性能下降.
  • 如果我的断言是正确的,那么我该如何解决这个问题?我在SO上读过的其他帖子似乎反对调用System.gc(),范围从中性("它只是运行GC的请求,所以JVM可能只是忽略你")完全反对("依赖的代码System.gc()从根本上被打破") .或者我不在这里,我应该在我自己的代码中寻找导致此行为和间歇性性能损失的缺陷?

更新
我已经在PlayApps.net上开启了一个关于这个问题的讨论,并在这里提到了一些观点; 特别是@Affe关于完整GC设置非常保守的评论,以及@ G_H关于初始和最大堆大小设置的评论.

这是讨论链接,但遗憾的是您需要一个playapps帐户才能查看.

当我得到它时,我会在这里报告反馈; 非常感谢大家的回答,我已经从中学到了很多东西!

解决方案
Playapps支持,这仍然很好,对我没有很多建议,他们唯一的想法是,如果我广泛使用缓存,这可能会使对象保持活动的时间超过需要,但事实并非如此.我还是学到了很多东西(呜呜!),我给了@Ryan Amos绿色支票,因为我提出了他System.gc()每半天打电话的建议,现在工作正常.

Aff*_*ffe 22

任何详细的答案都将取决于你正在使用哪个垃圾收集器,但有些东西在所有(现代的,sun/oracle)GC中基本相同.

每当您看到图表中的用法向下时,这就是垃圾收集.堆释放的唯一方法是通过垃圾收集.事情是有两种类型的垃圾收集,轻微和完整.堆被分成两个基本的"区域".年轻和终身.(实际上有更多的子组.)任何占据Young的空间并且在小型GC出现以释放一些内存时仍在使用的东西将被"提升"为终身.一旦某些东西进入终身,它会无限期地坐着,直到堆没有可用空间并且需要完整的垃圾收集.

因此,对该图的一种解释是,您的年轻一代相当小(默认情况下,它可能是某些JVM上总堆的相当小的百分比),并且您在相当长的时间内保持对象"活着".(也许你在网络会话中持有对它们的引用?)所以你的对象是'幸存'垃圾收集,直到它们被提升到终身空间,在那里它们无限期地停留在那里,直到JVM很好并且真正的内存不足.

同样,这只是一种适合您所拥有数据的常见情况.需要有关JVM配置和GC日志的完整详细信息才能确切了解发生了什么.

  • 是的,你可以提一下它看起来像完整集合触发时的设置是如此吝啬,以至于次要集合的频率在完整集合发生之前很久就会影响性能.它也可能是使用只有一个处理器内核的并发垃圾收集器,可以预期它会以与您的问题一致的方式运行. (2认同)

Rya*_*mos 19

Java不会运行垃圾清理器,因为垃圾清理器会减慢速度,并且不应该经常运行.我认为您可以更频繁地安排清洁,例如每3小时一次.如果应用程序永远不会占用全部内存,那么就没有理由运行垃圾清理器,这就是为什么Java只在内存非常高时运行它.

所以基本上,不要担心别人说的话:做最好的事情.如果您发现在66%内存下运行垃圾清理器可以提高性能,那就去做吧.

  • Upvoted.不要听那些说"不要这样做"的人.如果它有效,不会使事情变得复杂,不会创建一个不可维护的架构,并且看不到更好的解决方案,那就去做吧. (2认同)

G_H*_*G_H 12

我注意到图表并没有严格向上倾斜直到下降,但是局部变化较小.虽然我不确定,但如果没有垃圾收集,我不认为内存使用会显示这些小滴.

Java中有一些次要和主要的集合.次要收藏经常发生,而主要收藏品更少见,性能更低.次要集合可能倾向于清除诸如在方法中创建的短期对象实例之类的东西.主要集合将删除更多,这可能发生在图表的末尾.

现在,我在输入时发布的一些答案对垃圾收集器,对象生成等方面的差异给出了很好的解释.但这仍然无法解释为什么在进行严肃的清洁工作之前需要花费如此荒谬的时间(将近24小时).

在启动时可以为JVM设置的两件有趣的事情是允许的最大堆大小和初始堆大小.最大值是硬限制,一旦达到该限制,进一步的垃圾收集不会减少内存使用量,如果需要为对象或其他数据分配新空间,则会出现OutOfMemoryError.但是,内部也存在软限制:当前堆大小.JVM不会立即吞噬最大内存量.相反,它从您的初始堆大小开始,然后在需要时增加堆.可以把它想象成JVM的RAM,可以动态增加.

如果应用程序的实际内存使用量开始达到当前堆大小,则通常会启动垃圾回收.这可能会减少内存使用,因此不需要增加堆大小.但是,应用程序当前确实需要所有内存并且超出堆大小也是可能的.在这种情况下,只要它尚未达到最大设定限制,它就会增加.

现在,您的情况可能是初始堆大小设置为与最大值相同的值.假设是这样,那么JVM将立即抓住所有内存.应用程序累积足够的垃圾以达到内存使用中的堆大小需要很长时间.但那一刻你会看到一个大集合.从足够小的堆开始并允许它增长使得内存使用限制在所需的范围内.

这假设您的图表显示堆使用而未分配堆大小.如果情况并非如此,并且您实际上看到堆本身就像这样增长,那么还会发生其他事情.我承认我对垃圾收集的内部及其调度非常不够精确,以确定这里发生的事情,其中​​大部分是通过观察剖析器中泄漏的应用程序.所以如果我提供了错误的信息,我会把这个答案拿下来.

  • 这有可能发生.人们有时会认为"嘿,我只是将start和max设置为相同,因此Java不必为这个堆调整大小的业务而烦恼".但实际上,这实际上可能会降低性能.如果你不关心大量未使用的内存闲置一段时间,那么它可能是有道理的.但是大部分时间你都希望与系统中渴望内存的其他进程一起玩得很好. (2认同)