GLIBC内存分配机制引发的“内存泄露”

简介:

我们正在开发的类数据库系统有一个内存模块,出现了一个疑似”内存泄露”问题,现象如下:内存模块的内存释放以后没有归还操作系统,比如内存模块占用的内存为10GB,释放内存以后,通过TOP命令或者/proc/pid/status查看占用的内存有时仍然为10G,有时为5G,有时为3G, etc,内存释放的行为不确定。

首先说一下内存模块的内存管理机制。我们的内存管理很简单,使用全局的定长内存池,每一个内存块为64KB,如果申请的内存小于等于64KB时,直接从内存池的空闲链表中获取一个内存块,内存释放时归还空闲链表;如果申请的内存大于64KB,直接通过操作系统的malloc和free获取。某些数据结构涉及到很多小对象的管理,比如Hash表,B-Tree,这些数据结构从全局内存池获取内存后再根据数据结构的特点进行组织。为了提高内存申请/释放的效率,减少锁冲突,为每一个线程单独保留一个8MB的内存块,每个线程优先从线程专属的8MB内存块获取内存,专属内存不足时才从全局的内存池获取。

由于我们的所有内存申请/释放操作都需要通过全局的内存池进行,我们在全局的内存池中加入对每个子模块的内存统计功能:每个子模块申请内存时都将子模块编号传给全局的内存池,全局的内存池进行统计。复现问题后发现全局的内存池的统计结果符合预期,因此怀疑是操作系统或者glibc的行为。

Linux下Glibc的内存管理机制大致如下:

从操作系统的角度看,进程的内存分配由两个系统调用完成:brk和mmap。brk是将数据段(.data)的最高地址指针_edata往高地址推,mmap是在进程的虚拟地址空间中找一块空闲的。其中,mmap分配的内存由munmap释放,内存释放时将立即归还操作系统;而brk分配的内存需要等到高地址内存释放以后才能释放。也就是说,如果先后通过brk申请了A和B两块内存,在B释放之前,A是不可能释放的,仍然被进程占用,通过TOP查看疑似”内存泄露”。默认情况下,大于等于128KB的内存分配会调用mmap/mummap,小于128KB的内存请求调用sbrk(可以通过设置M_MMAP_THRESHOLD来调整)。详细的内存管理机制可以参考百度分享的文章

我们的内存模块申请/释放内存都是以2MB为单位的,按理说应该是使用mmap和munmap进行内存分配和释放的,不会出现内存释放以后仍然被进程占用的情况。在内核同学的协助下,经过长时间的分析定位,发现了Glibc的新特性:M_MMAP_THRESHOLD可以动态调整。M_MMAP_THRESHOLD的值在128KB到32MB(32位机)或者64MB(64位机)之间动态调整,每次申请并释放一个大小为2MB的内存后,M_MMAP_THRESHOLD的值被调整为2M到2M + 4K之间的一个值(具体可以参考Glibc的patch说明)。例如:

char* no_used = new char[2 * 1024 * 1024];

memset(no_used, 0xfe, 2 * 1024 * 1024);

delete[] no_used;

// M_MMAP_THRESHOLD的值调整为2M到2M + 4K之间的一个值,后续申请 <= 2 * 1024 * 1024的内存块都会走sbrk而不是mmap

了解到这种现象后,我们找到了”内存泄露”的原因:M_MMAP_THRESHOLD的值动态调整,后续的2MB的内存申请通过sbrk实现,而sbrk需要等到高地址内存释放以后低地址内存才能释放。可以通过显式设置M_MMAP_THRESHOLD或者M_MMAP_MAX来关闭M_MMAP_THRESHOLD动态调整的特性,从而避免上述问题。

当然,mmap调用是会导致进程产生缺页中断的,为了提高性能,常见的做法如下:

1, 将动态内存改为静态,比如采用内存池技术或者启动的时候给每个线程分配一定大小,比如8MB的内存,以后直接使用;

2, 禁止mmap内存调用,禁止Glibc内存缩紧将内存归还系统,Glibc相当于实现了一个内存池功能。只需要在进程启动的时候加入两行代码:

mallopt(M_MMAP_MAX, 0); // 禁止malloc调用mmap分配内存

mallopt(M_TRIM_THRESHOLD, 0); // 禁止内存缩进,sbrk申请的内存释放后不会归还给操作系统

花絮:

追查”内存泄露”问题的过程中,尝试使用Glibc的钩子函数(Malloc Hook) 统计malloc和free的内存量:具体做法为malloc的时候多申请8个字节,其中4个字节记录长度,4个字节记录magic_num,malloc和free的时候统计进程申请和释放的内存量。实践表明无论自定义钩子函数是否加锁,malloc和free钩子函数在多线程的情况下运行都不正常,其它同学也发现了相同的问题(Malloc Hook多线程问题)。

目录
相关文章
|
2天前
|
算法 程序员 调度
深入理解操作系统的内存管理机制
【5月更文挑战第9天】 在现代计算机系统中,操作系统的内存管理是一个至关重要的部分,它直接影响到系统的性能和稳定性。本文将深入探讨操作系统的内存管理机制,包括物理内存的管理、虚拟内存的概念和应用,以及内存分配和回收的策略。通过对这些内容的深入理解,我们可以更好地理解操作系统的工作原理,提高我们的编程效率和质量。
|
11天前
|
算法
深入理解操作系统的内存管理机制
【4月更文挑战第30天】 在现代计算机系统中,操作系统扮演着至关重要的角色,它负责协调和管理硬件资源,确保系统高效、稳定地运行。其中,内存管理是操作系统的核心功能之一,涉及到物理内存的分配、虚拟内存的映射以及内存的优化等多个方面。本文将深入探讨操作系统中的内存管理机制,包括分页、分段和段页式结合等技术,旨在为读者提供一个清晰的内存管理框架视图,并解释这些技术如何提高系统的性能和稳定性。
|
2天前
|
算法 安全 UED
深入理解操作系统的内存管理机制
【5月更文挑战第9天】 在本文中,我们将探讨操作系统的核心组件之一——内存管理。不同于传统的摘要概述,我们将直接切入主题,首先介绍内存管理的基础知识,然后深入讨论操作系统如何处理内存分配、内存保护以及虚拟内存技术。通过分析具体实例和案例研究,文章旨在为读者提供一个清晰的框架,以理解内存管理在现代操作系统中的实现和重要性。
4 0
|
2天前
|
存储 内存技术
深入理解操作系统的内存管理机制
【5月更文挑战第9天】操作系统的内存管理机制是计算机科学中的核心概念,它负责协调和管理计算机的内存资源,确保系统的稳定性和效率。本文将深入探讨操作系统的内存管理机制,包括内存分配、内存保护和虚拟内存等关键技术,帮助读者更好地理解和掌握操作系统的运行原理。
|
11天前
|
存储 缓存 算法
深入理解操作系统的内存管理机制
【4月更文挑战第30天】 在现代计算机系统中,操作系统的内存管理机制是确保系统高效稳定运行的关键。本文将深入探讨操作系统中的内存管理概念,包括虚拟内存、物理内存、内存分配策略以及页面置换算法。通过对这些关键概念的剖析,我们能够理解操作系统如何有效地管理有限的内存资源,支持多任务并发执行,同时保证系统的响应速度和稳定性。
|
11天前
|
监控 算法 安全
深入理解操作系统的内存管理机制
【4月更文挑战第30天】 在现代计算机系统中,操作系统的内存管理是确保系统高效、稳定运行的关键。本文将探讨操作系统内存管理的核心技术,包括内存分配、虚拟内存、分页和分段等概念,以及它们是如何协同工作以提高内存利用率和系统性能的。通过对这些技术的详细分析,我们可以更好地理解操作系统背后的原理,并评估不同内存管理策略对系统行为的影响。
|
11天前
|
算法 安全 Linux
深度解析:Linux内核内存管理机制
【4月更文挑战第30天】 在操作系统领域,内存管理是核心功能之一,尤其对于多任务操作系统来说更是如此。本文将深入探讨Linux操作系统的内核内存管理机制,包括物理内存的分配与回收、虚拟内存的映射以及页面替换算法等关键技术。通过对这些技术的详细剖析,我们不仅能够理解操作系统如何高效地利用有限的硬件资源,还能领会到系统设计中的性能与复杂度之间的权衡。
|
11天前
|
Swift 开发者
【Swift开发专栏】Swift中的内存管理ARC机制
【4月更文挑战第30天】Swift的Automatic Reference Counting (ARC)自动管理内存,通过跟踪对象引用实现对象的释放。当引用计数为0时,系统回收内存。引用计数在变量赋值时增加,引用移除时减少。循环引用可能导致内存泄漏,Swift通过weak(可选)和unowned(非空)引用解决此问题,根据对象生命周期选择合适类型。理解ARC和正确处理循环引用是关键。
|
12天前
|
存储 安全 算法
深入理解操作系统的内存管理机制
【4月更文挑战第29天】 在现代计算机系统中,操作系统的内存管理是一个至关重要的组成部分。它不仅负责物理内存的分配与回收,还涉及虚拟内存到物理内存的映射、内存保护以及效率优化等多个方面。本文将深入探讨操作系统内存管理的基本原理和关键技术,包括分页系统、分段、请求分页和分段以及它们在现代操作系统中的应用。文章旨在为读者提供一个清晰、系统的内存管理知识框架,并通过分析不同内存管理技术的优势和挑战来展望内存管理的发展趋势。
|
12天前
|
算法 安全 调度
深入理解操作系统的内存管理机制
【4月更文挑战第29天】 在现代计算机系统中,操作系统的内存管理是确保系统高效稳定运行的关键要素。本文将探讨操作系统内存管理的核心技术,包括虚拟内存、分页机制、内存分配策略以及内存保护。文章旨在为读者提供一个清晰的框架,以理解操作系统是如何有效地管理有限的物理内存资源,同时满足多个并发运行程序的需求。通过分析不同内存管理技术的优势与局限,本文也将展示内存管理对于提高系统性能和安全性的重要性。