通过前面几节对ptmalloc实现的粗略分析,尝试去分析和解决我们遇到的问题,我们系统遇到的问题是glibc内存暴增,现象是程序已经把内存返回给了Glibc库,但Glibc库却没有把内存归还给操作系统,最终导致系统内存耗尽,程序因为OOM被系统杀掉。
请参考3.2.2节对ptmalloc的设计假设与3.2.7节对ptmalloc的使用注意事项,原因有如下几点:
-
在64位系统上使用默认的系统配置,也就是说ptmalloc的mmap分配阈值动态调整机制是开启的。我们的NoSql系统经常分配内存为2MB,并且这2MB的内存很快会被释放,在ptmalloc回收2MB内存时,ptmalloc的动态调整机制会认为2MB对我们的系统来说是一个临时的内存分配,每次都用系统调用mmap()向操作系统分配内存,ptmalloc认为这太低效了,于是把mmap的阈值设置成了2MB+4K,当下次再分配2MB的内存时,尽量从ptmalloc缓存的chunk中分配,缓存的chunk不能满足要求,才考虑调用mmap()进行分配,提高分配的效率。
-
系统中分配2M内存的地方主要有两处,一处是全局的内存cache,另一处是网络模块,网络模块每次分配2MB内存用于处理网络的请求,处理完成后就释放该内存。这可以看成是一个短生命周期的内存。内存cache每次分配2MB,但不确定什么时候释放,也不确定下次会什么时候会再分配2MB内存,但有一点可以确定,每次分配的2MB内存,要经过比较长的一段时间才会释放,所以可以看成是长生命周期的内存块,对于这些cache中的多个2M内存块没有使用free list管理,每次都是先从cache中free调用一个2M内存块,再从Glibc中分配一块新的2M内存块。Ptmalloc不擅长管理长生命周期的内存块,ptmalloc设计的假设中就明确假设缓存的内存块都用于短生命周期的内存分配,因为ptmalloc的内存收缩是从top chunk开始,如果与top chunk相邻的那个chunk在我们NoSql的内存池中没有释放,top chunk以下的空闲内存都无法返回给系统,即使这些空闲内存有几十个G也不行。
-
Glibc内存暴增的问题我们定位为全局内存池中的内存块长时间没有释放,其中还有一个原因就是全局内存池会不定期的分配内存,可能下次分配的内存是在top chunk分配的,分配以后又短时间不释放,导致top chunk升到了一个更高的虚拟地址空间,从而使ptmalloc中缓存的内存块更多,但无法返回给操作系统。
-
另一个原因就是进程的线程数越多,在高压力高并发环境下,频繁分配和释放内存,由于分配内存时锁争用更激烈,ptmalloc会为进程创建更多的分配区,由于我们的全局内存池的长时间不释放内存的缘故,会导致ptmalloc缓存的chunk数量增长得更快,从而更容易重现Glibc内存暴增的问题。在我们的ms上这个问题最为突出,就是这个原因。
-
内存池管理内存的方式导致Glibc大量的内存碎片。我们的内存池对于小于等于64K的内存分配,则从内存池中分配64K的内存块,如果内存池中没有,则调用malloc()分配64K的内存块,释放时,该64K的内存块加入内存中,永不还回给操作系统,对于大于64K的内存分配,调用malloc()分配,释放时调用free()函数换回给Glibc。这些大量的64K的内存块长时间存在于内存池中,导致了Glibc中缓存了大量的内存碎片不能释放回操作系统。比如:
假如应用层分配内存的顺序是64K,100K,64K,然后释放100K的内存块,Glibc会缓存这个100K的内存块,其中的两个64K内存块都在mempool中,一直不释放,如果下次再分配64K的内存,就会将100K的内存块拆分成64K和36K的两个内存块,64K的内存块返回给应用层,并被mempool缓存,但剩下的36K被Glibc缓存,再也不能被应用层分配了,因为应用层分配的最小内存为64K,这个36K的内存块就是内存碎片,这也是内存暴增的原因之一。
问题找到了,解决的办法可以参考如下几种:
-
禁用ptmalloc的mmap分配阈值动态调整机制。通过mallopt()设置M_TRIM_THRESHOLD,M_MMAP_THRESHOLD,M_TOP_PAD和M_MMAP_MAX中的任意一个,关闭mmap分配阈值动态调整机制,同时需要将mmap分配阈值设置为64K,大于64K的内存分配都使用mmap向系统分配,释放大于64K的内存将调用munmap释放回系统。但强烈建议不要这么做,这会大大降低ptmalloc的分配释放效率。因为系统调用mmap是串行的,操作系统需要对mmap分配内存加锁,而且操作系统对mmap的物理页强制清0很慢,请参看3.2.6选项配置相关描述。由于最初我们的系统的预分配优化做得不够好,关闭mmap的动态阈值调整机制后,chunkserver在ssd上的性能减少到原来的1/3,这种性能结果是无法让人接受的。
-
我们系统的关键问题出在全局内存池,它分配的内存是长生命周期的大内存块,通过前面的分析可知,对长生命周期的大内存块分配最好用mmap系统调用直接向操作系统分配,回收时用munmap返回给操作系统。比如内存池每次用mmap向操作系统分配8M或是更多的虚拟内存。如果非要用ptmalloc的malloc函数分配内存,就得绕过ptmalloc的mmap分配阈值动态调整机制,mmap分配阈值在64位系统上的最大值为32M,如果分配的内存大于32M,可以保证malloc分配的内存肯定是用mmap向操作系统分配的,回收时free一定会返回给操作系统,而不会被ptmalloc缓存用于下一次分配。但是如果这样使用malloc分配的话,其实malloc就是mmap的简单封装,还不如直接使用mmap系统调用想操作系统分配内存来得简单,并且显式调用munmap回收分配的内存,根本不依赖ptmalloc的实现。
-
改写内存cache,使用free list管理所分配的内存块。使用预分配优化已有的代码,尽量在每个请求过程中少分配内存。并使用线程私有内存块来存放线程所使用的私有实例。这种解决办法也是暂时的。
从长远的设计来看,我们的系统也是分阶段执行的,每次网络请求都会分配2MB为单位内存,请求完成后释放请求锁分配的内存,内存池最适合这种情景的操作。我们的线程池至少需要包含对2MB和几种系统中常用分配大小的支持,采用与TCMalloc类似的无锁设计,使用线程私用变量的形式尽量减少分配时线程对锁的争用。或者直接使用TCMalloc,免去了很多的线程池设计考虑。
分享到:
相关推荐
《Glibc内存管理--ptmalloc2源代码分析》 Glibc是GNU项目提供的C语言标准库,它在Linux系统中扮演着至关重要的角色。其中,内存管理是Glibc中的核心部分,它负责程序运行时的内存分配与释放,对系统的性能有着深远...
在分析glibc内存管理的ptmalloc源代码之前,我们需要先了解一些基础知识,包括操作系统对内存的分配和管理方法,以及glibc内存分配机制。 内存管理是操作系统的一个核心功能,它负责维护和管理计算机系统中的物理和...
《glibc内存管理ptmalloc源代码分析》是一份深入探讨Linux系统中glibc库内存管理机制的专业资料。glibc,全称GNU C Library,是Linux操作系统下广泛使用的C语言标准库,其中ptmalloc是glibc中负责动态内存分配的核心...
在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念。内存管理是操作系统和编程语言库中的核心组件,它负责有效地分配和回收内存,以确保程序的高效运行和资源的有效利用。 2.1 X86...
通过对ptmalloc源代码的深入分析,可以找到潜在的优化点,如调整分配策略、改进数据结构设计等,以解决这些问题。 #### 13. 使用注意事项 虽然ptmalloc提供了强大的内存管理功能,但在使用过程中也需要注意一些事项...
glibc内存管理ptmalloc源代码分析
在深入探讨Glibc内存管理的Ptmalloc源代码之前,我们先来了解一下内存管理的基本概念和Glibc中的Ptmalloc2。内存管理是操作系统和应用程序中的核心部分,它负责为程序分配和释放内存,以确保资源的有效利用和避免...
### glibc内存管理ptmalloc源代码分析 #### 1. 问题 在开发一款NoSQL系统的过程中,我们遇到一个令人头疼的问题:系统中使用的内存管理模块,在高并发、高负载的场景下,虽然已将内存释放给C运行时库(即glibc),...
### glibc内存管理ptmalloc源代码分析-清晰版 #### 一、背景介绍与文档概览 本文档针对glibc中的ptmalloc2内存管理模块进行了深入的源代码分析,旨在帮助开发者更好地理解ptmalloc的工作原理及其内部机制。文档...
`glibc`是GNU项目提供的C标准库,而`ptmalloc`是它的一部分,负责程序的内存分配与管理。`ptmalloc`优化了内存分配效率,但在某些情况下也可能成为安全漏洞的来源。 `GDB`(GNU调试器)是一款强大的调试工具,它...
本文将深入探讨内存管理的基本原理及其调试方法,特别关注于Linux环境下的Glibc内存管理库以及Ptmalloc2的具体实现。 #### 二、基础知识 ##### 2.1 X86平台Linux进程内存布局 **2.1.1 32位模式下进程内存经典布局...
通过对glibc库中的ptmalloc源代码进行深入分析,我们不仅了解了其内部实现机制,还能够针对实际应用中的内存管理问题提出有效的解决方案。这对于提升程序性能、减少内存碎片以及提高系统的整体稳定性具有重要意义。...
学习自《glibc内存管理ptmalloc源代码分析》庄明强 著 部分资料参考自互联网 chunk 描述: 当用户通过malloc等函数申请空间时,实际上是从堆中分配内存 目前 Linux 标准发行版中使用的是 glibc 中的堆分配器:...
《堆漏洞的利用技巧》 堆溢出是计算机安全领域中的一个重要...同时,阅读glibc内存管理的源代码分析对于提高理解和实践能力非常有帮助。通过这些知识的学习,不仅可以增强安全意识,也能提升在CTF竞赛中的攻防能力。