序:
Flink的内存管理机制也是Flink的一大亮点。Flink在JVM内部实现了自己的内存管理。
一、MemorySegment
MemorySegment是Flink内存管理的核心,是Flink的内存抽象。默认情况下,一个MemorySegment可以被看做是一个32KB大小的内存块抽象。这个内存即可以是JVM里的一个byte[],也可以是堆外内存,这个可以从MemorySegment的构造方法中看出来。
Flink为MemorySegment提供了两个实现类,分别是org.apache.flink.core.memory.HeapMemorySegment和org.apache.flink.core.memory.HybridMemorySegment。HeapMemorySegment用于管理heap memory,而HybridMemorySegment可以作用于on-heap,off-heap direct或者off-heap unsafe。这样就使得HeapMemorySegment的作用被HybridMemorySegment替代了。事实上,确实如此,HeapMemorySegment已经被打上了unsed标识,说明Flink已经不再用它了。
二、NetworkBufferPool和LocalBufferPool
NetworkBufferPool是在TaskManager启动的时候创建的,具体源码涉及到 :TaskManagerRunner的runTaskManager方法 创建TaskManagerRunner实例,然后再运行startTaskManager,在startTaskManager中会调用TaskManagerServices.fromConfiguration方法,去创建NetworkBufferPool。所以NetworkBufferPool在每个TaskManager上只有一个,负责所有子Task的内存管理。其 实例化时,就会获取所有可由它管理的内存。
NetworkBufferPool是一个总的管理TaskManager内存的类。而TM上,每个task的内存隔离是通过LocalBufferPool来实现的,每个Task都有一个和其他Task隔离的LocalBufferPool。NetworkBufferPool负责分配MemorySegments给各个LocalBufferPool,LocalBufferPool在初始化的时候就需要有numberOfRequiredMemorySegments 个MemorySegments,这个是由ResultPartition的subpartition决定。LocalBufferPool就是来管理各自task上的MemorySegments的。可以看下这个类中所拥有的一些属性
/** Global network buffer pool to get buffers from. */
private final NetworkBufferPool networkBufferPool;
/** 该内存池需要的最少内存片数目*/
/** The minimum number of required segments for this pool. */
private final int numberOfRequiredMemorySegments;
/**
*
* 当前已经获得的内存片中,还没有写入数据的空白内存片
* The currently available memory segments. These are segments, which have been requested from
* the network buffer pool and are currently not handed out as Buffer instances.
*
*/
private final ArrayDeque<MemorySegment> availableMemorySegments = new ArrayDeque<MemorySegment>();
/**
*
* 注册的所有监控buffer可用性的监听器
* Buffer availability listeners, which need to be notified when a Buffer becomes available.
* Listeners can only be registered at a time/state where no Buffer instance was available.
*/
private final ArrayDeque<BufferListener> registeredListeners = new ArrayDeque<>();
/** 能给内存池分配的最大分片数*/
/** Maximum number of network buffers to allocate. */
private final int maxNumberOfMemorySegments;
/** 当前内存池大小 */
/** The current size of this pool. */
private int currentPoolSize;
/**
* 所有经由NetworkBufferPool分配的,被本内存池引用到的(非直接获得的)分片数
* Number of all memory segments, which have been requested from the network buffer pool and are
* somehow referenced through this pool (e.g. wrapped in Buffer instances or as available segments).
*/
private int numberOfRequestedMemorySegments;
介绍完这个三个概念,就可以对Flink的内存管理有个初步的意识。NetworkBufferPool 负责管理和分配MemorySegment给每个Task的LocalBufferPool,而数据则是序列化成byte[]或者堆外内存,存放在这个MemorySegment中。而MemorySegment则是在TaskManager启动的时候就已经初始化好了,不需要再次去申请。当不需要这块数据的时候,可以直接调用MemorySegment的free或者release方法。release方法是对free的一层封装,其实内部使用的还是free,去释放内存。
这样就可以实现自主的内存管理了。对于堆内存来说,直接获取所有内存并放入老年代,并令用户对象只在新生代存活,可以极大的减少Full GC。