I'm trying to write some unit tests that check whether memory has been freed -- to check for memory leaks -- on OS X (10.9 Mavericks). I'm trying to use mstats() and malloc_zone_statistics() to discover the information I need. But it seems that they don't show memory being released (see example output below ... memory usage does not change after free() is called)
I suspect this has more to do with the heap management than with a problem with those functions. I think the heap is not releasing freed memory, perhaps so it can reuse it without the overhead of removing and adding blocks.
- Is there some way to tell the heap to release freed blocks? To be more aggressive or turn off optimizations?
- Am I just using mstats() or malloc_zone_statistics() incorrectly?
Update: solution found ... provided at the bottom ...
Here is the output from my test program:
=== 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
And here is the C code for the program:
#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;
}
Solution: Thanks to the response from John Zwinck I took a closer look at malloc/malloc.h
and found a method malloc_zone_pressure_relief()
that I can use to force the heap to free unused bytes so that I can get accurate metrics.
So I added this method:
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);
}
}
and called it before every call to heapInUse()
, like so:
printf("=== Before allocation ===\n");
freeAsMuchAsPossible();
heapInUse(&bytesInUse, &blocksInUse, &sizeAllocated);
printf(
"in use: %zu, allocated: %zu, blocks: %zu\n",
bytesInUse, sizeAllocated, blocksInUse);
heapStats();
And now I get expected and useful results, for example:
=== 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
With this technique I can write unit tests that check for memory leaks. Very nice.