- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章Linux Glibc 内存站岗问题及解决方法由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
。
对于嵌入式设备来说,用户态内存管理是一项基础功能,目前主流的用户态内存管理库有glibc、uclibc、tcmalloc、jemalloc等.
本文基于glibc2.17版本进行分析,围绕glibc内存分配原理、内存站岗问题形成原因展开讨论,并对glibc缓存大量内存(高达几十个 G甚至上百 G)且不释放的问题给出一种解决方案.
笔者遇到的问题是基于glibc进行内存管理的64 位Linux系统。具体现象如下:设备32G物理内存,在大规格打流情况下,某用户进程占用的物理内存暴涨至20G左右.
在停止打流后,观察到业务模块已经释放了绝大部分内存,但是进程占用的物理内存依然达到16G左右,此后内存状况一直维持该状态,导致系统内存紧张,若叠加上其他业务则出现了OOM的现象,已排除该进程内存泄露的可能性.
。
Glibc使用了ptmalloc的内存管理方式,本文在描述时均使用glibc来称呼。Glibc申请内存时是从分配区申请的,分为主分配区和非主分配区,分配区都有锁,在分配内存前需要先获取锁,然后再去申请内存.
一般进程都是多线程的,当多个线程同时需要申请内存时,如果只有一个分配区,那么效率太低.
glibc为了支持多线程的内存申请释放,会在多个线程同时需要申请内存时根据cpu核数分配一定数量的分配区,将分配区分配给线程。如果线程数量较多,则会出现多个线程争用一个分配区的的情况,这里不展开.
内存申请基本原理:当用户调用malloc申请内存时,glibc会查看是否已经缓存了内存,如果有缓存则会优先使用缓存内存,返回一块符合用户请求大小的内存块.
如果没有缓存或者缓存不足则会去向操作系统申请内存(可通过brk、mmap申请内存),然后切一块内存给用户,如图1所示.
图 1 。
内存释放基本原理:当业务模块使用完毕后调用free释放内存时,glibc会检查该内存块虚拟地址上下内存块的使用状态(fast bin除外)。若其上一块内存空闲,则与上一块内存进行合并。若下一块内存空闲,则与下一块内存进行合并。如图2所示.
若下一块内存时top chunk(top chunk一直是空闲的),则看top chunk的大小是否超过一个阈值,如果超过一个阈值则将其释放给OS,如图3所示.
图2 。
图3 。
。
内存站岗概念:
内存站岗指的是glibc从OS申请到内存后分配给业务模块,业务模块使用完毕后释放了内存,但是glibc没有将这些空闲内存释放给OS,也就是缓存了很多空闲内存无法归还给系统的现象.
内存站岗原因:
glibc设计时就确定其内存是用于短生命周期的,因此在设计上内存释放给OS的时机是当top chunk的大小超过一个阈值时会释放top chunk的一部分内存给OS。当top chunk不超过阈值就不会释放内存给OS.
那么问题来了,若与top chunk相邻的内存块一直在使用中,那么top chunk就永远也不会超过阈值,即便业务模块释放了大量内存,达到几十个G 或者上百个G,glibc也是无法将内存还给OS的.
对于glibc来说,其有主分配和非主分配区的概念。主分配通过sbrk来增加分配区的内存大小,而非主分配区则是通过一个或多个mmap出来的内存块用链表链接起来模拟主分配区的。为了更清晰的解释内存站岗,下面举个例子来说明主分配区的内存站岗,如图4所示.
图4 。
如上有(a) (c) (e) (g)内存块正在使用,故而导致了空闲内存(b) (d) (f)无法和top chunk连成一块更大的空闲内存块,glibc的阈值(64位系统默认是128K),尽管目前空闲内存有将近130M,也无法还给OS.
接下来看非主分配区的内存站岗,如图 5 所示,实际的非主分配区可能有很多个heap,这里假设只有4个heap.
图5 。
在定位过程中,笔者与同事讨论过多次如何解决站岗。在一次讨论过程中由邓竑杰提出降低heap的size(类似于tcmalloc的做法),虽然实测后发现完全没有效果,但是为后续解决问题起到了启示作用.
后面笔者在走读代码时发现这是glibc原生机制,同时笔者在查看内存布局时观察到非主分配区大量heap均为free状态。原有机制是先释放heap3,如果heap3有内存在使用,尽管heap0、heap1、heap2的内存都释放了,那也是无法释放给系统.
glibc有多个分配区,每个分配区都几百 M 空闲内存的话,则整个进程占用达到几十个G也就不奇怪了.
。
在内存释放时,对于主分配区和非主分配其走的流程是不一样的,我们64位系统的进程内存模型为经典模式,栈是从高地址向低地址生长的.
对于主分配区的内存站岗我还没有遇到过,若主分配区内存站岗,一种方法是可以尝试madvise将主分配区的pagesize对齐的空闲内存进行释放,但是这样效果可能不太明显.
另外一种是通过创建线程,然后将主线程的业务移到新线程即可,这样主分配区就不会造成站岗了,而将站岗转移到了非主配区,而非主分配区则是我们接下来要进行优化的主战场.
针对非主分配区进行两处优化:
a) heap0,heap1,heap2是空闲的,那么我们就可以将heap1,heap2释放掉,
b) heap默认是64M,降低每个heap的size(笔者测试时设置为512K).
图 6 。
这里需要特别解释一下为什么不释放heap0和最后一个heap3,heap0的组成如图7所示。图左边是第一个heap即heap0,图右边是最后一个heap即heap3.
从图中可以清晰的看到如若释放掉heap0那么会将struct malloc_state结构体释放,会造成进程崩溃。右边这个由于有在用的内存,也不能释放掉。当然如果heap3的内存全部被释放了,则由glibc原生代码进行了处理,patch不再处理.
图 7 。
经过修改glibc源码,优化其释放机制,实际打流测试.
在打流到峰值后,进程使用了20G的内存,在停止打流后数秒内便恢复到了打流前的内存水平,进程所占用的内存基本还给系统了。至此,glibc内存站岗问题得到解决.
以上我们介绍了如何解决内存站岗的原理,纸上得来终觉浅,现在我们看patch源码实现.
目前笔者已经将该优化的patch提交到开源社区审核,提交到社区的patch未对heap的size进行修改,这是因为想要谨慎一些,毕竟开源的代码使用场景较多,如有需要可自行决定heap的size.
Patch基于glibc2.17代码 。
。
不同的内存管理方式均有其优势和缺陷,由于工作需要,笔者有幸研究过glibc、tcmalloc、uclibc内存管理,本文讨论了glibc内存管理存在的一个共性问题,并给出可行的解决方案.
对于内存站岗问题,一般的做法是用户自己缓存一些长时间不释放的内存。另一种是干脆将glibc替换为tcmalloc。因为 tcmalloc 的 span比较小,所以站岗发生的概率极低,即便发生也就站岗一个span的大小。若由于某些原因不能用tcmalloc代替glibc的场景,如上的解决思路可以尝试一下,该问题也困扰我们多时了,花费了较长时间和较多精力去定位.
在glibc2.28的版本中,glibc有了tcache的特性,对于业务进程使用大量小内存的场景则更加容易出现内存站岗问题。在撰写本文时查看了glibc2.33版本,开源社区还未对该问题进行修改(或许是开源社区大神认为这不是glibc的问题,而是用户不释放内存).
原文链接:https://mp.weixin.qq.com/s/CcLabnMzy8g3Ijq_Z4p_dg 。
最后此篇关于Linux Glibc 内存站岗问题及解决方法的文章就讲到这里了,如果你想了解更多关于Linux Glibc 内存站岗问题及解决方法的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
这个问题在这里已经有了答案: Multiple glibc libraries on a single host (11 个回答) 5年前关闭。 有没有办法在本地文件夹中安装新版本的 GLIBC?我将
这个问题在这里已经有了答案: Deploying Yesod to Heroku, can't build statically (4 个回答) 1年前关闭。 我有两台机器 RHEL 4机 RHEL
我在 C 启动时读取 article ,它指的是函数 __libc_csu_init 。谁能告诉我CSU代表什么? 最佳答案 我在 libc p 中找到了对“ C s tart u p”的引用。 这远
我正在使用 AlpineLinux(安装在我的计算机上,而不是安装在 docker 中)并且我正在尝试使用依赖于 glibc 的共享库。 由于 Glibc 没有集成到这个发行版中,我看到开发了一个替代
所以我正在玩 Alpine Linux,它使用 musl 与 glibc。 我发现由于这个选择,我无法运行 Oracle Java 或在 glibc 下构建的静态二进制文件。 musl 是否应该与更广
在构建 glibc 时,出现此错误。 *glibc/build/elf/dl-allobjs.os: file not recognized: File format not recognized c
我一直在寻找没有好的结果。 我想知道在文本中解释为 Malloc Maleficarum 或 Malloc Des-Malleficarum 的技术在 glibc 2.12.1 版本中是否有效。 在提
我在 CentOS 上安装 gcc 时遇到问题。 我的命令的输出sudo yum install gcc: Loaded plugins: fastestmirror Loading mirror s
我想下载gzip类型的mariadb,但是我发现它有很多文件可以下载,例如mariadb-10.2.6-linux-x86_64.tar.gz,mariadb-10.2.6-linux-glibc_2
我想尝试使用不同的 glibc 的 sin 函数。他们是否可以在不编译整个 glibc 的情况下使用不同 glibc 版本的 sin 函数? 我已经在我的虚拟 ubuntu 机器上下载了不同版本的 g
我要在超算中心的目录下安装一个软件,但是sudo,root,连apt-get,yum都不能用...这个软件需要glibc2.7,所以我得手动安装gcc和glibc(因为我无法更改内置 gcc 的路径来
我正在研究需要在多个不同的嵌入式平台上运行的 C 和 C++ 程序,为此我有交叉编译器,因此我可以在我的 x86 桌面上进行构建。 我在某些功能上遇到了一个可怕的问题,例如“strtod()”。这是我
我遇到了一个非常烦人的问题:我有一个程序,它在开始时创建一个线程,该线程将在执行期间启动其他内容(fork() 紧随其后的是 execve())。 这是我的程序达到(我认为)死锁时两个线程的 bt:
我刚刚发现,glibc 2.23 有一个关于 stdio 函数 fmemopen() 的错误,参见例如Using rewind() on a FILE* opened with fmemopen .
[root@localhost Keystone2]# sudo rpm -ivh glibc-2.3.4-2.9.audit.x86_64.rpm --nodeps --force warning:
从该系列中很容易找到如何使用 dlsym()和其他函数,但是在内部如何工作呢?是否可以编写自己的,容易实现的dlsym()? 我想知道是否有可能实现类似的行为,但在不将与-ldl链接的情况下实现(可以
嗨,我目前使用的是 ubuntu 9.10 和 glibc 版本 2.11.1-0,好吧,我正在做一个项目,我想用 glibc 的另一个版本(2.5-58)进行测试,我想知道以下与此相关的事情: 如何
我已经下载了glibc,这样我就可以修改它的代码然后使用它。所以只是为了玩玩它,我更改了 glibc 的 stdio-common 目录中的 printf 函数,以便它打印“你可以看到我吗?”。 in
我正在集群上的 conda 环境中部署 R 脚本。该脚本加载了一些需要 glibc 版本 >= 2.14 的包。虽然我的系统的 glibc 版本为 2.14,但集群的 glibc 版本为 2.12,我
所以。我有一个问题,我在一台机器上有两个版本的 GCC。 3.4.6 和 4.1 这是由于新软件的一些依赖性问题。 (需要 glibc 4.1) 当我将这个新软件与 4.1 库链接时,它链接得很好。但
我是一名优秀的程序员,十分优秀!