I don't have exact same linux (the box I'm trying this on still has glibc 2.12), but I see this too and this is actually a good learning opportunity for the Linux malloc
basic info: Linux has two heap memory allocation strategies: sbrk and mmap.
sbrk (system break) is a single contiguous virtual memory range. malloc can ask the OS to grow or shrink sbrk (in one direction).
mmap (memory mapping) is a separate contiguous virtual memory range. malloc can ask the OS to create and destroy (not resize) multiple mmap regions
The glibc malloc uses both, choosing based on some heuristics
tools in use: strace, you can also call mallinfo() if you feel like digging deeper into sbrk contents or /proc/pid to follow the OS side of things
Before Step-1:
malloc asks for sbrk and grows it by 0x21000 (135k)
brk(0) = 0x12c0000
brk(0x12e1000) = 0x12e1000
|
then malloc allocates 20 independent mmaps 1052672 bytes each
mmap(NULL, 1052672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f706b2b0000
...
mmap(NULL, 1052672, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f7069167000
|
and one more mmap 4096 bytes in size
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f706b3ca000
|
After this you print "Step-1" and sleep
Before Step-2:
malloc releases the 20 mmaps to the OS - this is why ps output shows RSS reduced
munmap(0x7f706b2b0000, 1052672) = 0
...
munmap(0x7f7069167000, 1052672) = 0
|
After this you print "Step-2: deleting done"
Then malloc changes strategies and requests memory for your 10 objects by growing the system break 10 times by 0x101000 (1052672 bytes) each time
brk(0x13e2000) = 0x13e2000
brk(0x14e3000) = 0x14e3000
brk(0x15e4000) = 0x15e4000
brk(0x16e5000) = 0x16e5000
brk(0x17e6000) = 0x17e6000
brk(0x18e7000) = 0x18e7000
brk(0x19e8000) = 0x19e8000
brk(0x1ae9000) = 0x1ae9000
brk(0x1bea000) = 0x1bea000
brk(0x1ceb000) = 0x1ceb000
|
Here you print "Step-3: creating done:"
After that, malloc works within the system break, with no communication to the OS, as you print "Step-4: deleting done" and "Step-5: deleting done" and "Step-6: deleting array done"
You end up with an empty sbrk. It's great if you plan to malloc again, but not if you don't.
Linux has a function for that:
malloc_trim(size_t pad);
If possible, gives memory back to the system (via negative
arguments to sbrk) if there is unused memory at the `high' end of
the malloc pool. You can call this after freeing large blocks of
memory to potentially reduce the system-level memory requiremen
|
let's call it (note you can also make it happen automatically with
mallopt()
):
1 2 3
|
pt2 = NULL;
malloc_trim(0);
std::cout << "Step-6: deleting array done" << std::endl;
|
now malloc makes a brk call before printing Step-6, undoing everything almost all the way back to the break before Step-1:
brk(0x12e1000) = 0x12e1000
brk(0x12c1000) = 0x12c1000
|
and RSS drops in my ps output
So, why did malloc decide to switch from mmap to sbrk? Linux is open source, looking through
https://github.com/lattera/glibc/blob/master/malloc/malloc.c I see
1 2 3
|
/* the mmap_threshold is dynamic, until the user sets
it manually, at which point we need to disable any
dynamic behavior. */
|
so your other option is to set that threshold by calling
mallopt()
(this is what disables automatic behavior).. or learn the logic and adapt to it. It being Linux, you can also load different mallocs.