在Java中突发内存使用

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

我写了一个小样本程序试图certificate这一点。 它基本上有4个按钮……

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

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

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

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

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

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

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

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

这取决于你的意思是“永远消失”。

我也听说它说有些JVM在准备就绪时会给内存带回内存。 不幸的是,考虑到低级内存API通常工作的方式,JVM必须返回整个段,并且“撤出”一个段以便可以返回它往往很复杂。

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

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

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

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


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

 cost ~= (amount_of_live_data * W1) + (amount_of_garbage * W2) 

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

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

 efficiency = cost / amount_of_garbage_collected 

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

 efficiency ~= (amount_of_live_data * W1) / amount_of_garbage + W2. 

GC运行时,

 heap_size ~= amount_of_live_data + amount_of_garbage 

所以

 efficiency ~= W1 * (amount_of_live_data / (heap_size - amount_of_live_data) ) + W2. 

换一种说法:

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

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

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

一旦程序终止,Windows中的taskmanager中的内存使用量是否会下降? 我认为内存已经发布但未显示为您正在监视的操作系统中的默认任务监视器。 通过解除分配指针的向量来解决关于C ++ 问题的这个问题

一个常见的误解是Java在运行时会耗尽内存,因此它应该能够将内存返回给操作系统。 实际上,Oracle / Sun JVM一旦启动就将虚拟内存保留为连续的内存块。 如果没有足够的连续虚拟内存可用,即使程序不会使用那么多,它也会在启动时失败。

然后发生的事情是操作系统足够智能,在使用之前不会为程序分配物理内存。 它不能轻易地回收内存,但如果需要它可以交换到磁盘并且它已经使用了一段时间。 Java不能很好地处理堆的部分交换到磁盘,所以应该避免这种情况。

如果不需要atm,大多数JVM都不能或不能将先前获取的内存释放回主机操作系统。 这是因为这是一项昂贵且复杂的任务。 垃圾收集器仅适用于Java虚拟机中的堆内存 因此它不会向OS返回( C语言中的free() )内存。 例如,如果不再使用大对象,则GC将在JVM堆中将内存标记为空闲,而不是将其释放到OS。

Java仅将内存分配给对象。 没有明确的内存分配。 事实上,Java甚至将数组类型视为对象。 每次创建一个对象时都会进入堆中。

Java运行时使用垃圾收集器,一旦确定对象不再可访问,它就会回收对象占用的内存。 这是自动过程。

调用System.gc()可能不会在您调用时收集垃圾; 这就是为什么你的记忆没有减少。 通常,最好让系统决定何时需要收集堆,以及是否进行完整收集。

System.gc()甚至不强制进行垃圾收集; 它只是对JVM暗示 “现在可能是清理一下的好时机”

Java内存在这里解释了 link2

Sun / Oracle编写了一些很好的文档来描述Java的垃圾收集。 快速搜索“Java垃圾收集调整”结果如: http://www.oracle.com/technetwork/java/gc-tuning-5-138395.html和http://java.sun.com/docs/hotspot/gc1.4.2/

引入Oracle doc状态;

Java TM 2平台标准版(J2SE TM平台)用于各种应用程序,从桌面上的小applet到大型服务器上的Web服务。 在J2SE平台版本1.4.2中,有四个垃圾收集器可供选择,但用户没有明确的选择,总是选择串行垃圾收集器。 在5.0版中,收集器的选择基于启动应用程序的机器的类。

垃圾收集器的这种“更聪明的选择”通常更好,但并不总是最好的。 对于想要自己选择垃圾收集器的用户,本文档将提供有关该选择的信息。 这将首先包括垃圾收集和调整选项的一般function,以充分利用这些function。 这些示例是在串行,世界各地的收集器的上下文中给出的。 然后将讨论其他收集器的具体特征以及在选择其他收集器时应考虑的因素。

他们描述了可用的各种收集器以及它们应该被使用的情况。 我记得在JConsole旁边使用它来监视应用程序在使用各种不同选项启动时的执行情况。

这些文档将根据您使用的参数,更深入地了解集合的发生方式。

我在Windows上遇到了这个问题,并找到了解决方案,所以我发布它作为答案,以防它可以帮助其他人。

这里有很多答案表明Java的行为是好的和/或2.垃圾收集的不可避免的后果。 这些都是假的。

问题:

如果你像我一样想要编写Java来为工作站编写小型应用程序,甚至在服务器上运行多个较小的进程,那么Oracle的JVM内存分配行为几乎完全没用。 即使使用-client运行,每个JVM进程一旦分配就会保留内存,并且永远不会将其返回。 无法禁用此行为。 作为OP注意事项:每个jvm进程无限期地保留其未使用的内存,即使它永远不会再次使用它,即使其他jvm进程正在挨饿。 这种莫名其妙的行为使得Oracle对于除了单一的单一应用场景之外的所有场景都是无用的实现。

另外:这不是垃圾收集的结果。 见证.Net应用程序在Windows上运行,使用垃圾收集,并且根本不会遇到这个问题。

解决方案:

我发现的解决方案是使用IKVM.NET JVM,您可以将其用作Windows上java.exe的替代品。 它将Java字节码编译为.Net IL代码,并作为.Net进程运行。 它还包含将.jar文件转换为.Net .dll和.exe程序集的实用程序。 性能通常优于Oracle的JVM,在GC之后,内存会立即返回到操作系统。 (注意:这也适用于带Mono的Linux)

要明确的是,除了我的小应用程序之外,我仍然依赖Oracle的JVM以及调试我的小应用程序,但是一旦稳定,我使用ikvm来运行它们就像它们是本机Windows应用程序一样,这很有效,我已经惊讶。 它有许多有益的副作用。 编译后,进程之间共享的DLL只加载一次,应用程序在任务管理器中显示为.exe而不是全部显示为javaw.exe。

不幸的是,不是每个人都可以使用ikvm来解决这个问题,但我希望这可以帮助那些在我的情况下。