Flink使用MemorySegment来管理内存,同时也是flink中内存的抽象。MemorySegment的实现也分为HeapMemorySegment和HybirdMemorySegment。
其中,HeapMemorySegment实现很简单,数据通过其内部的byte[]数组来实现。
HybirdmemorySegment既可以使用堆内内存,也可以使用堆外内存。
如何来确认使用的是堆内内存还是堆外内存?使用堆内内存的存储在构造方法中直接传入一个byte数组,而使用堆外内存的情况下则是在其构造方法中传入一个java.nio的ByteBuffer来作为堆外内存的存储。
HybridMemorySegment(byte[] buffer, Object owner) {
super(buffer, owner);
this.offHeapBuffer = null;
}
HybridMemorySegment(ByteBuffer buffer, Object owner) {
super(checkBufferAndGetAddress(buffer), buffer.capacity(), owner);
this.offHeapBuffer = buffer;
}
来看其中的HybirdmemorySegment的put()方法。
@Override
public final void put(int offset, ByteBuffer source, int numBytes) {
// check the byte array offset and length
if ((offset | numBytes | (offset + numBytes)) < 0) {
throw new IndexOutOfBoundsException();
}
final int sourceOffset = source.position();
final int remaining = source.remaining();
if (remaining < numBytes) {
throw new BufferUnderflowException();
}
if (source.isDirect()) {
// copy to the target memory directly
final long sourcePointer = getAddress(source) + sourceOffset;
final long targetPointer = address + offset;
if (targetPointer <= addressLimit - numBytes) {
UNSAFE.copyMemory(null, sourcePointer, heapMemory, targetPointer, numBytes);
source.position(sourceOffset + numBytes);
}
else if (address > addressLimit) {
throw new IllegalStateException("segment has been freed");
}
else {
throw new IndexOutOfBoundsException();
}
}
else if (source.hasArray()) {
// move directly into the byte array
put(offset, source.array(), sourceOffset + source.arrayOffset(), numBytes);
// this must be after the get() call to ensue that the byte buffer is not
// modified in case the call fails
source.position(sourceOffset + numBytes);
}
else {
// neither heap buffer nor direct buffer
while (source.hasRemaining()) {
put(offset++, source.get());
}
}
}
这里这个三个参数的put()方法第代表将第二个参数的ByteBuffer作为数据源,将接下来第三个参数个数的byte数据放到以HybirdmemorySegment中第一个参数偏移量为起点的数据当中。
如果数据源的ByteBuffer占据的是堆外空间,那么将会直接通过getAddress()方法计算得到其指针,再根据其数据源buffer有效数据开始位置position得到数据源的源数据起始位置的指针,在根据HybirdmemorySegment中数据的第一个参数偏移量得到本身起始位置的指针,根据这些直接通过unsafe将堆外内存的数据拷贝到HybirdmemorySegment中的目标位置,然后相应的数据源buffer的position曾加相应的大小。
如果数据源是堆内内存,那么则是取出数据源的buffer的array数组,在以array数组为参数的put()方法中继续操作。
@Override
public final void put(int index, byte[] src, int offset, int length) {
// check the byte array offset and length
if ((offset | length | (offset + length) | (src.length - (offset + length))) < 0) {
throw new IndexOutOfBoundsException();
}
final long pos = address + index;
if (index >= 0 && pos <= addressLimit - length) {
final long arrayAddress = BYTE_ARRAY_BASE_OFFSET + offset;
UNSAFE.copyMemory(src, arrayAddress, heapMemory, pos, length);
}
else if (address > addressLimit) {
throw new IllegalStateException("segment has been freed");
}
else {
// index is in fact invalid
throw new IndexOutOfBoundsException();
}
}
这里底层还是通过unsafe,但由于是堆内内存,其偏移量是一个相对偏移 ,以数据源在堆内内存为基准,不需要再同之前的一样得到一个指针。
那么,HybirdmemorySegment中得到bytebuffer指针的getAddress()方法是怎么实现的。
private static final Field ADDRESS_FIELD;
static {
try {
ADDRESS_FIELD = java.nio.Buffer.class.getDeclaredField("address");
ADDRESS_FIELD.setAccessible(true);
}
catch (Throwable t) {
throw new RuntimeException(
"Cannot initialize HybridMemorySegment: off-heap memory is incompatible with this JVM.", t);
}
}
private static long getAddress(ByteBuffer buffer) {
if (buffer == null) {
throw new NullPointerException("buffer is null");
}
try {
return (Long) ADDRESS_FIELD.get(buffer);
}
catch (Throwable t) {
throw new RuntimeException("Could not access direct byte buffer address.", t);
}
}
在HybirdmemorySegment中,有一个私有的静态成员,可以看到是通过反射机制得到的java.nio的buffer中的address字段,也就是buffer的指针字段,在getAddress()方法中,也就是根据目标buffer中的此字段得到目标的指针。
get()方法与put()方法类似。