注意:我已删除之前的答案并研究了来源(还构建了自己的
JVM
,以找出这个特定时刻),以下是答案。
简短回答:
目前的
JVM 11版本
在缩小堆时,不会低于
Xms
。
长回答:
真相在源代码中。
这里是决定是否收缩堆的地方。在下面几行,您可以看到如果我们进入了那个
if
,就会有一个日志声明:
尝试缩小堆(Full GC后容量高于最大期望容量)。
因此,本质上,如果我们能理解两个参数:
capacity_after_gc
和
maximum_desired_capacity
- 我们就可以解决这个谜团。通常,
capacity_after_gc
不是很容易理解;主要是因为它取决于有多少垃圾以及当前GC可以回收多少。为简单起见,我将编写一些不生成任何垃圾的代码,以使该值保持不变。
在这种情况下,我们只需要理解
maximum_desired_capacity
。
在几行之前, 你可以看到这是计算为:
maximum_desired_capacity = MAX2(maximum_desired_capacity, min_heap_size);
不幸的是,这就是问题所在,因为需要跟踪和理解大量代码才能真正看到这些人体工程学是如何设置的;特别是因为它们依赖于启动了各种参数的JVM
。
例如,min_heap_size
被设置为:
// If the minimum heap size has not been set (via -Xms),
// synchronize with InitialHeapSize to avoid errors with the default value.
注意,他们甚至将
-Xms
称为
最小值;尽管文档说它是
初始值。您还可以注意到,它
进一步取决于
另外两个属性:
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语句
永远不会被执行,堆永远不会低于
-Xms
。
我已经使用JDK-13运行了相同的代码,尽管在给出-Xms
参数时存在缩小日志,但基础堆仍停留在-Xms
。更有趣的是,在java-13
下尝试运行:
-Xmx22g -Xms5g -XX:InitialHeapSize=1g
将会正确报错,错误信息为:
指定的最小堆和初始堆大小不兼容
G1
源代码。人们说它会,我相信他们。 - Eugene