【问题标题】:Does G1GC release back memory to the OS even if Xms = Xmx?即使 Xms = Xmx,G1GC 是否会向操作系统释放内存?
【发布时间】:2020-04-09 06:41:24
【问题描述】:

在阅读了thisJEP-346 之类的一些答案后,我意识到 G1 确实会将内存释放回操作系统。

但是,它是否会将内存释放回操作系统,甚至到当前内存使用量可能低于初始堆内存(即在此 JEP 之前,在我的情况下为 JDK11)?

假设我在8GB RAM 上有一个运行XmsXmx 设置为5GB 的Java 11 VM,但是我只消耗1GB 左右。 G1 会释放足够的内存给操作系统吗?

我在任何地方都没有找到任何说明 G1 仅限于发布的文档,请牢记Xms 阈值。

我在生产中观察到这一点,MemAvailable 一直下降到一个点,然后在 GC 之后,它在 8GB 盒子上跃升至接近 30-35%。所以我假设它正在释放内存,这就是 MemAvailable 跳回的原因。

另外,释放内存给操作系统到底是什么意思,是调用 free/unmap 吗?

【问题讨论】:

    标签: java garbage-collection java-11 g1gc


    【解决方案1】:

    注意:我已经删除了我之前的答案并研究了来源(也建立了我自己的JVM 只是为了找出这个特定的时刻),这是答案。

    简短回答

    JVM 11 version(目前)在使堆变小时不会低于Xms

    长答案

    绝对真理在源代码中。 here 是决定是否缩小堆。下面几行,可以看到如果我们输入那个if,就会有一个log语句:

    尝试堆收缩(Full GC 后容量高于最大所需容量)。

    所以本质上,如果我们能理解两个参数:capacity_after_gcmaximum_desired_capacity - 我们就能解开这个谜团。总的来说,capacity_after_gc 不是很容易掌握的;主要是因为这取决于有多少垃圾以及当前 GC 可以回收多少。为简单起见,我将编写一些不会产生任何垃圾的代码,以使该值保持不变。

    在这种情况下,我们只需要了解maximum_desired_capacity

    A few lines above,您可以看到这是计算为:

    maximum_desired_capacity =  MAX2(maximum_desired_capacity, min_heap_size);
    

    不幸的是,这就是它变得棘手的地方,因为要真正了解这些人体工程学是如何设置的,需要遵循和理解大量代码;特别是因为它们依赖于 JVM 开始使用的各种参数。

    例如min_heap_sizeis set as:

    // If the minimum heap size has not been set (via -Xms),
    // synchronize with InitialHeapSize to avoid errors with the default value.
    

    注意,他们甚至将-Xms 称为最小值;虽然文档说它是 initial。您还可以注意到它进一步取决于另外两个属性

     reasonable_minimum , InitialHeapSize
    

    这将很难进一步解释;这就是为什么我不会。相反,我将向您展示一些简单的证明(我确实完成了大部分代码......)


    假设你有这个非常简单的代码:

    public class HeapShrinkExpand {
    
        public static void main(String[] args) throws InterruptedException {
    
            for (int i = 0; i < 10; i++) {
                Thread.sleep(500);
                System.gc();
            }
        }
    }
    

    我运行它:

    -Xmx22g 
    -XX:InitialHeapSize=1g
    "-Xlog:heap*=debug" 
    "-Xlog:gc*=debug" 
    "-Xlog:ergo*=debug" 
    

    在日志中,我会看到:

    [0.718s][debug][gc,ergo,heap   ] GC(0) Attempt heap shrinking (capacity higher than max desired capacity after Full GC). Capacity: 1073741824B occupancy: 8388608B live: 1018816B maximum_desired_capacity: 27962026B (70 %)
    [0.719s][debug][gc,ergo,heap   ] GC(0) Shrink the heap. requested shrinking amount: 1045779798B aligned shrinking amount: 1044381696B attempted shrinking amount: 1044381696B
    

    这会告诉你一些关于需要缩小多少、当前容量是多少等的统计信息。下一行将显示堆实际上下降了多少:

    [0.736s][debug][gc,ihop] GC(0) Target occupancy update: old: 1073741824B, new: 29360128B
    

    堆确实缩小了,降至29MB 左右。

    如果我添加一个 JVM 启动标志:-Xms10g,那些负责显示堆缩小到多少的 GC 日志;将不再存在。

    事实上,如果我运行自己的JMV(启用一些日志记录),这两个值:capacity_after_gcmaximum_desired_capacity始终具有相同的值;这意味着永远不会输入if statement,并且堆永远不会低于-Xms


    我已经使用 JDK-13 运行了相同的代码,虽然那里存在缩小的日志(当-Xms 作为参数给出时),底层堆仍然保持在-Xms。我发现更有趣的是,在java-13 下,尝试运行:

     -Xmx22g -Xms5g -XX:InitialHeapSize=1g
    

    将正确错误输出:

    指定的最小和初始堆大小不兼容

    【讨论】:

    • JEP 对此很明确,但我的问题是这也是 JDK11 的现状吗?编辑了我的问题以使其更加明确。
    • @AdwaitKumar AFAIK yes,但是这到底什么时候会发生 - 你需要研究 G1 源代码。人们say that it does,我相信他们。
    • 我对上述答案发表了评论,但没有回复。
    • @AdwaitKumar 我也评论过,好像对方也不是很懂;这让我现在更加正确。
    • 感谢您的调查。我可以通过一个小程序自己验证吗?我写了一个来创建内存压力并分析 GC 日志,但找不到堆大小调整的迹象。也许我看的不是正确的事情。
    猜你喜欢
    • 2015-08-08
    • 2019-11-02
    • 1970-01-01
    • 1970-01-01
    • 2011-01-26
    相关资源
    最近更新 更多