在Java中突发内存使用

joc*_*ull 25 java garbage-collection memory-leaks memory-management

我试图在Java中处理正确的内存使用和垃圾收集.无论如何我都不是新手程序员,但在我看来,一旦Java接触到某些内存,它就永远不会被其他应用程序使用.在这种情况下,您必须确保峰值内存永远不会太高,或者您的应用程序将持续使用峰值内存使用量.

我写了一个小样本程序试图证明这一点.它基本上有4个按钮......

  1. BigList = new ArrayList<string>()使用大约25,000,000个长字符串项填充类范围变量.
  2. 呼叫 BigList.clear()
  3. 重新BigList = new ArrayList<string>()分配列表 - 再次(缩小列表大小)
  4. 致电System.gc()- 是的,我知道这并不意味着GC会真正运行,但这就是我们所拥有的.

接下来,我在Windows,Linux和Mac OS上进行了一些测试,同时使用默认任务监视器来检查进程报告的内存使用情况.这是我发现的......

  • Windows - 抽取列表,调用clear,然后多次调用GC不会减少内存使用量.但是,使用new几次重新分配列表然后多次调用GC将减少内存使用量回到起始级别.IMO,这是可以接受的.
  • Linux(我在Sun JVM上使用了Mint 11发行版) - 与Windows相同的结果.
  • Mac OS - 我遵循了与上面相同的步骤,但即使重新初始化列表调用GC似乎也没有效果.该程序将使用数百MB的RAM,即使我没有内存.

任何人都可以向我解释这个吗?有些人告诉我一些关于"堆"记忆的东西,但我仍然不完全理解它,我不确定它是否适用于此.据我所知,我不应该看到我在Windows和Linux上的行为.

这只是Mac OS的活动监视器测量内存使用情况的方式还是有其他不同之处?我宁愿不让我的程序闲置大量的RAM使用.感谢您的见解.

Wil*_*ung 19

Sun/Oracle JVM不会将不需要的内存返回给系统.如果给它一个较大的最大堆大小,并且实际上在某个时候使用了该堆空间,则JVM不会将其返回给OS以供其他用途.其他JVM会这样做(JRockit曾经,但我不认为它会再做).

因此,对于Oracles JVM,您需要调整应用程序和系统的峰值使用率,这就是它的工作原理.如果您正在使用的内存可以使用字节数组进行管理(例如使用图像或其他内容),那么您可以使用映射的字节缓冲区而不是Java字节数组.映射的字节缓冲区直接从系统中获取,而不是堆的一部分.当你释放这些对象时(并且它们是GC,我相信,但不确定),内存将返回到系统.假设它甚至可以应用,你可能不得不玩那个.


Ste*_*n C 5

...但在我看来,一旦Java触及一些记忆,它就永远消失了.你永远不会得到它.

这取决于你的意思是"永远消失".

我还听说过有人说,某些JVM 给内存给操作系统时,他们都愿意并能够.遗憾的是,考虑到低级内存API通常的工作方式,JVM必须返回整个段,并且"撤出"一个段以便可以返回它往往很复杂.

但我不会依赖它...因为有各种各样的东西可以阻止记忆被回馈.可能是JVM 不会将内存返回给操作系统.但在JVM将继续使用它的意义上,它并没有"永远消失".即使JVM再也没有接近峰值使用,所有内存都将有助于提高垃圾收集器的运行效率.

在这种情况下,您必须确保峰值内存永远不会太高,否则您的应用程序将不断耗尽数百MB的RAM.

事实并非如此.假设您采用的是从小堆开始并让它增长的策略,JVM不会要求比峰值内存大得多的内存.JVM不会持续占用更多内存......除非你的应用程序有内存泄漏并且(因此)其峰值内存要求没有限制.

(OP的评论表明,这不是他想说的.即便如此,这也是他所说的.)


关于垃圾收集效率的主题,我们可以将高效垃圾收集器的运行成本建模为:

cost ~= (amount_of_live_data * W1) + (amount_of_garbage * W2)
Run Code Online (Sandbox Code Playgroud)

其中W1和W2是(我们假设)取决于收集器的常数.(实际上,这是一个过度简化.第一部分不是活动对象数量的线性函数.但是,我声称它对以下内容无关紧要.)

然后收集器的效率可以表示为:

efficiency = cost / amount_of_garbage_collected
Run Code Online (Sandbox Code Playgroud)

哪个(如果我们假设GC收集所有数据)扩展为

efficiency ~= (amount_of_live_data * W1) / amount_of_garbage + W2.
Run Code Online (Sandbox Code Playgroud)

当GC运行时

heap_size ~= amount_of_live_data + amount_of_garbage
Run Code Online (Sandbox Code Playgroud)

所以

efficiency ~= W1 * (amount_of_live_data / (heap_size - amount_of_live_data) )
              + W2.
Run Code Online (Sandbox Code Playgroud)

换一种说法:

  • 当你增加堆大小时,效率趋于恒定(W2),但是
  • 你需要一个很大比例的heap_size和amount_of_live_data才能实现这一点.

另一点是,对于高效的复制收集器,W2仅涵盖了"从空间"中清空垃圾对象所占空间的成本.其余的(跟踪,将活动对象复制到'空间',以及将它们占用的'从空间'归零)是初始方程的第一项的一部分;即由W1覆盖.这意味着W2很可能比W1小得多......并且最终方程的第一项有效期更长.

现在显然这是一个理论分析,成本模型简化了真正的垃圾收集器的真正工作方式.(并没有考虑到应用程序正在进行的"真正"工作,或者是系统级别的关闭太多内存的影响.)然而,数学告诉我,从GC效率的角度来看,这是一个很大的问题.堆确实帮了很多忙.

  • 垃圾收集器不需要那么多额外的内存来高效运行.关于第二句话,你不会以任何方式与海报相矛盾.他基本上都在问为什么即使实际内存需求下降,内存使用量也不会从峰值下降. (3认同)