注意:我已经删除了我之前的答案并研究了来源(也建立了我自己的JVM 只是为了找出这个特定的时刻),这是答案。
简短回答
JVM 11 version(目前)在使堆变小时不会低于Xms。
长答案
绝对真理在源代码中。 here 是决定是否缩小堆。下面几行,可以看到如果我们输入那个if,就会有一个log语句:
尝试堆收缩(Full GC 后容量高于最大所需容量)。
所以本质上,如果我们能理解两个参数:capacity_after_gc 和maximum_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_gc 和maximum_desired_capacity 将始终具有相同的值;这意味着永远不会输入if statement,并且堆永远不会低于-Xms。
我已经使用 JDK-13 运行了相同的代码,虽然那里存在缩小的日志(当-Xms 作为参数给出时),底层堆仍然保持在-Xms。我发现更有趣的是,在java-13 下,尝试运行:
-Xmx22g -Xms5g -XX:InitialHeapSize=1g
将正确错误输出:
指定的最小和初始堆大小不兼容