Is there a common practice how to make freeing memory for Garbage Collector easier in .NET?

风格不统一 提交于 2019-12-02 07:06:45

is it useful to assign null value to objects that are not longer needed?

Generally, no. Most of the samples you'll see online that do this are by people that came to .Net from VB6, where this was a common best practice. In .Net, it's less helpful. If you have a very long running method, it might let the garbage collector find the object a little earlier — but if your method is that long you have other problems.

Instead, in .Net you should build short methods and define your variables as late as possible in the smallest scope blocks possible. Use the "natural" lifetime of the variable as determined by scope, but keep that natural lifetime short.

You should not be doing your own garbage collection, as suggested by at least one other answer here. This can actually make things slower. .Net uses a generational garbage collector. By forcing a garbage collection, you might collect the target object (you also might not - there are no guarantees with garbage collection). But you'll likely also force a bunch of other objects that you can't collect yet to be stored in a higher order generation, making them harder to collect in the future. So just don't do this.

You shouldn't worry too much and not optimize prematurely. Memory management in .NET is automatic and very efficient. If you do start to 'optimize' you need to know exactly what you are doing or you might slow it down.

So finish your game first, and then if it's too slow use a profiler to find the problem. Most likely it will not be a memory problem.

Most of the graphics related objects in .net (Image and its descendants, Graphics, Pen, Brush, etc) implement IDisposable. If you're going to be using an object for a specific operation, then not again, use the following pattern:

using(var g = Graphics.FromBitmap(bmp))
{
    //Do some stuff with the graphics object
}

Doing it like this will ensure any unmanaged resources will get freed when they fall out of scope.

I find allocating objects in .net one of the things that affects performance the most. To get around this I use the a Factory pattern where I recycle objects that I have used. Here is simple generic implementation:

internal class ListFactory<T> 
    where T: IRecyclable, new()
{
    private List<T> _internalList;
    private int _pointer;

    public ListFactory()
    {
        _internalList = new List<T>();
        _pointer = 0;
    }

    public void Clear()
    {
            _pointer = 0;
    }

    public int Count
    {
        get
        {
            return _pointer;
        }
    }

    public List<T> InnerList
    {
        get
        {
            return _internalList;
        }
    }

    //Either return T form the list or add a new one
    //Clear T when it is being reused
    public T Create()
    {
        T t;

        //If the pointer is less than the object count then return a recycled object
        //Else return a new object 
        if (_pointer < _internalList.Count )
        {
            t = _internalList[_pointer];
            t.Recycle();
        }
        else
        {
            t = new T();
            _internalList.Add(t);
        }
        _pointer ++;
        return t;
    }
}

For my line routing algorithm, I need to constantly keep many values as a RouteNode which implements the following interface:

public interface IRecyclable
{
    void Recycle();
}

These get constantly created and destroyed. To recycle these objects, create a new factory:

nodeFactory = new ListFactory<RouteNode>();

When you need an object, call the create method:

RouteNode start = nodeFactory.Create();
RouteNode goal = nodeFactory.Create();

When you have finished with the objects in the list, clear the list. The pointer is reset to the start of the list, but the objects themselves are not destroyed. In fact the Recycle method is only called when the object is reclaimed again. (You may want to do this earlier if you have other object references, see comments below)

This is a pretty naive implementation, but its a place to start.

It can be useful in some cases to set objects to null that are no longer needed. Often it is useless or counterproductive, but not always. Four main situations when it can be helpful:

  • Objects containing fields declared as "WithEvents" in vb.net should implement IDisposable, and should nulled those fields in the Dispose method. I don't know why vb.net doesn't include a nice way of automatically nulling WithEvents fields, but since it doesn't they must be cleared manually.
  • If a local variable holds a reference to an object which in fact will never be used again, but it may be awhile before the code reaches a point where the compiler knows the variable won't get used, clearing the variable may allow the reference to be freed up sooner.
  • If at some point an array which is large or has been around for some time is known to be useless, and if references to some recently-allocated objects are stored in it, clearing those references before destroying all surviving references to the array may allow the newly-created objects to be freed much sooner than they otherwise would be.
  • If an object has a Finalize routine and is in the finalization queue, all objects directly or indirectly referenced by it will be exempted from garbage collection. Consequently, finalizable objects should not reference anything that won't be needed for finalization.
  • To do this (and I've had to do this with frequent >50mb allocations), call:

            myObj = null;
            GC.Collect();
            GC.WaitForPendingFinalizers();
    

    I've noticed that the memory footprint of the app will then greatly diminish. In theory, you shouldn't need to do this. However, in practice, with a 32 bit windows OS you might get 2 contiguous blocks of >300mb free at any given time, and having that space taken up by lots of little allocations or a series of big ones can mean that other large allocations will fail unnecessarily. The garbage collector runs in the background when it can, but if you absolutely must make large allocations right now, that set of lines helps to make that possible for me.

    EDIT: From what I put in the comments, for the downvoters.

    If you read the entire post about garbage collection by Rico Mariani, you'll note that large, infrequent, non-predictable memory allocations fall into scenario #2. To whit:

    Rule #2

    Consider calling GC.Collect() if some non-recurring event has just happened and this event is highly likely to have caused a lot of old objects to die.

    A classic example of this is if you're writing a client application and you display a very large and complicated form that has a lot of data associated with it. Your user has just interacted with this form potentially creating some large objects... things like XML documents, or a large DataSet or two. When the form closes these objects are dead and so GC.Collect() will reclaim the memory associated with them.

    Now why would I suggest this as a possible time to call the collector? I mean, my usual advice goes something like "the collector is self-tuning so don't mess with it." Why the change of attitude you might ask?

    Well here is a situation where the collector's tendancy[sic] to try to predict the future based on the past is likely to be unsuccessful.

    If you make large allocations in your game, you will need to be careful about how memory is handled. The garbage collector works on prediction based on past events, and large blocks of memory on a 32bit machine can be devastating to future allocations if not properly managed. If you haven't done it, don't automatically assume that I'm wrong; if you have done it, I'd welcome an explanation of how to do it properly (ie, how to defrag memory to make sure I can always allocation 50-100mb of memory at a given time).

    易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
    该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!