6

I have been running overnight memory tests on an embedded Linux system. Using vmstat I have observed that the free memory steadily decreases over time. According to some smaps analysis in procfs, the heap of one process grows at roughly the same rate. I suspected a memory leak and found a few spots in the code where new and delete are regularly used. However, I did not see a new calls without matching delete calls.

I ran the memory test again and this morning cleared the memory caches with the following call

echo 3 > /proc/sys/vm/drop_caches

The free memory listed in vmstat went down to a value close to when the test was started.

Does the kernel regularly reclaim unused heap pages? If so, are there other times besides the one above that this is done? Probably when free memory gets below a certain threshold?

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
waffleman
  • 4,159
  • 10
  • 39
  • 63

4 Answers4

5

As others said, it is the process's duty to return memory to the kernel.

Usually there are 2 ways to allocate memory: if you malloc()/new a memory block above a certain size, the memory gets allocated from the OS via mmap() and eturned as soon as it is free. Smaller blocks are allocated by increasing the process's data area by shifting the sbrk border upwards. This memory is only freed if a block over a certain size is free at the end of that segment.

E.g.: (pseudo code, I don't know C++ very well)

a = new char[1000];
b = new char[1000];

Memory map:

---------------+---+---+
end of program | a | b |
---------------+---+---+

If you free a now, you have a hole in the middle. It is not freed because it cannot be freed. If you free b, the process's memory may or may not be reduced; the unused remainder is returned to the system.

A test with a program as simple as

#include <stdlib.h>

int main()
{
    char * a = malloc(100000);
    char * b = malloc(100000);
    char * c = malloc(100000);
    free(c);
    free(b);
    free(a);
}

leads to a strace output like

brk(0)                                  = 0x804b000
brk(0x8084000)                          = 0x8084000
brk(0x80b5000)                          = 0x80b5000
brk(0x809c000)                          = 0x809c000
brk(0x8084000)                          = 0x8084000
brk(0x806c000)                          = 0x806c000

is shows that the brk value is first increased (for malloc()) and then decreased again (for free()).

glglgl
  • 89,107
  • 13
  • 149
  • 217
  • which runtime library does that? – Tom Tanner Aug 29 '12 at 14:39
  • Will a program ever actually reduce its size via `sbrk`? I always assumed that since the chance of having a completely free block at the end is so low that it would never bother reducing the size. – edA-qa mort-ora-y Aug 29 '12 at 20:07
  • @edA-qamort-ora-y Yes. See my attached example. Test environment is Linux@i686. On x86-64, the same behaviour is observed. – glglgl Aug 29 '12 at 21:42
  • This runs completely counter to what I've been lead to believe. If the size of `a` in your first example more than two pages, then once it's freed the OS can reclaim the page(s) that were there. That's one of the reasons we have pages in the first place. Just because the virtual addresses are consecutive doesn't mean the physical parts must be. – Mooing Duck Aug 29 '12 at 21:50
  • Not the physical parts, right. This is completely free to the OS which to use there. But th point is: Once the application has allocated memory via `sbrk` and uses them, the OS must keep the data put there, either in RAM or in swap. Only if the program decreases `sbrk` again, the content may be dropped. All this only counts for small `malloc()` sizes; bigger ones are served via `mmap()`. The threshold is `M_MMAP_THRESHOLD`, one of the [`mallopt`s](http://man7.org/linux/man-pages/man3/mallopt.3.html). `M_TRIM_THRESHOLD` is interesting as well. – glglgl Aug 30 '12 at 04:52
  • @edA-qamort-ora-y I've never come across a runtime library that uses mmap for sufficiently large mallocs – Tom Tanner Aug 30 '12 at 08:07
  • @TomTanner The glibc does that if you `malloc()` more than `M_MMAP_THRESHOLD`. See the provided link in my comment above. – glglgl Aug 30 '12 at 08:10
1

The kernel will reclaim cached memory pages when it needs them, i.e. when the system would otherwise run out of memory. whether memory pages from the processes's heap (free store) are ever returned to the OS is at the discretion of the process's memory manager, in this case the new/delete implementation in the C++ library. This is a completely voluntary operation with which the kernel has nothing to do.

From the fact that drop_caches did the trick, you can infer that it was the kernel cache, not the process's heap, that was filling up memory. Use the free command to find out how much memory is actually available for application use, esp. the -/+ buffers/cache line it reports.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
1

Calling delete in your program causes memory to return to the memory manager that is part of your program runtime. In principle this could be written so as to return freed memory to the OS, but I would be surprised if it did. Rather the recycled memory is kept aside for subsequent calls to new.

Note that this is the virtual memory of your process; how much of it is actually residing in physical memory at any time during program execution depends on overall system load and is handled by the operating system.

Nicola Musatti
  • 17,834
  • 2
  • 46
  • 55
0

User calls to malloc and free (or new and delete), to the best of my knowledge never return no-longer used pages to the O/S. Instead, they just remember what memory has been freed so that if you do a malloc/new of a size that can be satisfied by previously freed memory, then it will use that, rather than going to the O/S and using sbrk to get more memory.

Thus this code:

for (;;)
{
    struct { char data[200 * 1024 * 1024] } HugeBuffer;
    HugeBuffer *buff = new HugeBuffer;
    delete buff;
}

Will allocate 200Mb once, and then just steadily use that memory forever. It will go to the O/S once on the original allocation, and then loop fiddling around in user space.

Tom Tanner
  • 9,244
  • 3
  • 33
  • 61