0

I have some troubles with memory leak. Here's my test code:

// Create the object
string book = "This is a book";
Debug.WriteLine(book);
// Set weak reference
WeakReference wr = new WeakReference(book);
// Remove any reference to the book by making it null
book = null;
if (wr.IsAlive)
{
     Debug.WriteLine("Book is alive");
     var book2 = wr.Target as string;
     Debug.WriteLine("again -> " + book2);
     book2 = null;
}
else
     Debug.WriteLine("Book is dead");
// Lets see what happens after GC
GC.Collect();
GC.WaitForPendingFinalizers();
// Should not be alive
if (wr.IsAlive)
    Debug.WriteLine("again -> Book is alive");
else
    Debug.WriteLine("again -> Book is dead");

And output is:

This is a book
Book is alive
again -> This is a book
again -> Book is alive

So, why "wr" still alive after call GC.Collect()? Anything wrong with GC? I'm run on WP8 & WP8.1 preview. Can you help me.

bl4ck
  • 1

3 Answers3

2

You have a reference to a string, which since it is a constant, is probably interned and will never be collected:

string strBook = wr.Target as string;
if(strBook  != null) {
    Console.WriteLine("again -> Book is alive");
    if(string.IsInterned(strBook) != null)
        Debug.WriteLine("Because this string is interned");
}
else Console.WriteLine("again -> Book is dead");
DmitryG
  • 17,677
  • 1
  • 30
  • 53
  • There is any documentation about this behavior? – Alberto Jul 23 '14 at 09:19
  • @Alberto I believe you can start from reading of remarks section of the [String.IsInterned](http://msdn.microsoft.com/en-us/library/system.string.isinterned(v=vs.110).aspx) method reference-article – DmitryG Jul 23 '14 at 10:34
  • @DmitryG but i set it null before collected: "book = null;" – bl4ck Jul 24 '14 at 02:50
  • @bl4ck Yes, you've removed this concrete GC-root reference but all the interned strings are GC-rooted within special "intern-pool" and that fact prevents all of these strings from collecting. Yet another note - all the strings, known at compilation time, will be potentially interned... – DmitryG Jul 24 '14 at 08:29
1

Perhaps it's because string literals are stored in an internal dictionary to prevent duplication? See here for details: String interning and String.Empty

Try allocating a POCO class (e.g. a StringBuilder) instead of a string literal for your test.

dbc
  • 104,963
  • 20
  • 228
  • 340
0

You should never rely on GC.Collect() to reclaim the memory. .NET is a managed environment, you give control of memory management to the runtime in exchange for not having to write code that manages it directly, with all the considerations that brings.

Calling GC.Collect() simply advises the runtime that it may wish to run a collection cycle when it next gets the opportunity - it is not going to abort some complex computation that it is in the middle of simply to do a garbage collection.

Even then, collection will only occur on objects that are no longer reachable by any code, and then there are three tiers of cache within the garbage collector, so expecting objects to instantly disappear when GC.Collect() is called is the wrong way to go about things.

Structuring your program correctly should remove the need to rely on GC.Collect(). If instances are only available where they are required, you will not have problems with other areas of the program interfering with them. Finally, had the wr object been garbage collected, you call to .IsAlive() would have produced an unhandled NullReferenceException.

dyson
  • 866
  • 6
  • 12
  • Do note the [MSDN doc](http://msdn.microsoft.com/en-us/library/xe0c2357(v=vs.110).aspx) indicates `GC.Collect` forces an **immediate** garbage collection. That's not just an indication. – ken2k Jul 23 '14 at 08:39
  • There's a similar discussion to this in [this](http://stackoverflow.com/questions/4673237/net-garbage-collection-behavior-with-datatable-obj) question and its answers and comments. – dyson Jul 23 '14 at 08:45
  • 1
    The top-voted answer of the question you linked is wrong unless someone finds accurate documentation about this that proves otherwise. From what the spec says, it forces an immediate collection. – ken2k Jul 23 '14 at 08:54
  • Clearly, either the documentation is wrong, or the implementation of the method in the .NET Framework, as there's no instant collection in the example above. Garbage collection runs on a normal priority thread, so it can never be guaranteed to run instantly. – dyson Jul 23 '14 at 09:02
  • This answer is not the reason the behaviour reported is happening. "Try allocating a POCO class (e.g. a StringBuilder) instead of a string literal for your test." <--- try this – Mitch Wheat Jul 23 '14 at 09:16