Java13新特性 -- ZGC:取消使用未使用的内存
在JDK 11中,Java引入了ZGC,这是一款可伸缩的低延迟垃圾收集器,但是当时只是实验性的。号称不管你开了多大的堆内存,它都能保证在 10 毫秒内释放 JVM ,不让它停顿在那。但是,当时的设计是它不能把内存归还给操作系统。对于比较关心内存占用的应用来说,肯定希望进程不要占用过多的内存空间了。
在Java 13中,JEP 351再次对ZGC做了增强,将没有使用的堆内存归还给操作系统。ZGC当前不能把内存归还给操作系统,即使是那些很久都没有使用的内存,也只进不出。这种行为并不是对任何应用和环境都是友好的,尤其是那些内存占用敏感的服务,例如:
- 按需付费使用的容器环境;
- 应用程序可能长时间闲置,并且和很多其他应用共享和竞争资源的环境;
- 应用程序在执行期间有非常不同的堆空间需求,例如,可能在启动的时候所需的堆比稳定运行的时候需要更多
的堆内存。
使用细节
ZGC的堆由若干个Region组成,每个Region被称之为ZPage。每个Zpage与数量可变的已提交内存相关联。当ZGC压缩堆的时候,ZPage就会释放,然后进入page cache,即ZPageCache。这些在page cache中的ZPage集合就表示没有使用部分的堆,这部分内存应该被归还给操作系统。回收内存可以简单的通过从page cache中逐出若干个选好的ZPage来实现,由于page cache是以LRU(Least recently used,最近最少使用)顺序保存ZPage的,并且按照尺寸(小,中,大)进行隔离,因此逐出ZPage机制和回收内存相对简单了很多,主要挑战是设计关于何时从pagecache中逐出ZPage的策略。
一个简单的策略就是设定一个超时或者延迟值,表示ZPage被驱逐前,能在page cache中驻留多长时间。这个超时时间会有一个合理的默认值,也可以通过JVM参数覆盖它。Shenandoah GC用了一个类型的策略,默认超时时间是5分钟,可以通过参数-XX:ShenandoahUncommitDelay = milliseconds覆盖默认值。像上面这样的策略可能会运作得相当好。但是,用户还可以设想更复杂的策略:不需要添加任何新的命令行选项。例如,基于GC频率或某些其他数据找到合适超时值的启发式算法。JDK13将使用哪种具体策略目前尚未确定。可能最初只提供一个简单的超时策略,使用-XX:ZUncommitDelay = seconds选项,以后的版本会添加更复杂、更智能的策略(如果可以的话)。
uncommit能力默认是开启的,但是无论指定何种策略,ZGC都不能把堆内存降到低于Xms。这就意味着,如果Xmx和Xms相等的话,这个能力就失效了。-XX:-ZUncommit这个参数也能让这个内存管理能力失效。