If broken it is, fix it you should

Using the powers of the debugger to solve the problems of the world - and a bag of chips    by Tess Ferrandez, ASP.NET Escalation Engineer (Microsoft)

ASP.NET Quiz - Does Page.Cache leak memory?

ASP.NET Quiz - Does Page.Cache leak memory?

  • Comments 30

Yesterday I received an email from a blog reader about caching and memory leaks…

Paraphrasing freely it went something like this:

We use Page.Cache to store temporary data, but we have recently discovered that it causes high memory consumption. The bad thing is that the memory never goes down even though the cache items have expired, and we suspect a possible memory leak in its implementation.

We have created this simple page:

protected void Page_Load(object sender, EventArgs e){
       
this.Page.Cache.Add(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), null, DateTime.MaxValue, TimeSpan.FromMinutes(1), CacheItemPriority.NotRemovable, new 
        CacheItemRemovedCallback(this.OnRemoved));
}

public void OnRemoved(string key, object value, CacheItemRemovedReason r)
{
          value = null;
}

Which we stress with ACT (Application Center Test) for 5 minutes. Memory usage peaks at 450 MB, and after some time it decreases to 253 MB, but never goes down completely even though we waited for 10 minutes after the stress test. Our expectation is that the memory should go down to about 50-60 MB.

The question is does the above scenario fall into the category of memory leaks?

Since I get quite a bit of emails I don’t have time to answer them privately, so if you email me and I don’t answer, and you really need urgent help, please contact Microsoft Support or comment on the blog.

However, if I think the question is common enough that the answer would benefit more people (which may or may not be true, but I’ll reserve the right to make that call:)) I’ll answer with a blog post like in this case.

I will never give out any names of people who email me or include any sensitive data on the blog such as machine names, custom classes or anything else that can identify the sender, but if you email me and do not want me to paraphrase your question on the blog, please let me know.

Back to the question... The short answer to the question is No (or at least not that I currently know of), however with a few minor changes to the code and the stress test you will see memory go down significantly.

So then why does memory not go down using this sample? And also, the test (when I ran it on my machine) generated a total of 15 161 requests, but the memory usage seems to indicate that a lot more than just the actual cached items stick around, why?

I will answer the question more thoroughly but before I do (later this week) I wanted to give all of you a chance to dissect this and see what things you come up with because I think it can bring up some nice discussion items, so feel free to comment away. 

Hint: With a subtle (but not too obvious change) I can get the memory usage to peak at a maximum of 48 MB on my machine, now that is a huge difference:).

The questions we want answered are:

  1. Why does memory usage not go down to about 50-60 MB?
  2. Why does it seem like we are using more memory than the actual items we store in cache?
  3. What makes the stress test “invalid”?
  4. What is the difference between Page.Cache and Cache?

I will summarize the comments and add any additional things I can think of on Thursday or Friday.   

Laters,





  • Tess, I look forward to your summarization/comments!!!!  Without a doubt, your blog is the single most useful source of information for people working with .net applications...   particularly those with performance issues and/or bugs in their code.  And how many apps in production don't have those issues ??? ;-)

    Hope you can lead a session in the New England area or do some training in Charlotte...

    ...  from your #1 fan in Rhode Island...
  • Thanks much Jim,

    I probably won't doing any work in Charlotte or New England for a while, but maybe you should make a business case for training in Sweden:)
  • OnRemoved is an instance member which will force the Page object to remain reachable and not eligible for Garbage Collection.
  • My first instinct is the fact that every new request generates a brand new Cache instance (via the guid)... versus a more real world scenario where between 99% - 20% of the requests wouldn't allocate a new slot.

    So in theory 256MB is the timeout of the set of data that could be created in that space of time...  Your machine going to 50MB would be influenced to be direct relationship to how many times you can run a request.

    Thats my first instinct...
  • Nice comments both of them, the OnRemoved that Matt talks about is the subtle change I made (which alone made it go down to about 50 MB) so spot on Matt.  Eric's comment is also very valid. I didn't change the data I cached in my test, but in a real-life scenario that would be appropriate.

    So awesome stuff already, keep them comming...
  • This one seems really too simple - Why are we adding to the cache on EVERY page load?  Why not on just the initial load?  

  • I should clarify, the sample is used to simulate caching many items and is supposed to be used to determine if the cache is leaking, i.e. why cached objects are not released from memory after the cache item is expired.  

    In the real-world scenario the caching would only occur under certain conditions.  
  • Sorry I cant help you..?
  • I'm with Matt on this one.
  • A little off topic but is this scenerio also valid for the regular Cache object? i.e.

    Cache["something"] = "some value"

    When does this get clear? I have seen the behavior that the aspnet_wp.exe gets bigger upto 150MB (on not very busy site) and stays at that size.

    Any clue?

    Thanks,
    Rachit
  • My guess is that the memory usage did not drop because the cache objects where still alive and could not be removed from the cache to free up memory since they where marked as NotRemovable the last time the garbage collector ran.

    Even if one would wait 5 min after the stress test to be sure that all the timeouts expires the memory would not drop because nothing is causing the GC to run again. I believe Tess has blogged about having some sort of a slow down period in the end of the stress test to avoid stuff like that.

    But hey, I could be totaly off
  • I am impressed, by all the comments on here.  I must say that Kumaresh comment made me laugh:)

    Petros, you are completely correct about the cool down period, GC's only happen during allocation (unless someone called GC.Collect).   Now here is a tough one, and I have to say, I am not completely clear on this one... how does a good cool-down period look?  I generally either do small collections allowing me eventually to cause a gen 2 GC, or call GC.Collect explicitly to with a GC.WaitForPendingFinalizers in order to make sure that as much as possible of what could be collected has been collected.  Does anyone have any other nice tips and tricks for cool-down periods? I am all ears...

    The NotRemovable, is not non-removable forever, only until the cache items expire, but that brings up an interesting question i think... when do these cache items expire using the code above?

    Rachit,  I think the summary post will take care of answering your comment, but i want to wait a little with that... here is an unanswered question though, relating to your comment... what is the difference between Page.Cache and Cache?

    Btw, feel free to add other questions/comments about caching too even if they are not completely related to the question at hand...  
  • But if non-removable is used, IIRC asp.net cannot remove that item due to memory pressure at all.  If that's the case then by using this setting don't you turn cache into a session like system that concievably has more memory problems than session itself (assuming its per-user)?  

    I've always thought that you should always let asp.net handle the cache stuff, freeing memory etc.. then build the logic into your app to repopulate the cache (or retrieve the data from elsewhere) if it can't find it in the cache.   The idea being if you use the cache and let asp.net free memory then its theoretically hard to run out of memory (well harder).  But if you use the cache and disallow asp.net memory management (for cached items) you're basically locking that memory away and making it unavailable to the rest of the app.
  • I would not use the cache to store session data since the cache lives in the scope of the application.

    I believe that there is just one cache per application and that Page.Cache and Cache points to that same instance of System.Web.Caching.Cache which is created when the application is started

    I have to disagree with Scott. I would say that it is ok to use the NotRemovable priority. After all I am caching objects and not just making a weak reference to them telling the GC it ok to collect it in shortage of memory. Hopefully I know what I am doing :)
  • Isn't the Page.Cache an application scope type of storage while the HttpContext.Current.Cache is per worker process?
Page 1 of 2 (30 items) 12
Leave a Comment
  • Please add 4 and 5 and type the answer here:
  • Post