How to identify and prevent memory leak no.NET?

I know that CLR has tools like garbage collection, responsible for memory management in .NET applications .one of the functions of the garbage collector, GC, is to avoid unnecessary memory consumption.

Even with the tool, for several reasons, the memory leak arises. It is unnecessarily allocated memory that over time can cause a OutOfMemoryException. The bigger problem is that this exception will not be fired where the memory leak is, but in the line of code that the program does not have enough memory to be able to allocate.

How to identify these leaks in the .NET application and what practices to adopt to prevent them?

Author: Vinicius Brasil, 2017-02-25

1 answers

What is memory leak?

We can say that memory leakage occurs every time there is an allocated memory longer than necessary. This often occurs because the code (or more precisely the programmer who wrote it) is lost and forgets to free up memory after it is no longer needed. There is a waste of memory, and the accumulation of waste can lead to the impossibility of execution because of so much memory that the application is occupy.

Actually has an even bigger problem which is to release memory prematurely that still needs to be accessed, but the subject here is another.

With a more commum garbage collector it is normal for objects not to be released soon after it is no longer needed. The collection takes place from time to time according to the need for memory. There is a certain waste, but it is by design. When using something like a garbage collector we give up having the largest possible memory savings. But we can not give up having memory no longer needed to go without release.

When there is a GC the definition of memory leak varies a little. Only objects that survive a collection even if they are no longer needed in fact are considered leakage. Before having a collection it is considered normal that the object is there even if it is not needed anymore.

If you have a lot of leakage, sooner or later you will receive a OutOfMemoryException.

In applications that run for a short time may have leaks that are never noticed, some innocuous, others not so much.

Let it be clear that there are no leaks that are allocated in the stack, this memory has automatic management.

Why does the GC not collect something that is already unnecessary?

It's not because he's bad. It is because the code has some problem and keeps at least one reference to an object that in fact it is no longer necessary, but what the code is saying is that it is necessary. This can occur for some reasons that we will see below.

Do I have to restart the computer to free up the leaked memory?

No. At the end of the application the operating system will release all memory linked to it, even if your application does not. He does it perfect and complete every time, not least because it was he who gave memory to his application, he knows everything that was given to take from back.

Managed memory

In fact .NET works with a memory manager, which contrary to the name cares more about the way of allocation than the release of memory. This greatly facilitates the development of the application since it is a huge inconvenience to manage the memory correctly, especially when we use more advanced features of the language and library.

.NET has a very smart GC and allows allocations at a cost close to that of allocation in stack (which is absurdly fast) and has no release cost. But it has the collection breaks. In addition it keeps the memory without fragments, which helps maintain reference locale that provides better performance.

The problem is that many programmers think that because of this we do not need to worry about anything in memory usage. We have several questions here in SOpt where users report memory leak.

Memory no managed

Not all memory allocated in your application is managed by the garbage collector. You can use operating system libraries and services that allocate memory on their own, and .NET has no direct control over that allocation, only that component that has allocated can free up memory, and typically it needs to be warned that your application no longer needs that allocated content .

The mechanism that .NET has adopted to give this warning is disposable pattern . Any object that accesses resources outside of managed memory must implement the Dispose() method of the IDisposable interface. In this method all the necessary work is done to release all resources allocated outside of managed memory.

Note that the object's managed memory is not released, this will only occur when the GC triggers a collection.

To ensure that the Dispose() always be called it is important to put it within a finally of a try. Or better yet, use the statement using which mounts a scope where the object needs unmanaged memory and is guaranteed to call the method to its end.

If you create a disposable object without the using or call the method manually in a guaranteed way, there will be a memory leak.

Has many questions on the subject:

Wow, good thing I always do this and don't take risks

Not quite so. It has objects that you have to implement Dispose(). It is true that this is not so common in most applications, in general you only consume these classes, after all almost always access to the resource will be done by an API in C or another unmanaged language. But if you have to make a class like this you will have to release the appropriately managed resource according to the component you are using. The method does not release anything magically. Properly freeing unmanaged memory is not always intuitive. But there are cases that it is simple and the operating system itself does for you simply by signaling that it is shutting down.

As I said this pattern is a flag that something must be done to throw away something no longer needed.

One of the most common mistakes I see people make is create a class to manage connection. Someone taught this wrong one day and everyone went on to do wrong. First that in general this class does not help much, and second that it has a feature that must be discarded. This pattern of dispose is viral, that is, if you use it on an object it must necessarily become disposable.

What if I forget to use using I get the leak until the end of the application?

Maybe not. All .NET objects they have a method Finalize() that ends up being called by the GC when an object is collected and if it is written right it will kill the leak, but we can still say that there was a leak because it stayed alive longer than it should.

Nothing guarantees that the finisher will ever run. And it is even somewhat common that it is not called because it never has the object collected. The managed object does not make tickle in memory, so it does not cause pressure on the GC, but the resource unmanaged that it references usually takes up a lot of space and causes havoc. Not to mention that it should perhaps be closed for another use, when it is exclusive, and this does not occur.

Another problem of letting to release the resource during collection is that it will take longer.

It is rare to happen, but a code can suppress the completion improperly. Not so rare is a finisher preventing others from executing .

Objects referenced

Eventually some object, especially large, ends up being referenced by another object that lives longer than that first object needs to survive. This can be considered by some as a leak.

May be that you put a reference to it on a static object that will likely have the lifetime for every application. Or it is referenced in an object that is circulating everywhere in the application without need. Or still in an unmanaged object that is never released.

Some components of .NET or third-party libraries have such situations. Either because they were done badly or because there is no possibility of doing it correctly. It is rare, but it exists. And you have to take additional care.

Another common error is the programmer putting a null to end the reference. Almost every time you do that you have something wrong in the code. Of course there are cases that the semantics of null object is valid.

Events

A typical case is the use of the event. For those who do not know the event it is the implementation of the standard Observer . In it you reference a delegate from another object that you want to be notified. If the observed object survives longer than the Observer needs it will hold the Observer alive without need.

var obj = new Classe();
// ...
obj2.Event += obj.objEventHandler;
// ...
obj = null //deveria librar para o GC coletar, mas ainda tem referência p/ ele

This can be solved by removing the notification signature when the observer no longer needs to stay live. And it is rare that the life time of these objects is different, but it is a possibility, attention to it. Something like this should be done:

obj2.Event -= obj.objEventHandler;

Closures

If you create a closure, i.e. a delegate that makes use of a variable created outside its scope, that variable will be created in the heap (creates a class to support the variable(S)) and will only lose reference when the delegate ceases to exist. This may take longer than it should or never happen.

In a way is the same problem of having a reference on any object. But many people don't realize that the variable will be "promoted" to the heap and that the delegate may survive longer than you initially think.

Concatenation of collections

If you have to concatenate many strings, each step in the concatenation generates a new object and the previous one is discarded. This is not exactly a memory leak classic, but it does not cease to produce too much garbage. In this case it is better to use a StringBuilder.

The same can occur in a List and other collections, but the problem is a little different from what occurred with string. These collections were made to add new items. The collection does not generate a new object on each addition, but generates a new object logarithmically and can be large objects to discard. The ideal is to avoid this already by reserving space at least close to what is expect it will consume (it's Java, but .NET is similar).

If we reserve too much space we can consider a bit like leakage, after all something allocated and not used is also not good, this must be minimized.

Occurs with any immutable object that requires many changes or large changes at some times.

Are not leaks per se , but they act almost as if they are, I think it is important to understand why this type of use can cause more problems that some real leaks.

Allocate in heap no need

Is also not a memory leak per se , but creating an object in the heap (maybe by boxing will allocate something that would even need to be there and therefore would not need to be collected. It is a temporary leak, but it can be bad if done in quantity, and even can by undue pressure on the GC.

Today there are several ways to avoid that, one of them is the Span.

Cache

Cache must be accessed by a reference that will die very quickly or by a weak reference that allows the referenced object to be released. If this does not occur you can hold an object improperly not only occupying general memory, but also the cache space that could have something more useful.

frameworks

WPF and WinForms can leak memory in binding, no Textbox undo, EventHandler and others.

This is not a complete list, just cited examples, several frameworks of various types leak memory depending on how you use, so read the documentation in full, carefully is necessary before doing. Doing fast can't be an excuse to skip that requirement.

Thread

If a thread does not terminate when it should, if it enters deadlock , not only the objects referenced inside it can live longer than it should, but the memory itself needed for this thread stays alive (only the stack can have 1MB and is all there, even if it has no allocations in it).

A deadlock can occur for a variety of reasons, including forgetting to give a Monitor.Exit().

Dynamically generated code

It is rare to do this, but if you generate a lot of code at runtime (this can be done from several ways) it is likely that all the memory needed for your creation will no longer be freed even if you no longer need this code.

Fragmentation of LOH

The Large object Heap (see below) is not properly compressed. If the reuse of the holes left are much smaller than the one occupied before there will be a huge waste. It is true that it does not occur so much because if you have several minors, it is possible that you can fit two or more new blocks in the places of one old. This is a rare concern and not exactly a memory leak, but a waste that can compromise in extreme cases.

Extreme care

I recently learned a "good practice": D for applications running with a gerenational garbage collector where the objects must die young or survive forever.

These memory managers have a short 0 generation (something like 256KB) to ensure that the collection of it is done in a very fast (in the microsecond House). Obviously it tends to fill relatively quickly (not so much, I have seen statistics that most applications have objects with average size of incredible 35 bytes since the large ones do not enter these generations) and when this occurs the collection is triggered copying all objects that still have references to Generation 1. ideally should not copy any object. Of course this is almost impossible, but we must try that this occur.

The more objects are copied to Gen 1 the faster it fills. It is also a bit short (about 2MB). It is done so to be fast (1 or 2ms) and not give noticeable pause. When it fills, it has to copy everything that should still survive to Generation 2. Again the ideal is to copy as little as possible.

Gen 2 has no theoretical size limit. Of course he tries to keep everything inside the RAM. Even virtual memory has its limit, at 32 bits it is 4GB and at 64 bits is 16 Exabytes (rare machines that manage to get past 1 TB today, which are 7 orders of magnitude smaller than the maximum allowed, or if you prefer it is more or less all RAM available on all computers in the world today).

Takes a long time, but if this last generation fills up, the pause will be potentially long. It is true that there are techniques to minimize this, such as concurrency from part of the process, and it should also be rarer to have a triggered collection. So the ideal is that rare objects arrive in this generation.

If many objects arrive in Gen 2 it would be better to create a object pooling preferably large enough to fall into the LOH and never be copied or collected, for example you can use Memory.

Objects above 85000 bytes are allocated in the LOH - Large object Heap and are only compressed along with Gen 2 collection. In fact it does not compact in the sense of reducing the Memory Fragments, it just releases the which is not being used. Because they are large objects, holes are not a problem for performance as is the case with small objects. The holes enter a free list to be used again by new objects that fit there, more or less as occurs in memory managed by the operating system.

Of course not every kind of application needs all this concern, most do not give the perception that there are stops.

And most importantly, like all good practice, have to use where it makes sense, be useful, and only after understanding all implications. Creating a pool of objects can cause memory leakage.

What does this have to do with memory leakage?

Not that this is exactly a classic memory leak, but this memory usage pattern can lower the pressure on the garbage collcetor. Letting an object survive longer than necessary, even if it ends up being collected does not cease to be a temporary leak.

How to detect

Each problem requires a specific technique. What will help are the memory profilers. already answered something about this and listed some profilers.

I found an interesting technique to test .

Article in Code project .

.NET memory Profiller tips.

Question on the subject in SO .

Doubts specific can be asked.

 6
Author: Maniero, 2020-11-27 12:04:14