Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linux Allocator Does Not Release Small Chunks of Memory

The Linux glibc allocator seems to be behaving weirdly. Hopefully, someone can shed some light on this. Here is the source file that I have:

first.cpp:

#include <unistd.h>
#include <stdlib.h>
#include <list>
#include <vector>

int main() {

  std::list<char*> ptrs;
  for(size_t i = 0; i < 50000; ++i) {
    ptrs.push_back( new char[1024] );
  }
  for(size_t i = 0; i < 50000; ++i) {
    delete[] ptrs.back();
    ptrs.pop_back();
  }

  ptrs.clear();

  sleep(100);

  return 0;
}

second.cpp:

#include <unistd.h>
#include <stdlib.h>
#include <list>

int main() {

  char** ptrs = new char*[50000];
  for(size_t i = 0; i < 50000; ++i) {
    ptrs[i] = new char[1024];
  }
  for(size_t i = 0; i < 50000; ++i) {
    delete[] ptrs[i];
  }
  delete[] ptrs;

  sleep(100);

  return 0;
}

I compile both:

$ g++ -o first first.cpp
$ g++ -o second second.cpp

I run first, and after it's sleeping, I see the resident memory size:

When I compile first.cpp, and run it, I look at memory with ps:

$ ./first&
$ ps aux | grep first
davidw    9393  1.3  0.3  64344 53016 pts/4    S    23:37   0:00 ./first


$ ./second&
$ ps aux | grep second
davidw    9404  1.0  0.0  12068  1024 pts/4    S    23:38   0:00 ./second

Notice the resident memory size. In first, the resident memory size is 53016k. in second, it is 1024k. First never released the allocations back to the kernel for some reason or another.

Why does the first program not relinquish memory to the kernel, but the second program does? I understand that the first program uses a linked list and the linked list probably allocates some nodes on the same page as the data we're freeing. However, those nodes should be freed, as we're popping those nodes off, then clearing the linked list. If you run either of these programs through valgrind, it comes back with no memory leaks. What is probably happening is memory gets fragmented in first.cpp that doesn't in second.cpp. However, if all memory on a page is freed, how does that page not get relinquished back to the kernel? What does it take for memory to get relinquished back to the kernel? How can I modify first.cpp (continuing to put the char*'s in a list) so that the memory is relinquished to the kernel.

like image 433
user1418199 Avatar asked Jun 08 '12 06:06

user1418199


People also ask

How Linux allocate memory?

Linux provides a variety of APIs for memory allocation. You can allocate small chunks using kmalloc or kmem_cache_alloc families, large virtually contiguous areas using vmalloc and its derivatives, or you can directly request pages from the page allocator with alloc_pages .

How to allocate memory in kernel space?

malloc is a user space library function. You cannot use it in kernel space. There is a function called kmalloc() which is used to allocate memory in kernel space. You can use vmalloc() also.

What is kernel memory allocation?

Kernel memory, however, is often allocated from a free-memory pool different from the list used to satisfy ordinary user-mode processes. There are two primary reasons for this: 1. The kernel requests memory for data structures of varying sizes, some of which are less than a page in size.


3 Answers

This behaviour is intentional, there is a tunable threshold that glibc uses to decide whether to actually return memory to the system or whether to cache it for later reuse. In your first program you make lots of small allocations with each push_back and those small allocations are not a contiguous block and are presumably below the threshold, so don't get returned to the OS.

Calling malloc_trim(0) after clearing the list should cause glibc to immediately return the top-most region of free memory to the system (requiring a sbrk system call next time memory is needed.)

If you really need to override the default behaviour (which I wouldn't recommend unless profiling reveals it actually helps) then you should probably use strace and/or experiment with mallinfo to see what's actually happening in your program, and maybe using mallopt to adjust the threshold for returning memory to the system.

like image 113
Jonathan Wakely Avatar answered Oct 07 '22 14:10

Jonathan Wakely


It keeps the smaller chunks available in case you request them again. It is a simple caching optimization, and not behaviour to be concerned about.

like image 26
Puppy Avatar answered Oct 07 '22 15:10

Puppy


Typically, the memory allocated by new will only be returned to the system when the process terminates. In the second case, I suspect that libc is using a special allocator for very large continuous blocks, which does return it, but I'd be very surprised if any of your new char[1024] were returned, and on many Unices, even the large block won't be returned.

like image 24
James Kanze Avatar answered Oct 07 '22 14:10

James Kanze