Patterns for reducing memory usage
Memory problems happen when you application use more memory that you would like. It isn’t necessarily paging or causing OutOfMemory, but it is using enough memory to generate complaints. The most common cases for memory issues are:
- Memory leaks
- Garbage spewers
- In memory nuts
- Framework bugs
Let me take each of them in turn.
Memory leaks in a managed language are almost always related to dangling references, such as in a cache with no expiration or events where you never unsubscribe. Those are usually nasty to figure out, because tracking down what is holding the memory can be unpleasant. But, by the same token, it is also fairly straightforward to do so.
Garbage spewers are pieces of code that allocate a lot of memory that will have to be freed soon afterward. A common case of that is:
public string Concat(string[] items) { string result = ""; foreach(var item in items) results += item; return result; }
This is going to allocate a lot of memory, which will have to be freed soon after. This will get cleaned up eventually, but it will put a lot of pressure on the GC first, will cause the application to consume more memory and in general won’t play nice with others. While the code above is the simplest way to explain this, it is fairly common in ways that are harder to detect, a common case would be to load a DTO from the database, convert that to an entity and convert that to a view model. Along the way, you are going to consume a lot of memory for doing pretty much the same thing.
Now the caveat here is that most objects are actually small, so you don’t really notice that, but if you are working with large objects, or a lot of them, this is something that is going to hit you.
In memory nuts refer to a common problem, you simply put your entire dataset in memory, and commonly refer to it by direct model traversal. When your dataset becomes too big, however… well, that is the point where the pain is really going to hit you. Usually, fixing this is a costly process, because your code assumes that the entire thing is in memory. Even if you can easily save it to persistent storage, fixing all the places where the code assumes that everything is just a pointer reference away is a big problem.
Framework bugs are my least favorite, it is when you run into cases where the framework just won’t release memory. Most often, this is because you are doing something wrong, but occasionally you will hit the real framework bug, and tracking that down is a pure PITA.
In all cases, you need to set up some goals, what is acceptable memory usage, in what scenarios, over what time frame, etc. Then build test scenarios that are repeatable and try each of your improvements out. Do not try to implement too much upfront, that way lies the road to madness.
Comments
Really interesting article.
But, I did some test and found that Code sample above posted doesn't create garbage in the foreach loop.
string gets finalized only when we return from the function.
Please help with this example.
Pet,
String is immutable, as such, a new string is generated every time we concat the string.
Garbage spewers (quick fix)
public string Concat(string[] items)
Fero,
I am not really worried about that particular example, I am talking about the general case.
Minor code fix: "results" should be "result" in your garbage spewer example. Threw me off for a second.
Another fun one is the scenario with small managed objects managing large unmanaged objects. COM interop is the primary example. When you have large COM objects being used from managed code, the garbage collector isn't aware of that memory usage and doesn't trigger garbage collection.
Kevin,
That is why you have GC.AddMemoryPressure
Sure, but AddMemoryPressure assumes a priori knowledge of the size of the objects. Not very practical in the general case. It's one of the nasty flaws in the whole COM interop story.
Comment preview