程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2023-06(3)

flink内存管理的理解

发布于2021-06-14 10:02     阅读(1015)     评论(0)     点赞(11)     收藏(4)


在这里插入图片描述

一,flink的内存结构

在这里插入图片描述

1,堆内存

  • frame heap:flink框架基于jvm运行,本身有很多类对象,存储在堆内存
  • task heap:用户代码创建的java对象存放在对内存,heapstatebackend使用的内存

2,堆外内存

  • managed memory:RockDBStateBackend申请的堆外内存,不受flink和jvm管理,可以通过设置rocksdb内存的大小间接控制;批处理算子的排序也会在这块内存进行
  • direct memory:有flink通过java申请的堆外内存
  • framewor off-heap:
  • framewor off-heap:
  • network buffer:flink任务网络数据传输使用的缓存
  • jvm metaspace:jvm元空间,存放字节码,常量池
  • jvm overhead

在这里插入图片描述

二,内存管理

1,为什么要单独的内存管理?

  • java对象的低密度存储
  • gc的性能消耗和用户不友好性(stw)
  • oom的用户不友好性

2,积极的内存管理

针对上面三个问题,flink采取有针对性的措施基于jvm进行更高层次的内存管理。

  • 独有的序列化
    针对java对象低密度存储的问题和flink内存计算的特点,flink基于自身特点开发了不同于java序列化框架的技术,flink任务缓存在内存的数据都会被序列化,节省了大量的空间。

  • 走向堆外
    堆内大内存会导致jvm初始化时间长、GC时间长,针对这个问题,flink充分利用堆外内存,将RocksDBBackend所需的内存放在堆外,需要网络传输的数据放在堆外缓冲区。这样会减少gc的次数和oom发生的概率。

基于堆外内存,网络传输还可以实现zero-copy

  • 缓存友好的数据结构

  • flink的二进制数据操作
    排序缓冲区在内部分为两个内存区域:第一个区域保存所有对象的完整二进制数据,第二个区域包含指向完整二进制对象数据的指针(取决于 key 的数据类型)。将对象添加到排序缓冲区时,它的二进制数据会追加到第一个区域,指针(可能还有一个 key)被追加到第二个区域。分离实际数据和指针以及固定长度的 key 有两个目的:它可以有效的交换固定长度的 entries(key 和指针),还可以减少排序时需要移动的数据。如果排序的 key 是可变长度的数据类型(比如 String),则固定长度的排序 key 必须是前缀 key,比如字符串的前 n 个字符。请注意:并非所有数据类型都提供固定长度的前缀排序 key。将对象序列化到排序缓冲区时,两个内存区域都使用内存池中的 MemorySegments 进行扩展。一旦内存池为空且不能再添加对象时,则排序缓冲区将会被完全填充并可以进行排序。Flink 的排序缓冲区提供了比较和交换元素的方法,这使得实际的排序算法是可插拔的。默认情况下, Flink 使用了 Quicksort(快速排序)实现,可以使用 HeapSort(堆排序)。

key通常是指作为排序依据的属性。

补充:为什么需要对外内存才能实现zero copy

参考文章

DirectByteBuffer是一个特殊的ByteBuffer,底层同样需要一块连续的内存,操作模式与普通的ByteBuffer一致,但这块内存是调用unsafe的native方法分配的堆外内存。

直接缓冲区的内存释放也是由unsafe的native方法完成的,DirectByteBuffer指向的内存通过PhantomReference持有,由JVM自行回收。但如果DirectByteBuffer经过数次GC后进入老年代,就很可能由于Full GC间隔较长而长期存活,进而导致指向的堆外内存也无法回收。当需要手动回收时,需要通过反射调用DirectByteBuffer内部的Cleaner的clean私有方法。

为何要使用堆外内存

Java应用一般能够操作的是JVM管理的堆内内存,一段数据从应用中发送至网络需要经过多次复制:

从堆内复制到堆外
从堆外复制到socket缓存
socket缓存flush
考虑到Java内存模型,可能还存在工作内存/主内存之间的复制;

考虑到GC,可能还存在堆内内存之间的复制;

而如果使用堆外内存,则少了一步从堆内到堆外的复制过程。

使用直接缓冲区的优点:

这块缓冲区内存不受JVM直接管理回收
大小不受JVM分配的最大内存限制
一些IO操作可以避免堆外内存和堆内内存间的复制,比如网络传输
某些生命周期较长的大对象可以保存在堆外内存,减少对GC的影响

缺点:

不受JVM直接管理,容易造成堆外内存泄露
由于堆外内存并不能保存复杂对象而只能保存基本类型的包装类(底层都是byte array),因此要保存对象时需要序列化

必须先复制到堆外内存的原因

底层通过write、read、pwrite,pread函数进行系统调用时,需要传入buffer的起始地址和buffer count作为参数。如果使用java heap的话,我们知道jvm中buffer往往以byte[] 的形式存在,这是一个特殊的对象,由于java heap GC的存在,这里对象在堆中的位置往往会发生移动,移动后我们传入系统函数的地址参数就不是真正的buffer地址了,这样的话无论读写都会发生出错。而C Heap仅仅受Full GC的影响,相对来说地址稳定。
JVM规范中没有要求Java的byte[]必须是连续的内存空间,它往往受宿主语言的类型约束;而C Heap中我们分配的虚拟地址空间是可以连续的,而上述的系统调用要求我们使用连续的地址空间作为buffer。

参考文献

1,为什么要堆外内存才能zero copy
2,flink内存管理总结
3,一文搞定flink内存管理
4,flink内存模型各模块解释
5,flink中文官网-内存管理
*6,flink如何操作二进制数据-以排序为例



所属网站分类: 技术文章 > 博客

作者:java之恋

链接:http://www.javaheidong.com/blog/article/222769/4912e21e87914396c2df/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

11 0
收藏该文
已收藏

评论内容:(最多支持255个字符)