为什么mstats和malloc_zone_statistics在免费后没有显示恢复的内存?

时间:2014-09-01 16:33:32

标签: c++ c memory memory-management memory-leaks

我正在尝试编写一些单元测试来检查内存是否已被释放 - 以检查内存泄漏 - 在OS X(10.9 Mavericks)上。我正在尝试使用mstats()和malloc_zone_statistics()来发现我需要的信息。但似乎他们没有显示内存被释放(参见下面的示例输出......在调用free()后内存使用率不会改变)

我怀疑这与堆管理有关,而不是与这些功能有问题。我认为堆不会释放释放的内存,也许它可以重用它而不需要删除和添加块的开销。

  1. 有没有办法告诉堆释放释放的块?为了更积极或关闭优化?
  2. 我是不是错误地使用了mstats()或malloc_zone_statistics()?
  3. 更新:找到的解决方案......在底部提供......

    以下是我的测试程序的输出:

    === Initial conditions ===
    in use: 23584, allocated: 9437184, blocks: 320
    SimpleLeaker(19583,0x7fff7b2a2310) malloc: total: 9437184, used: 23584, free: 9413600
    === Before allocation ===
    in use: 23584, allocated: 9437184, blocks: 320
    SimpleLeaker(19583,0x7fff7b2a2310) malloc: total: 9437184, used: 23584, free: 9413600
    === After malloc ===
    in use: 33824, allocated: 9437184, blocks: 321
    SimpleLeaker(19583,0x7fff7b2a2310) malloc: total: 9437184, used: 33824, free: 9403360
    === After free ===
    in use: 33824, allocated: 9437184, blocks: 321
    SimpleLeaker(19583,0x7fff7b2a2310) malloc: total: 9437184, used: 33824, free: 9403360
    

    以下是该程序的C代码:

        #include <stdio.h>
        #include <unistd.h>
        #include <stdlib.h>
        #include <pthread.h>
        #include <mach/mach.h>
        #include <mach/task.h>
        #include <malloc/malloc.h>
        #include <errno.h>
    
    
        /** heapStats()
         * print the output from the mstats() function: total heap bytes, 
         * used bytes, and free bytes.
         */
        void heapStats()
        {
          struct mstats ms = mstats();
          malloc_printf(
            "total: %d, used: %d, free: %d\n",
            ms.bytes_total,
            ms.bytes_used,
            ms.bytes_free);
        }
    
        /* heapInUse()
         * Gather the heap usage metrics from each zone, using 
         * malloc_zone_statistics().
         */
        void heapInUse(
          size_t * bytesInUse,
          size_t * blocksInUse,
          size_t * sizeAllocated)
        {
          *bytesInUse = 0;
          *blocksInUse = 0;
          *sizeAllocated = 0;
          unsigned int i;
          vm_address_t * zones;
          unsigned int count;
          kern_return_t rc =
          malloc_get_all_zones(mach_task_self(), 0, &zones, &count);
          if (0 != rc)
          {
            fprintf(stderr, "rc was %d\n", rc);
          }
          for (i = 0; i < count; ++i)
          {
            malloc_zone_t * zone = (malloc_zone_t*)zones[i];
            char const * name = malloc_get_zone_name(zone);
            if (NULL == name)
            {
              continue;
            }
            malloc_statistics_t stats;
            stats.blocks_in_use = 0;
            stats.size_in_use = 0;
            stats.max_size_in_use = 0;
            stats.size_allocated = 0;
            malloc_zone_statistics(zone, &stats);
            *bytesInUse += stats.size_in_use;
            *blocksInUse += stats.blocks_in_use;
            *sizeAllocated += stats.size_allocated;
          }
        }
    
        /** main()
         * entry point
         */
        int main(int argc, const char * argv[])
        {
          char * buff = (char *)0;
          size_t bytesInUse = 0;
          size_t blocksInUse = 0;
          size_t sizeAllocated = 0;
          printf("=== Initial conditions ===\n");
          heapInUse(&bytesInUse, &blocksInUse, &sizeAllocated);
          printf(
            "in use: %zu, allocated: %zu, blocks: %zu\n",
            bytesInUse, sizeAllocated, blocksInUse);
          heapStats();
    
          printf("=== Before allocation ===\n");
          heapInUse(&bytesInUse, &blocksInUse, &sizeAllocated);
          printf(
            "in use: %zu, allocated: %zu, blocks: %zu\n",
            bytesInUse, sizeAllocated, blocksInUse);
          heapStats();
    
          // Allocate the buffer
          //
          buff = (char *)malloc(10000);
          printf("=== After malloc ===\n");
          heapInUse(&bytesInUse, &blocksInUse, &sizeAllocated);
          printf(
            "in use: %zu, allocated: %zu, blocks: %zu\n",
            bytesInUse, sizeAllocated, blocksInUse);
          heapStats();
    
          // Free the buffer
          //
          if (NULL != buff)
          {
            free(buff);
            buff = NULL;
          }
          printf("=== After free ===\n");
          heapInUse(&bytesInUse, &blocksInUse, &sizeAllocated);
          printf(
            "in use: %zu, allocated: %zu, blocks: %zu\n",
            bytesInUse, sizeAllocated, blocksInUse);
          heapStats();
    
          // Get out
          //
          return 0;
        }
    

    解决方案:感谢John Zwinck的回复,我仔细查看了malloc/malloc.h并找到了一个方法malloc_zone_pressure_relief(),我可以使用它来强制堆释放未使用的字节,以便我可以获得准确的指标。

    所以我添加了这个方法:

    void freeAsMuchAsPossible()
    {
      vm_address_t * zones;
      unsigned int count;
      unsigned int i;
    
      kern_return_t rc =
        malloc_get_all_zones(mach_task_self(), 0, &zones, &count);
      if (0 != rc)
      {
        fprintf(stderr, "rc was %d\n", rc);
      }
      for (i = 0; i < count; ++i)
      {
        malloc_zone_t * zone = (malloc_zone_t*)zones[i];
        char const * name = malloc_get_zone_name(zone);
        if (NULL == name)
        {
          continue;
        }
        malloc_zone_pressure_relief(zone, 0);
      }
    }
    

    并在每次调用heapInUse()之前调用它,如下所示:

    printf("=== Before allocation ===\n");
    freeAsMuchAsPossible();
    heapInUse(&bytesInUse, &blocksInUse, &sizeAllocated);
    printf(
      "in use: %zu, allocated: %zu, blocks: %zu\n",
      bytesInUse, sizeAllocated, blocksInUse);
    heapStats();
    

    现在我得到了预期和有用的结果,例如:

    === Initial conditions ===
    in use: 23584, allocated: 9437184, blocks: 4294966976
    SimpleLeaker(22142,0x7fff7b2a2310) malloc: total: 9437184, used: 23584, free: 9413600
    === Before allocation ===
    in use: 23584, allocated: 9437184, blocks: 4294966976
    SimpleLeaker(22142,0x7fff7b2a2310) malloc: total: 9437184, used: 23584, free: 9413600
    === After malloc ===
    in use: 33824, allocated: 9437184, blocks: 4294966967
    SimpleLeaker(22142,0x7fff7b2a2310) malloc: total: 9437184, used: 33824, free: 9403360
    === After free ===
    in use: 23584, allocated: 9437184, blocks: 4294966966
    SimpleLeaker(22142,0x7fff7b2a2310) malloc: total: 9437184, used: 23584, free: 9413600
    

    使用这种技术,我可以编写检查内存泄漏的单元测试。很好。

3 个答案:

答案 0 :(得分:2)

在OS X上,从10.7开始,我们可以使用malloc_zone_pressure_relief()强制堆释放未使用的字节。我已用解决方案更新了我的问题。

感谢John Zwinck的回复,促使我再次密切关注malloc.h,在那里我发现了这种方法。

答案 1 :(得分:1)

你可能是对的:每次调用free()时,分配器都不会将内存释放回操作系统。但您可以使用mallopt()更改此设置。尝试将M_MMAP_THRESHOLD设置为0.这将使每个单独的分配独立。您可能不希望将其用于生产,但是对于测试它可能会对您有所帮助。

答案 2 :(得分:0)

我建议您查看OSX leaks工具以查找程序中的内存泄漏。可在此处找到介绍性文章:leaks documentation at Mac Developer Library。您可以man leaks了解更多详情。另外,仅针对您的问题1,您可以使用libgmalloc。请man libgmalloc了解详情。此外,这些手册页还包含指向malloc_history等其他工具的链接,您可能会发现这些工具对您的目的有用和/或更快。