【问题标题】:Is there a way to lower Java heap when not in use?有没有办法在不使用时降低 Java 堆?
【发布时间】:2015-09-16 03:08:14
【问题描述】:

我目前正在开发一个 Java 应用程序并努力优化它的内存使用。据我所知,我正在遵循正确垃圾收集的准则。但是,我的堆似乎处于最大大小,尽管它不是必需的。

我的程序每小时运行一次资源密集型任务,此时计算机未有人使用。该任务使用了相当大的内存块,但在任务完成后立即将其全部释放。 NetBeans 分析器显示内存使用情况如下所示:

我真的很想在不使用时将所有堆空间还给操作系统。我没有理由在程序至少再过一个小时内什么都不做的情况下占用这一切。

这可能吗?谢谢。

【问题讨论】:

  • 我认为这取决于您使用的 JVM。 JVM 规范对此只字未提,IIRC。
  • @Fakrudeen,你说的不正确。如果不按进程解除提交,操作系统可能会回收物理内存,但它需要先将内容写入磁盘,这会大大降低性能。对于许多服务器来说,它没有交换来确保它不会抖动。在这种情况下,操作系统永远无法回收内存,无论它多久未被触及。您的评论会误导之前没有上过操作系统课程的任何人。这是有害的。
  • @Fakrudeen,将未使用的内存交换到磁盘始终是个坏主意。写入磁盘时会产生 IO。更重要的是,如果 JVM 再次需要更多内存,它需要从磁盘读取垃圾内容。如果 JVM 可以缩小并避免被交换,那么如果它需要更多内存,它可以要求操作系统为它们分配空闲页面,这比从磁盘读取垃圾内容便宜得多。我很难过你们对交换的东西感觉很好。如果一个新手读到这个并且有一个终生的印象认为这不是什么大不了的事情被交换了怎么办。
  • @Fakrudeen,看看 OP 的图表,JVM 在他的资源密集型工作完成后进行 GC,JVM 肯定知道它使用了多少内存。我从不说 GC 不贵。我们说的问题不是 GC 是否昂贵,而是在 GC 之后,JVM 知道它有一堆空闲堆,但从不将其返回给 OS。伙计,我知道人们在受到批评时会本能地进行防御。但请考虑一下。另外,我想让新手知道交换是非常糟糕的事情。如果某个进程的磁盘 IO 很重,几乎所有其他进程都会挂起,例如你甚至不能通过 SSH 连接到机器。
  • 最后,请注意,所有这些仅适用于 Java 的堆(即 GC 管理的内存)。从 GC 的角度来看,依靠交换来获取堆外内存(例如,ByteBuffer.allocateDirect())绝对没有问题,并且可能完全可以接受(显然,取决于用例)。例如,所有主要的基于 Java 的数据库、缓存和搜索引擎都会这样做,尽管这些通常建议调整内存设置以避免匿名页面被换出。

标签: java memory-management heap-memory


【解决方案1】:

JVM 不是这样工作的。你不能把它还给操作系统。

正如四年前编写的许多人所指出的那样,如果您为 JVM 提供正确的 GC 设置,您可以将内存还给操作系统。

【讨论】:

  • 将(Java)堆降低到较低级别不会将内存归还给操作系统。不过效果是一样的,因为未使用的页面会被换出,不会产生重大影响。
  • @jocull:JVM 可以垃圾收集和压缩它的堆;会有很多未使用的页面。 JVM 不能将其作为释放的内存返回给操作系统,但它不会使用这些页面。用完 RAM,操作系统会将这些页面换出,允许活动进程使用这些页面过去占用的物理 RAM。这并不完全是“释放内存”,并且涉及到一些磁盘 I/O,但效果是相似的:你给活动进程一些 JVM 之前占用的 RAM。
  • 我似乎 JVM 将内存归还给 Windows XP 下的操作系统。所以这是可能的。
  • -Xincgc 确实将内存归还给系统!
  • 至少从 Java 1.5 开始,内存可以返回给操作系统。请参阅 stopcoding.wordpress.com/2010/04/12/… 以获取令人印象深刻的示例。
【解决方案2】:

您也许可以使用-XX:MaxHeapFreeRatio - 这是在 GC 收缩之前可用的堆的最大百分比(默认为 70)。也许将其设置得更低(40 或 50?)然后使用 System.gc() 可能会花费一些时间来获得所需的行为?

没有办法强迫这种情况发生,但是您可以尝试并鼓励 JVM 这样做,但您不能随心所欲地拉出内存。虽然上述可能会缩小堆,但不一定会将内存直接交还给操作系统(尽管在最近的 JVM 实现中确实如此。)

【讨论】:

  • 这很有趣,但它并没有解决问题本身,即“我真的很想在不使用时将所有堆空间还给操作系统......这可能吗? ?"... GC 方向只是告诉 JVM 如何管理(单调增长的)Java 堆。
  • @andersoj:此设置确实将 RAM 还给操作系统。请参阅 stopcoding.wordpress.com/2010/04/12/… 以获取令人印象深刻的示例。
  • @A.H.:太酷了,太酷了。不知道,谢谢指点。 (希望我足够聪明,可以改进上面的答案以涵盖这一点……如果您在此处发布答案,我会投票。)
  • @andersoj 请支持这个答案,因为 berry120 是引入该特定参数的人。
  • 另一篇很有意思的文章,显示SerialGCParNewGC回馈内存:stefankrause.net/wp/?p=14
【解决方案3】:

一种可能性是让您的后台 java 应用程序每小时启动一个外部 jvm 实例来运行您的任务。这样,只有您原来的 jvm 应用程序在任务之间运行。

【讨论】:

    【解决方案4】:

    如果您的应用在不活动期间处于静止状态,则操作系统可能会为您换出这些页面,从而减轻它们对物理内存的压力。

    http://www.linuxvox.com/2009/10/what-is-the-linux-kernel-parameter-vm-swappiness/

    【讨论】:

      【解决方案5】:

      Java 最好的秘密:-Xincgc 它确实会影响性能,但并不总是那么大。有时确实如此,这取决于您在做什么。 增量垃圾收集器很好地将内存交还给系统!

      【讨论】:

      • 谢谢!如果我有机会回到这个项目,我会研究一下。
      • 注意:使用 OpenJDK 8,这会打印出 OpenJDK 64-Bit Server VM warning: Using incremental CMS is deprecated and will likely be removed in a future release
      【解决方案6】:

      短版:可以。

      长版:

      Java/JVM 如何管理内存

      对于大多数应用程序,JVM 默认值是可以的。看起来 JVM 期望应用程序只运行一段有限的时间。因此,它似乎不会自行释放内存。

      为了帮助 JVM 决定如何以及何时执行垃圾回收,应提供以下参数:

      • -Xms 指定最小堆大小
      • –Xmx 指定最大堆大小

      对于服务器应用程序添加:-server

      这对我来说还不够。我想要更多的控制权!

      如果上面提到的参数还不够,你可以影响JVM关于垃圾收集的行为。

      首先,您可以使用System.gc() 告诉虚拟机您何时认为垃圾收集有意义。其次,您可以指定 JVM 应该使用哪些垃圾收集器:

      不同种类的垃圾收集器:

      • 串行 GC

        命令行参数:-XX:+UseSerialGC

        停止您的应用程序并执行 GC。

      • 并行 GC

        命令行参数:-XX:+UseParallelGC -XX:ParallelGCThreads=value

        与您的应用程序并行运行次要集合。减少主要收集所需的时间,但使用另一个线程。

      • 并行压缩 GC

        命令行参数:-XX:+UseParallelOldGC

        与您的应用程序并行运行主要集合。使用更多 CPU 资源,减少内存使用。

      • CMS GC

        命令行参数:-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=value -XX:+UseCMSInitiatingOccupancyOnly

        执行较小的收集,并且比串行 GC 更频繁,从而限制应用程序的中断/停止。

      • G1

        命令行参数:-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC

        实验性(至少在 Java 1.6 中):尝试确保应用程序永远不会停止超过 1 秒。

        示例

      未经任何优化的 Play Framework Web 应用程序的内存使用情况: 如您所见,它使用了相当多的堆空间,并且使用的空间会定期释放。

      在这种情况下,仅使用参数进行的优化无效。有一些计划任务使用了相当多的内存。在这种情况下,通过在内存密集型操作后使用CMS GCSystem.gc() 可以实现最佳性能。结果,WebApp 的内存使用量从 1.8 GB 减少到大约 400-500 MB。

      您可以在这里看到另一个来自 VisualVM 的屏幕截图,它显示了 JVM 如何释放内存并实际返回给操作系统:

      注意:我使用 VisualVM 的“Perform GC”按钮来执行 GC,而不是在我的代码中使用 System.gc(),因为消耗内存的计划任务仅在特定时间启动,并且使用 VisualVM 更难捕获。

      进一步阅读

      【讨论】:

        【解决方案7】:

        Java 12 使用 G1GC 支持此功能。

        JEP 346:立即从 G1 返回未使用的已提交内存。

        增强 G1 垃圾收集器,在空闲时自动将 Java 堆内存返回给操作系统。

        https://openjdk.java.net/jeps/346

        Java 13 使用 zgc 支持此功能

        JEP 351:ZGC:取消提交未使用的内存

        ZGC 目前不会取消提交并将内存返回给操作系统,即使该内存已长时间未使用。对于所有类型的应用程序和环境,这种行为并不是最佳的,尤其是那些关注内存占用的应用程序和环境。例如: 资源按使用付费的容器环境。

        • 应用程序可能长时间处于空闲状态并与许多其他应用程序共享或竞争资源的环境。

        • 应用程序在执行期间可能有非常不同的堆空间需求。例如,启动期间所需的堆可能大于稍后在稳态执行期间所需的堆。

        http://openjdk.java.net/jeps/351

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-26
          • 1970-01-01
          • 1970-01-01
          • 2021-11-15
          相关资源
          最近更新 更多