如何解决内存碎片问题?
————NEOHOPE的内存碎片优化手册
其实无论采用哪种分配方式,内存的碎片化都是难以彻底避免的。无论是操作系统、虚拟机还是应用,都要面对这个问题。
业界有多种思路来解决或缓解此问题:
一、操作系统层面
1、把不可移动内存集中管理,内存分区其实在一定程度上解决了这些问题
从操作系统的角度来说,有些内存页面是可以移动的,有些是无法移动的。如果无法移动的页面过于分散,整个系统内存的连续性就很差,碎片化就更严重,而且更加不容易管理。所以在操作系统设计的时候,一般会根据功能用途对内存进行分区,一定程度上避免此类问题。
2、linux采用了buddy system来缓解内存碎片问题
buddy system是一个很巧妙的设计,将内存连续页面按2的n次方进行分组。
申请时会匹配第一个大于等于所需页面的2的n次方连续页面,并将多余页面拆分后挂载到对应的2的x方组。
释放内存页面时,仍按2的n次方进行归还,并尝试将内存进行合并。
3、linux中为了处理特别小的内存请求,引入了slab技术,来辅助buddy system
类似于对象池的概念,系统先申请一部分页面用于对象池。
申请时划分一部分出来给应用使用,如果内存不足会进行主动扩容。
归还时对象还给对象池,如果空闲对象比较多时,会主动释放部分内存。
4、windows有一种LFH(Low Fragmentation Heap)技术,缓解内存碎片问题
在应用程序启动时,操作系统会额外分配一定的连续内存LFH给这个进程备用
如果应用需要使用内存,会优先从LFH中申请,从而降低系统层面的内存管理负担
5、windows在进程退出后,不会立即释放dll文件内存
一方面提速,如果关闭一个应用,再开启,就会感觉很快
另一方面也缓解了操作系统内存管理负担。
其实,看下你手机的APP,切换到后台时,就是这个效果
6、内存整理服务
无论是linux还是windows都有低优先级线程,在后台默默做着内存规整工作,类似于磁盘碎片清理
比如linux内核中的kcompactd线程。
7、类似与LFH,可以考虑在内存分配时额外预留一部分,下次分配在预留的地方继续分配
windows在xp时代,需要应用自行开启LFH功能,但vista之后,操作系统会同一进行管理
8、为了内存规整方便,可以考虑靠近应用已分配内存区域进行分配
其实可操作性不高,还不如上一条可行性好一些
9、还有一种思路,就是将不连续的内存,转换为逻辑上连续的内存,来绕过碎片化问题
但一些情况下性能难以保证
二、虚拟机层面
1、JVM整体内存会被划分为多个部分,类似于做了分区:
一部分是虚拟机共有的【方法区、堆】,一部分是线程私有的【虚拟机栈、本地栈、程序计数器】
2、同时,JVM虚拟机也会根据对象的生命周期,类似进一步做了分区,而且不同分区采用不同GC策略
最常用的就是年代划分法,新生代、老年代、永久代【后来的Metaspace】
3、JVM虚拟机,GC时会通过标记-整理(比如CMS)或复制-清除(比如G1)的方法来解决部分碎片问题
三、应用层面
1、redis在处理内存的时候,申请时会额外申请一部分先备着【记得是jemalloc】
2、redis释放时也不会立即释放,有单独的线程进行处理,在应用层面去降低系统内存管理负担
3、redis在数据结构上也做了很多努力
4、在写程序时,如果需要频繁创建和释放对象,可以尝试使用对象池
5、在写程序的时候,尽量不要零零散散的去申请大量小内存;
6、除了标准库以外,可以试一下 jemalloc或Doug Lea’s malloc
7、感兴趣可以看下redis内存管理的代码