本文主要简单介绍TaskManager的内存管理策略,接上文。以下均为笔者个人观点,欢迎大家批评指正。
二、 内存分配
flink在启动一个TM的时候,只会通过两个启动参数限制的JVM的内存使用
- JVM使用的最大堆内存(-Xms -Xmx)
- JVM使用的最大堆外内存(-XX:MaxDirectMemorySize)
这两项内存的和即为上述TaskManager使用-tm指定的内存,比如TM的内存为2048M, 计算出来的堆内存(-Xmx)使用为1024M,则堆外内存(-XX:MaxDirectMemorySize)的分配为1024M
那么这个参数(-Xmx)是怎么计算出来的?
2.1 堆内存(-Xmx)计算
在介绍JVM堆内存之前,先简单介绍一下相关配置,以下是笔者从官网找到的相关配置
配置项 | 含义 |
containerized.heap-cutoff-ratio | 从container中保留一部分内存作为JVM的其他内存使用 |
网络缓存池内存比例 | |
env.java.opts | jVM 启动参数 |
下面我们来说明如何计算堆内存(设定TM指定的内存为M)
- 从TM指定的内存中减去保留的内存M1 = M * (1 - containerized.heap-cutoff-ratio), 此时剩余内存为M1
- 从剩余内存中减去网络内存池中使用的内存M2 = M1 * (1 -taskmanager.network.memory.fraction), 此时剩余内存为M2
- 如果TM管理的内存为堆外内存(taskmanager.memory.off-heap = true),则需要从剩余内存M2中减去这部分 M3 = M2 * (1-taskmanager.memory.fraction),否则 M3 = M2
- M3即为我们得到的堆内存
这样,我们已经得到了启动参数使用的最大堆内存,需要说明的是,在启动TM时,我们设定了-Xms -Xmx两个参数都为此时的最大堆内存。也就是说,我们的JVM至少会占用-Xmx设定的内存使用量。
另一方面,如果在配置文件中使用env.java.opts参数,并不会影响现有的堆内存计算方式,只是将该参数对应的配置项追加到JVM的启动参数中,显然env.java.opts如果设置了-Xms -Xmx参数,会覆盖掉我们之前计算出来的值。
至此,我们得到了JVM的使用的堆内存(-Xmx),接下来,我们将计算网络传输内存池和Flink自己管理的内存分配。
2.2 网络传输内存池的计算
网络内存池和Flink自己管理的内存分配是由当前运行下JVM的最大可用堆内存为基础反推得到的,需要注意的是
前面提到了我们设定了-Xmx参数来设定最大堆内存,但实际计算中并非直接使用的该值而是Runtime中拿到的maxMemory
final long maxMemory = Runtime.getRuntime().maxMemory();
这个内存与前述设定的-Xmx值并不相等,原因在于JVM的内存布局中两个Survivor Space不能同时使用,因此这里的内存是-Xmx指定的内存减去一个Survivor Space占用的内存,例如,我们前述使用-Xmx1024m设置限制了最大堆内存的使用为1024M,假如堆内存三种内存池的分配比例为Java提供的默认值,我们可以计算出这三种内存池使用的内存分别为
内存池 | 数量 | 内存使用量 |
Old Gen | 1 | 682.66 |
Eden Gen | 1 | 273.06 |
Survivor Space | 2 | 34.13 * 2 |
因此得到的计算基数为 1024 -34.13 = 989.87。
接下来计算网络内存池的内存数量,需要分两种情况,
当Flink自己管理的内存来自堆外内存时,其计算基数需要在maxMemory的基础上加上Flink自己管理的内存,即使用jvmHeapNoNet作为计算基数
base = maxMemory / (1.0 - taskmanager.memory.fraction);
当Flink自己管理的内存从Heap中划分时,其计算基数为前面计算的maxMemory
base = maxMemory
因此我们可以得到网络内存池使用的内存数量
networkBufBytes = (base / (1.0 - taskmanager.network.memory.fraction) * taskmanager.network.memory.fraction)
网络传输内存池的大小不允许超过堆内存,因此taskmanager.network.memory.fraction这个变量的值必须小于0.5。
2.3 Flink自己管理的内存数量
下面我们来计算Flink自己管理的内存数量
根据前述,Flink自己管理的内存可以根据taskmanager.memory.off-heap分为堆外内存和堆内存
针对堆内内存,其计算基数进行了调整
Runtime r = Runtime.getRuntime();
long base = r.maxMemory() - (r.totalMemory() - r.freeMemory())
在计算之前,会显式的调用一次GC.由公式可以看出,计算Flink管理内存时,其基数是当前的可用内存。
此时,我们可以可以得到Flink自己管理的数量
long memorySize = base * taskmanager.memory.fraction
针对堆外内存
如果使用堆外内存,其计算的基数为我们当前之前计算得到的maxMemory,如下:
long directMemorySize = (long) (maxJvmHeapMemory / (1.0 - taskmanager.memory.fraction) * taskmanager.memory.fraction);
还有一个配置项taskmanager.memory.preallocate需要注意,当这个配置项设置为true时,将会立即申请Flink管理的内存数量。
在分配完网络内存池、flink自己管理的内存以及堆内存后,其他的内存作为空闲内存预留在池子里,比如JVM的非堆内存的使用等。
这样整个内存就划分完毕。如有不正支持,欢迎批评指正。