1.容器化时代的jvm不应使用Xms/Xmx显示设置

​ JDK 10与JDK1.8 的 8u191之后版本支持UseContainerSupport识别容器分配的内存,再结合MaxRAMPercentage提供一个堆内存上限,一般给75/80。

​ 原因:Xmx 与容器平台的内存限制不匹配是关键问题。因为 JVM 进程内存不严格地等于 Xmx + 线程数 * Xss + 本地分配等等。如果 Xmx 已经设定,并且与限制相同,实际上堆内存还远未达到 Xmx 就可能达到内存限制,导致 Kubernetes 以 137 的退出码退出。这个情况必须进行动态调整。总的来说,实际堆内存还没达到Xmx上限,对外内存使用过多的话会引起OOM Killer。

2.吞吐量和停顿时间

​ 抛开OOM来说,JVM调优绕不开的一个问题必须是吞吐量和停顿时间之间的取舍,以几个常见垃圾收集器(老年代)为例

Parrallel

​ 老年代收集使用标记-整理(也叫标记-清除-整理)算法,暂停所有用户线程STW,所以停顿时间相对较长。

CMS

​ 初始标记-并发标记-重新标记-并发清除

  • 初始标记,会发生STW,仅标记GC Root能直接关联到的对象(存活对象)。
  • 并发标记,与用户线程并行,遍历整个图进行标记对象(存活对象)。
  • 重新标记,会发生STW,。
  • 并发清除,与用户线程并行,清理刚才未被标记的垃圾。

​ 总结:大部分时间都是在与用户线程并行,所以停顿时间极低,极端情况下由于内存碎片过多,Full GC后仍然无法分配连续内存,此时垃圾回收算法会由标记-清除变为标记-清除-整理

​ 但吞吐量受到了限制,由于发生GC的过程大部分都在于用户进程并行,CPU给用户线程分配是时间片的同时也需要给GC线程分配片,从而导致了吞吐量受到了影响。(什么是吞吐量?)

​ 同时,由于标记-清除期间用户线程仍是活动的,此时仍然会产生对象占用,

小记:为什么不会担心并发标记过程中,某些对象又突然从垃圾对象变为存活对象?见尾部

G1

​ G1垃圾收集器打破了常规收集器的物理分代,将内存区域划分成了大小相等的region,但年轻代/老年代仍逻辑存在。G1旨在可指定最大停顿时间的同时最大限度保证吞吐量,初始标记-并发标记-重新标记逻辑与CMS回收器类似,在真正清除垃圾的阶段,G1根据用户设置的停顿时间产生STW,并对可回收垃圾的逻辑判断哪个region存在的垃圾多则优先回收,将存活对象复制到空余区域,后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。

​ 总结:G1在尽可能提高吞吐量的情况下来控制停顿时间,看似是可以接受的,但存在的问题是回收阶段无法与用户线程并行,且由于G1的思想是划分region,所以导致了G1的记忆集(和 其他内存消耗)可能会占整个堆容量的20%乃至更多的内存空间。

吞吐量是什么:运行用户代码的时间/(运行用户代码的时间+垃圾回收的时间),可以简单理解为(QPS/TPS),由于并发标记/清除阶段会分走用户线程的CPU时间片,变相导致运行用户代码时间减少,从而吞吐量下降。

停顿时间是什么:当发生STW的时候,程序完全停止,导致程序相应用户的时间变长。

理解:在并发量高的系统上,我们应该更多的关注吞吐量,所以是否影响吞吐量的垃圾回收器不是那么的优秀?实际上并不是,当下系统较多数为IO密集型系统,即CPU其实有大量空闲,程序与垃圾收集器同处一个机器环境下,垃圾回收所占用的CPU时间片不会对系统造成太大的影响,程序本身也就不会遇到CPU瓶颈。