Wrestling the Garbage Collector.. And loosing..

Steven Schveighoffer schveiguy at yahoo.com
Wed Apr 30 11:13:03 PDT 2008


"Simen Haugen" wrote
> Hi.
>
> I'm having a problem with my application using extremly much memory, and 
> the only way I'm able to free it is by deleting objects explicitly. I 
> guess I'm not understanding the GC right, but I thought that it should 
> free all objects without references to it when it's collecting, but my 
> testing below shows I'm doing something wrong.
>
> // Test 1
> auto str = new char[512];
> delete str;
> str = new char[512]; // OK. No new allocation
>
> // Test 2
> auto str = new char[512];
> str = new char[512]; // New memory allocated. GC isn't run. Guess it's 
> expected behavior.
>
> // Test 3
> auto str = new char[512];
> str = null;
> str = new char[512]; // New memory allocated. GC isn't run. Guess it's 
> expected behavior.
>
> // Test 4
> auto str = new char[512];
> str = null;
> GC.collect;
> str = new char[512]; // New memory allocated. Why?
>
> // Test 5
> auto str = new char[512];
> str.length = 0;
> GC.collect;
> str = new char[512]; // New memory allocated. Why?
>
> I would have thought Test 4 and 5 shouldn't allocate more memory either as 
> I remove the reference (or zero length) and run the GC, but it does.
> I would also think Test 2 and 3 should make the GC reclaim memory when it 
> is run.

The GC is based on pointers.  So if a thread stack has pointers to any GC 
data, it will not collect that data.

In test 5, the str array has length 0, but the array ptr is still a pointer 
pointing to the beginning of that allocated data.  I would not expect Test 5 
to collect the original data.

In Test 4, this one may be due to the pointer still being in the registers. 
You might want to try executing some other functions or statements before 
calling GC.collect, but even then, it might not clear out the particular 
register that held the pointer.

There are other causes to the GC not reclaiming memory.  The GC only 
allocates memory based on bin sizes, in 16, 32, ..., 4096+ sizes.  So if you 
allocate just over one of those sizes, then you are still allocating the 
whole bin size.  Do this alot, and you will see a huge waste of memory. 
This is a tradeoff of minimal memory size vs. efficiency to find an empty 
memory location.

Another cause of lost memory is appending to an array.  The GC never 
releases pages back to the OS (this might have been fixed in Tango).  If you 
append a byte to an array over and over again, for instance, what happens?

// array.length == 16
array ~= 'a';

This needs to move array into another bin, but what happens to the original 
bin?  It's marked as free, but the memory is still held by the GC for the 
next <=16 byte allocation.

What ends up happening is as you append more and more data, the number of 
pages allocated grows a lot.  Even though this memory is marked as 'free', 
it's still being used by your application.

-Steve 




More information about the Digitalmars-d-learn mailing list