Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.Net 4 MemoryCache Leaks with Concurrent Garbage Collection

I'm using the new MemoryCache in .Net 4, with a max cache size limit in MB (I've tested it set between 10 and 200MB, on systems with between 1.75 and 8GB of memory). I don't set any time based expiration on the objects, as I'm using the cache simply as a high performance drive, and as long as there is space, I want it used. To my surprise, the cache refused to evict any objects, to the point that I would get SystemOutOfMemory exceptions.

I fired up perfmon, wired up my application to .Net CLR Memory\#Bytes In All Heaps, .Net Memory Cache 4.0, and Process\Private Bytes -- indeed, the memory consumption was out of control, and no cache trims were being registered.

Did some googling and stackoverflowing, downloaded and attached the CLRProfiler, and wham: evictions everywhere! The memory stayed within reasonable bounds based upon the memory size limit I had set. Ran it in debug mode again, no evictions. CLRProfiler again, evictions.

I finally noticed that the profiler forced the application to run without concurrent garbage collection (also see useful SO Concurrent Garbage Collection Question). I turned it off in my app.config, and, sure enough, evictions!

This seems like at best an outrageous lack of documentation to not say: this only works with non-concurrent garbage collection -- though I image since its ported from ASP.NET, they may not have had to worry about concurrent garbage collection.

So has anyone else seen this? I'd love to get some other experiences out there, and maybe some more educated insights.


Update 1

I've reproduced the issue within a single method: it seems that the cache must be written to in parallel for the cache evictions not to fire (in concurrent garbage collection mode). If there is some interest, I'll upload the test code to a public repo. I'm definitely getting toward the deep end of the the CLR/GC/MemoryCache pool, and I think I forgot my floaties...


Update 2

I published test code on CodePlex to reproduce the issue. Also, possibly of interest, the original production code runs in Azure, as a Worker Role. Interesting, changing the GC concurrency setting in the role's app.config has no effect. Possibly Azure overrides GC settings much like ASP.NET? Further, running the test code under WPF vs a Console application will produce slightly different eviction results.

like image 519
David Faivre Avatar asked Apr 13 '11 20:04

David Faivre


People also ask

What is concurrent garbage collection?

Concurrent garbage collection enables interactive applications to be more responsive by minimizing pauses for a collection. Managed threads can continue to run most of the time while the concurrent garbage collection thread is running. This design results in shorter pauses while a garbage collection is occurring.

How do you prevent memory leaks in .NET applications?

Start the debug diagnostic tool and select 'Memory and handle leak' and click next. Select the process in which you want to detect memory leak. Finally select 'Activate the rule now'. Now let the application run and 'Debugdiag' tool will run at the backend monitoring memory issues.

Can a memory leak happen in managed languages like C#?

A simple answer is that classic memory leaks are impossible in GC environments, as classically a memory leak is leaked because, as an unreferenced block theres no way for the software to find it to clean it up. On the other hand, a memory leak is any situation where the memory usage of a program has unbounded growth.

Is memory leak possible in C#?

If you have many of those your application takes a lot of memory and eventually you run out of it. In C#, these are some common memory leaks: Not removing event listeners. Any event listener that is created with an anonymous method or lambda expression that references an outside object will keep those objects alive.


2 Answers

You can "force" a garbage collection right after the problematic method and see if the problem reproduces executing:

System.Threading.Thread.Sleep(200); GC.Collect(); GC.WaitForPendingFinalizers(); 

right at the end of the method (make sure that you free any handles to reference objects and null them out). If this prevents memory leakage, and then yes, there may be a runtime bug.

like image 60
Teoman Soygul Avatar answered Oct 12 '22 15:10

Teoman Soygul


Stop-the-world garbage collection is based on determining whether a strong live reference to an object exists at the moment the world is stopped. Concurrent garbage collection usually determines whether a strong live reference to an object has existed since some particular time in the past. My conjecture would be that many strong references to objects held in WeakReferences are being individually created and discarded. If a stop-the-world garbage collector fires between the time a particular object is created and the time it's discarded, that particular object will be kept alive, but previously-discarded objects will not. By contrast, a concurrent garbage collector may not detect that all strong references an object have been discarded until a certain amount of time goes by without any strong references to that object being created.

I've sometimes wished that .net would offer something between a strong reference and a weak one, which would prevent an object from being wiped from memory, but would not protect it from being finalized or having weak WeakReferences to it invalidated. Such references would slightly complicate the GC process, requiring every object to have separate flags indicating whether strong and quasi-weak references exist to it, and whether it has been scanned for both strong and quasi-weak references, but such a feature could be helpful in many "weak event" scenarios.

like image 38
supercat Avatar answered Oct 12 '22 13:10

supercat