Kubernetes的cpu.requests是如何影响到jvm的新生代大小的

1,157 阅读3分钟

遇到的问题

部署在k8s的一个java pod,堆大小通过-xmx2500m -xms2500固定为2500m,但部署后通过jmap -heap发现新生代只有83m。

新生代83m是怎么来的

查看jinfo发现启动参数里设置了-XX:MaxNewSize=87228416 但通过jinfo的Command line和仓库全文搜索MaxNewSize,我们并没有人为设置MaxNewSize。 通过CMS默认新生代是多大这篇文章知道了MaxNewSize是jvm根据cpu核数算出来一个值,然后和1/3 * maxHeap取相对最小值求出来的。但这也没有完全解决我的问题,我们的机器是16核的,min((16 * 64 * 13 / 20), 1/3 * 2500)应该是833m才对。那么现在的问题可能就在于jvm拿到的cpu核数是错的。于是写了个小测试拿到pod里跑了一下。

public class Test {
    public static void main(String[] args) {
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

测试结果是1,这就和之前的数据对上了,1 * 64 * 13 / 10 = 83m

为什么availableProcessors=1

Runtime#availableProcessors是native方法,于是去看源码。不知道jdk源码怎么看的可以参考这两篇文章。1. openJDK源码各版本下载教程 2. 阅读openjdk源码. 需要把jdkhotspot的代码都下下来。 这里我直接给出我找的源码文件路径作为参考。

Runtime.c#Java_java_lang_Runtime_availableProcessors ->
jvm.cpp#JVM_ENTRY_NO_ENV(jint, JVM_ActiveProcessorCount(void)) ->
os_linux.cpp#os::active_processor_count ->
osContainer_linux.cpp#OSContainer::active_processor_count

OSContainer::active_processor_count的截图 代码很简单,if else一看就懂,其中的关键是cpu_countcpu_quotacpu_periodcpu_shares几个值是什么意思和值从哪来。cpu_count就是机器有几核,可用cat /proc/cpuinfo | grep processor | wc -l查看。剩下三个都是linux cgroup的概念,docker也使用这几个参数来控制资源占用。cpu_quotacpu_period配合使用,cpu_period默认值100000,如果设置cpu_quota=150000表示容器最多可使用1.5个cpu核的时间。cpu_shares表示cpu使用优先级,默认值1024,设置了这个参数的容器在宿主机cpu资源不紧张时的cpu使用不受限制。如果有两个容器在进行复杂的数学计算,都想占用全部的cpu。那么把cpu_shares都设为2,他们就只能各占50%的cpu时间。是的,是按比例来的,设成1024:1024和设成2:2效果一样。这几个值可以通过/sys/fs/cgroup/cpu目录下的cpu.cfs_period_uscpu.cfs_quota_uscpu.shares文件查看,我们环境下的值分别为100000-12-1表示值不生效。根据上面的源码,active_processor_countceilf((float)share / (float)PER_CPU_SHARES);决定,PER_CPU_SHARES=1024,所以active_processor_count = ceil(2 / 1024) = 1

为什么cpu.shares是2

直接上k8s文档,How Pods with resource limits are run 我们没有设置cpu request,所以使用默认值2。

怎么设置新生代合适的值

这部分的解决仅供参考,我也还在观察效果,如果大家有更好的方案,欢迎留言讨论。 我把spec.containers[].resources.requests.cpu设置为1。在k8s里设置requests.cpu=1k8s会将cpu * 1024的值设置到docker的--cpu-shares。在OSContainer::active_processor_count方法里,sharecpu_shares方法得到。 cpu_shares是1024时,会返回-1,对jvm来说,相当于没设置cpu_shares,就会返回真实的cpu核数。我们这就是16。根据前面提到的CMS确定新生代大小的函数MIN2(max_heap/(NewRatio+1), ScaleForWordSize(young_gen_per_worker * parallel_gc_threads)),填入我们的数据大概就是MIN(1/3 * 2500, 16 * 64 * 13 / 10) = 833availableProcessors主要影响线程池的大小和垃圾收集器的并发线程数,通过监控得知,我们k8s node的cpu使用率是不高的,所以让node上的所有pod都能使用全部的cpu问题不大,而且就算有竞争,大家的cpu_shares都是1024,可以公平分配资源。

参考资料

  1. Docker resource_constraints
  2. Docker Container CPU Limits Explained
  3. java is not detecting all processors in our cloud foundry environment