内存泄漏专题(9)内存池陷阱

简介: 内存泄漏专题(9)内存池陷阱

内存池的引入,主要是解决内存碎片和内核开销的。其主要思路就是提前申请一块池子,后续的所有的内存操作都从池子里申请内存,用完了还给池子,减少与系统的交互。

因此,有了内存池的引入,基本上不需要考虑内存泄漏问题,因为我们将对内存的管理转换成了对内存池的生命周期管理。我们只需要在程序一开始申请一个池子,在程序结束的时候释放这个池子,基本不会出现内存泄漏。大概的伪代码如下所示:

#include <stdio.h>
#include "pool.h"
int main(void){
  pool_t *pool = pool_create(size);
  //业务流程
  proccess(pool);
  pool_destroy(pool);
  return 0;
}

内存池一般分为定长内存池和非定长内存池。定长内存池就是一开始就向系统申请一大块固定大小的内存,后续的操作只用这块内存。但定长内存池的不足之处在于我们很难预估实际业务需要用到多大的内存。

比如我们有一个服务端程序,它可以接受客户端的连接并传输数据。我们存储连接会话使用内存池的堆上内存,假设一个连接会话需要使用100字节内存,那么1000个连接就需要100kb内存,如果是1万个,100万个呢?那么可能就需要100M,甚至更高的内存。

我们无法预估到底有多少连接,因此我们只能尽可能多地申请内存。那么带来的一个问题就是,万一没有那么大的量呢?多申请的内存不就浪费了吗?

因此非定长内存池可以很好地解决这个问题。非定长内存池常规做法是一开始申请一块比较小的内存,如果后续需要用到的内存超过了当前内存池的大小,再对内存池进行扩容。但是非定长内存池说白了还是把使用权交还给开发者了,如果扩容频繁,会频繁与系统内核交互,仍然会产生很多内存碎片,而且更恐怖的是,内存扩容是没有上限的!

理论上来说,内存池应该是非常安全的才对,我又为什么说内存池会有内存泄漏的陷阱呢?原因就在于非定长内存池。

比较著名的开源非定长内存池,就是libapr,这是apache开源的一套C语言跨平台底层库。这里面就有一套apr_pool的内存池实现。接下来笔者就以apr_pool来举例,具体说一说内存池的内存泄漏陷阱。

看下面一段代码:

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <apr_general.h>
#include <apr_pools.h>
int main(int argc, const char *argv[])
{
    apr_status_t rv;
    apr_pool_t *mp;
    char *buf1;
    char *buf2;
    /* 框架开始 */
    rv = apr_initialize();
    if (rv != APR_SUCCESS) {
        assert(0);
        return -1;
    }
    /* 1.创建内存池 */
    apr_pool_create(&mp, NULL);
    /* 2.申请内存 */
    buf1 = apr_palloc(mp, 1024);
    buf2 = apr_palloc(mp, 1024);
    /* 3.对申请的内存块进行操作 */
  strcpy(buf1, "hello apr");
  strcpy(buf2, buf1);
  printf("buf1:%s, buf2:%s\n", buf1, buf2);
    /* 4.销毁内存池 */
    apr_pool_destroy(mp);
  /* 框架结束 */
    apr_terminate();
    return 0;
}

以上是一个libapr操作内存池的最简单的例子。上面的例子自然是没有内存泄漏的。

但是实际上的业务逻辑肯定不会像上面那么简单,而是形如下面的形式:

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <apr_general.h>
#include <apr_pools.h>
int got_exit = 0;
void term(int sig){
  got_exit = 1;
}
int main(int argc, const char *argv[])
{
    apr_status_t rv;
    apr_pool_t *mp;
    char *buf1;
    char *buf2;
    /* 框架开始 */
    rv = apr_initialize();
    if (rv != APR_SUCCESS) {
        assert(0);
        return -1;
    }
    /* 1.创建内存池 */
    apr_pool_create(&mp, NULL);
  signal(SIGTERM, term);
  signal(SIGINT, term);
    /* 2.业务逻辑 mainloop */
    while(got_exit){
      buf1 = apr_palloc(mp, 1024);
      buf2 = apr_palloc(mp, 1024);
    strcpy(buf1, "hello apr");
    strcpy(buf2, buf1);
    printf("buf1:%s, buf2:%s\n", buf1, buf2);
  }
    /* 3.销毁内存池 */
    apr_pool_destroy(mp);
  /* 框架结束 */
    apr_terminate();
    return 0;
}

上面的代码更符合实际的项目代码使用逻辑。我们在程序一开始申请了一个内存池,然后进入mainloop进行业务处理,这个mainloop通常是不会退出的,只有当接收到了退出信号的时候,才会退出,在退出的时候,会销毁内存池。

这段代码的可怕之处在于,你使用valgrind之类的内存检测工具,根本检测不出来它有内存泄漏!但是随着程序的不间断运行,你的内存又会被程序不断蚕食,直到系统的物理内存全部被吃光!

而当代码变得庞杂之后,这样的细微操作是最不会让人提防的,很可能因为一个很小的函数里使用了内存池,而这个函数要被频繁调用,从而导致内存不断增加。所以,检查不出来的内存泄漏,才是最可怕的内存泄漏。

那么,这种情况就不能避免吗?

当然是可以的。我们需要养成任何时候都要使用子内存池的习惯,这样就不会造成无谓的内存池无限扩容,上述代码修改如下:

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <apr_general.h>
#include <apr_pools.h>
int got_exit = 0;
void term(int sig){
  got_exit = 1;
}
int main(int argc, const char *argv[])
{
    apr_status_t rv;
    apr_pool_t *mp;
    char *buf1;
    char *buf2;
    /* 框架开始 */
    rv = apr_initialize();
    if (rv != APR_SUCCESS) {
        assert(0);
        return -1;
    }
    /* 1.创建内存池 */
    apr_pool_create(&mp, NULL);
  signal(SIGTERM, term);
  signal(SIGINT, term);
    /* 2.业务逻辑 mainloop */
    while(got_exit){
      apr_pool_t *child = NULL;
      apr_pool_create(&child, mp);
      buf1 = apr_palloc(child, 1024);
      buf2 = apr_palloc(child, 1024);
    strcpy(buf1, "hello apr");
    strcpy(buf2, buf1);
    printf("buf1:%s, buf2:%s\n", buf1, buf2);
    apr_pool_destroy(child);
  }
    /* 3.销毁内存池 */
    apr_pool_destroy(mp);
  /* 框架结束 */
    apr_terminate();
    return 0;
}

如上所示,我们每次都从父池子里申请一个子池子,然后使用完成后,调用apr_pool_destroy将子池子的内存归还给父池子,这样,只要每一轮调用中,没有特别出格的内存调用,父池子的大小会保持在一个相对稳定的大小,而不会无线扩张。


本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对C/C++课程感兴趣的读者,可以点击链接,查看详细的服务:C/C++Linux服务器开发/高级架构师

目录
相关文章
|
21天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
40 6
|
1月前
|
Web App开发 JavaScript 前端开发
使用 Chrome 浏览器的内存分析工具来检测 JavaScript 中的内存泄漏
【10月更文挑战第25天】利用 Chrome 浏览器的内存分析工具,可以较为准确地检测 JavaScript 中的内存泄漏问题,并帮助我们找出潜在的泄漏点,以便采取相应的解决措施。
162 9
|
5月前
|
存储 设计模式 监控
运用Unity Profiler定位内存泄漏并实施对象池管理优化内存使用
【7月更文第10天】在Unity游戏开发中,内存管理是至关重要的一个环节。内存泄漏不仅会导致游戏运行缓慢、卡顿,严重时甚至会引发崩溃。Unity Profiler作为一个强大的性能分析工具,能够帮助开发者深入理解应用程序的内存使用情况,从而定位并解决内存泄漏问题。同时,通过实施对象池管理策略,可以显著优化内存使用,提高游戏性能。本文将结合代码示例,详细介绍如何利用Unity Profiler定位内存泄漏,并实施对象池来优化内存使用。
305 0
|
5月前
|
存储 算法 Java
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
66 0
|
3月前
|
Java
在 ArkTS 中,如何有效地进行内存管理和避免内存泄漏?
【9月更文挑战第25天】在ArkTS中,有效进行内存管理并避免内存泄漏的方法包括:及时释放不再使用的资源,如关闭监听器和清理定时器;避免循环引用,通过弱引用打破循环;合理使用单例模式,确保单例对象正确释放;及时处理不再使用的页面和组件,在卸载时清理相关资源。
109 9
|
3月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。
|
3月前
|
Arthas 监控 Java
监控线程池的内存使用情况以预防内存泄漏
监控线程池的内存使用情况以预防内存泄漏
|
4月前
|
Linux 测试技术 C++
内存管理优化:内存泄漏检测与预防。
内存管理优化:内存泄漏检测与预防。
64 2
|
3月前
|
监控 算法 数据可视化
深入解析Android应用开发中的高效内存管理策略在移动应用开发领域,Android平台因其开放性和灵活性备受开发者青睐。然而,随之而来的是内存管理的复杂性,这对开发者提出了更高的要求。高效的内存管理不仅能够提升应用的性能,还能有效避免因内存泄漏导致的应用崩溃。本文将探讨Android应用开发中的内存管理问题,并提供一系列实用的优化策略,帮助开发者打造更稳定、更高效的应用。
在Android开发中,内存管理是一个绕不开的话题。良好的内存管理机制不仅可以提高应用的运行效率,还能有效预防内存泄漏和过度消耗,从而延长电池寿命并提升用户体验。本文从Android内存管理的基本原理出发,详细讨论了几种常见的内存管理技巧,包括内存泄漏的检测与修复、内存分配与回收的优化方法,以及如何通过合理的编程习惯减少内存开销。通过对这些内容的阐述,旨在为Android开发者提供一套系统化的内存优化指南,助力开发出更加流畅稳定的应用。
76 0
|
5月前
|
Arthas 存储 监控
JVM内存问题之JNI内存泄漏没有关联的异常类型吗
JVM内存问题之JNI内存泄漏没有关联的异常类型吗