一、为什么自己管理内存?
- 现在越来越多的大数据开源项目开始自己管理内存。一般来说原因主要如下:
- 1、越来越流行的内存计算,导致越来越大的堆(上百G),GC时间越来越长,有时秒级甚至是分钟级的垃圾回收极大的影响了Java应用的性能和可用性。
- 2、OOM问题,当对象大小超过JVM内存大小后(Full GC后,内存还是不够分配对象),JVM就会出现OOM错误,导致崩溃。
- 3、Java 对象存储密度低。一个只包含 boolean 属性的对象占用了16个字节内存:对象头占了8个,boolean 属性占了1个,对齐填充占了7个。而实际上只需要一个bit(1/8字节)就够了。
- 4、对象存储结构引发的cache miss。
-
CPU逐渐成为了大数据领域的瓶颈。从 L1/L2/L3 缓存读取数据的速度比从主内存读取数据的速度快好几个量级。通过性能分析可以发现,CPU时间中的很大一部分都是浪费在等待数据从主内存过来上。如果这些数据可以从 L1/L2/L3 缓存过来,那么这些等待时间可以极大地降低,并且所有的算法会因此而受益。
-
现代CPU数据访问一般都会有多级缓存。当从内存加载数据到缓存时,一般是以cache line为单位加载数据,所以当CPU访问的数据如果是在内存中连续存储的话,访问的效率会非常高。如果CPU要访问的数据不在当前缓存所有的cache line中,则需要从内存中加载对应的数据,这被称为一次cache miss。当cache miss非常高的时候,CPU大部分的时间都在等待数据加载,而不是真正的处理数据。Java对象并不是连续的存储在内存上,同时很多的Java数据结构的数据聚集性也不好。
-
二、如何更好的内存管理
- 1、定制的序列化工具
- 显式内存管理的前提步骤就是序列化,将Java对象序列化成二进制数据存储在内存上(on heap或是off-heap)
- Java 生态圈提供了众多的序列化框架:Kryo, Apache Avro 等等。但是 Flink 实现了自己的序列化框架。
- 2、 显式的内存管理。
- 一般通用的做法是批量申请和释放内存,每个JVM实例有一个统一的内存管理器,所有的内存的申请和释放都通过该内存管理器进行。这可以避免常见的内存碎片问题,同时由于数据以二进制的方式存储,可以大大减轻垃圾回收的压力。同时可以自己控制内存的使用量,避免发生OOM。
- 3、缓存友好的数据结构和算法。
- 只将操作相关的数据连续存储,可以化的利用L1/L2/L3缓存,减少Cache miss的概率,提升CPU计算的吞吐量。
- Flink 通过定制的序列化框架将算法中需要操作的数据(如sort中的key)连续存储,而完整数据存储在其他地方。因为对于完整的数据来说,key+pointer更容易装进缓存,这大大提高了缓存命中率,从而提高了基础算法的效率。这对于上层应用是完全透明的,可以充分享受缓存友好带来的性能提升。
三、使用堆外内存
- 使用堆外内存的优点:
- 1、启动超大内存(上百GB)的JVM需要很长时间,GC停留时间也会很长(分钟级)。使用堆外内存的话,可以极大地减小堆内存,扩展到上百GB内存不是问题。
- 2、高效的 IO 操作。堆外内存在写磁盘或网络传输时是 zero-copy,而堆内存的话,至少需要 copy 一次。
- 3、堆外内存是进程间共享的。也就是说,即使JVM进程崩溃也不会丢失数据。这可以用来做故障恢复。
- 使用堆外内存的缺点:
- 1、堆内存的使用、监控、调试都要简单很多。堆外内存意味着更复杂更麻烦。
- 2、有时需要分配短生命周期的对象,这个申请在堆上会更廉价。
- 3、有些操作在堆内存上会快一点点。
四、后记
- 从去虚化(de-virtualized)和内联(inlined)的JIT编译优化,到自己进行内存管理,优化是没有止境的_。
网友评论