Memory management, GC mechanism, memory release process in .NET

xiaoxiao2021-03-05  23

introduction

As a .NET programmer, we know that the memory management of managed code is automatic. .NET can guarantee that our managed procedures are all released at the end, which saves our programmers to save a lot of trouble. We can do not want to manage memory, anyway .Net will guarantee everything. Ok, it makes sense, there is a certain reason. The problem is that when we use unmanaged resources. Net can't be automatically managed. This is because the non-hosting code is not controlled by the CLR (Common Language Runtime), which exceeds the management scope of the CLR. So how do you deal with these unmanaged resources, how do .NET manage and release managed resources?

Automatic memory management and GC

The memory allocation in the original program is like this: find the first memory address with enough space (not occupied), then assign the memory. The programmer needs to manually release this memory when the program no longer needs information in this memory. The memory of the stack is public, that is, all processes may override the memory content of another process, which is why many models of design will even let the operating system itself down. The procedure we sometimes encounter is inexplicably dead (random phenomena), is also caused by improper memory management (may be caused by the memory problem of the program or foreign program). Another common example is the TRAINER that everyone often sees, they reach the "invincible" effect by directly modifying the memory. Understand these we can imagine how dangerous if the memory address is confusing, we can also imagine why C programmers (some) have a headache for a pointer. In addition, if the memory in the program is not manually released by the programmer, the memory will not be redistributed until the computer will restart, that is, the memory leaks we said. These are in the non-hosting code, the CLR isolation of the code between the code is avoided by appdomain, which means that an appdomain cannot read / write another AppDomain in general cases. The managed memory release is responsible by Garbage Collector. What we have to further tell is this GC, but before this, we must first talk about the distribution of memory in the hosted code. The assignment of memory in the hosted stack is the order, which means a distribution of one. This speed is higher than the original program, but the speed of the GC will come back by GC. why? You will know the answer after watching the GC.

GC work mode

First we have to know when the object in the hosted code will recover, we can't manage it (unless GC.Collect is forced GC recycling, this is not recommended, and will explain why). The GC will perform a recycle at its "happy" (this has many reasons, such as when the memory is not enough. This is to improve memory allocation, recycling efficiency). So if we use Destructor? The same is not, because the concept of DEStructor in .NET has no longer existence, it turns into Finalizer, which will be told later. Currently remember that an object can only be reclaimed without any reference. For this, please see the following code:

[C #]

Object obja = new object ();

Object objb = Obja;

Obja = NULL;

// Forced recycling.

Gc.collect ();

Objb.toString ();

[Visual Basic]

DIM OBJA AS New Object ()

Dim objb as object = Obja

Obja = Nothing

'Forced recycling.

Gc.collect ()

Objb.toString () here Obja references is not recycled because this object has another reference, OBJB.

Objects have conditions being reclaimed after without any references. When GC recycles, it will do the following steps:

Determine the object without any references.

Check if the object is recorded on the Finalizer table.

If there is a record on the Finalizer table, move the record to another table, here we call it Finalizer2.

If there is a record on the Finalizer2 table, then release memory.

The Finalizer of the object on the Finalizer2 table will be deleted from the table after executing the thread of another low priority. When the object is created, the GC checks whether the object has Finalizer, if there is a record in the Finalizer table. What we said here is actually a pointer. If you look at these steps, we will find that Finalizer's object will not be recycled for the first time. That is, there is a Finalizer's object to be recycled. This is going slowly, so the author It is recommended that unless it is definitely not to create a Finalizer. In order to prove that GC does work so much rather than the author, we will give an example in the resurrection chapter of the object, seeing is true, earring is virtual! ^ _ ^

GC In order to improve the efficiency of the recycling, the principle is such that the object created before the first recycling is accommodated 0, then, each time the number of the generation will be moved later, that is, said, The original Generation 0 became Generation 1 for the second recycling, and the object created before the first recovery and the second recycling will belong to Generalization 0. GC will try to reclaim in objects belonging to Generation 0, because these are the latest, so it is most likely to be recycled, such as partial variables in some functions are exit function (can be reclaimed). If you recover enough memory in Generation 0, the GC will not be recovered again. If the recovery is not enough, then the GC will try to recover in Generation 1, if it is not enough, in Generation 2, analogy. Generation also has a maximum limit, depending on the Framework version, you can get GC.MAXGENERATION. After recovering memory, the GC will rearrange the memory, so that there is no space between the data, which is because the CLR sequentially assigns memory, so there is no empty memory between memory. Now we know that every time you recycle, you will waste a certain CPU time. This is what I generally don't manually gc.collect (unless you are like me, write some examples about GC! ^ _ ^).

Destructor's declined, Finalizer's birth

This is a new concept for Visual Basic programmers, so a previous part of the presentation will focus on C programmers. We know that when the object is deleted in C , the code in the Destructor will perform some memory release work (or other). However, due to the special working mode of GC, Destructor does not actually exist. In fact, when we use the Destructor's syntax, the compiler will automatically write it as protected virtual void finalize (), this method is what I said Finalizer. Just like it is said, it is used to end certain things, not used to destruct things. In Visual Basic it is in the form of a Finalize method, so Visual Basic programmers don't worry. C # programmer has written Finalizer with Destructor's syntax, but don't get it, there is no Destructor in .NET. In C , we can know when we execute DEStructor, but we can't know when to perform Finalizer during .NET because it is executed after the first object recycling operation. We can't know the order of implementation of Finalizer, that is, the Finalize, which may be executed first, and may be executed after the B is executed. That is, our code in Finalizer does not have any time logic. Let's calculate how many instances of the class are examples, pointing out that Finalizer and Destructor are different and pointed out in Finalizer's time logic errors, because there is no DEStructor in Visual Basic So only C # version: [c #]

Public class countObject {

Public static int count = 0;

Public countObject () {

COUNT ;

}

~ CountObject () {

Count -;

}

}

Static void main () {

CountObject obj;

For (int i = 0; i <5; i ) {

Obj = null; // This step is more, so written just for clearer!

Obj = new countObject ();

}

// count is not 1, because Finalizer will not be triggered immediately, and wait until a recycling operation will be triggered.

Console.writeline (countObject.count);

Console.readline ();

}

Note that the above code should be written with C , because we don't use the delete operator to manually clean the memory, but there is no memory leak in the managed code, because the GC will automatically detect the objects that are not referenced and recycled. Here, the author recommends that you only use Finalizer when you implement the IDisposable interface, do not use (there may be special situations) in other cases (there may be special situations). The release of the non-hosting resource is a better understanding of the IDisposable interface, let us be Jesus!

Object of resurrection

what? Can recovering objects can also "resurrect"? Yes, although this is not accurate. Let us first see a code:

[C #]

Public class resURRECTION {

Public int data;

Public Resurrection (int data) {this.data = data;

}

~ ResurRection () {

Main.INSTANCE = THIS;

}

}

Public class main {

Public Static Resurrection Instance;

Public static void main () {

Instance = new resurrection (1);

Instance = NULL;

Gc.collect ();

Gc.waitforpendingfinalizers ();

// See it, "resurrect" here.

Console.writeline (Instance.Data);

Instance = NULL;

Gc.collect ();

Console.readline ();

}

}

[Visual Basic]

Public Class Resurrection

Public Data As Integer

Public Sub New (ByVal Data As Integer)

Me.Data = data

End Sub

Protected overrides sub firmize ()

Main.instance = me

Mybase.finalize ()

End Sub

END CLASS

Public Class Main

Public Shared Instance As Resurrection

Sub main ()

Instance = new resurrection (1)

Instance = Nothing

Gc.collect ()

Gc.waitforpendingfinalizers ()

'I saw it, "resurrected" here.

Console.writeline (Instance.Data)

Instance = Nothing

Gc.collect ()

Console.readline ()

End Sub

END CLASS

You may ask: "Since this object can be resurrected, will this object be recovered after the program?" Yes, "why?". Let's take a look at the GC's work. You will understand what is going on.

1. Execute Collect. Check reference. No problem, the object has not been referenced.

2. When you create a new instance, you have been recorded on the Finalizer table, so we check that the object is Finalizer.

3, because I find Finalizer, move the record to the Finalizer2 table.

4. There is a record on the Finalizer2 table, so the memory is not released.

5, Collect is completed. At this time we used gc.waitforpendingfinalizers, so we will wait for the implementation of Finalizer on all Finalizer2 tables.

6. After the Finalizer execution, our instance has referred our objects. (Resurrected)

7. Remove all references again.

8. Execute Collect. Check reference. no problem.

9. Since the record has been removed from the Finalizer table last time, this time no object is Finalizer.

10. There is no existence on the Finalizer2 table, so the memory of the object is released.

Now you understand the reason, let me tell you the use of "resurrection". Well, this ... well, I don't know. In fact, there is no use of resurrection, and this is also very dangerous. It seems that this can only be said to be a vulnerability of the GC mechanism (please see gc.regegisterforfinalize. If you want to think about why you can say it is a vulnerability). The author suggests that you have forgotten what resurrections and avoid this type of use. Maybe you will ask: "Do you want to say these?" I said that these is to let everyone know more about GC's working mechanism! ^ _ ^ Release of unmanaged resources

Up to now, we have said managed memory management, then when we use unmanaged resources such as databases, files? At this time we have to use the standard in .NET Framework: IDisposable interface. According to the standard, all classes that need to manually release the non-hosting resource are implemented. This interface has only one way, but there is a relative guidelines indicating how to implement this interface, here I said to everyone. The class that implements the IDisposable interface requires such a structure:

[C #]

Public class base: idisposable {

Public void dispose () {

THIS.DISPOSE (TRUE);

Gc.supressFinalize (this);

}

Protected Virtual Void Dispose (Bool Disposing) {

IF (disposing) {

// Hosting class

}

// Unmanaged resource release

}

~ Base () {

THIS.DISPOSE (FALSE);

}

}

Public Class Derive: base {

Protected Override Void Dispose (BOOL Disposing) {

IF (disposing) {

// Hosting class

}

// Unmanaged resource release

Base.dispose (Disposing);

}

}

[Visual Basic]

Public Class Base

Implements idisposable

Public overloads Sub Dispose () IMPLEments Idisposable.dispose

Me.Dispose (TRUE)

Gc.suppressFinalize (ME)

End Sub

Protected Overloads Overridable Sub Dispose (Byval Disposing as Boolean)

IF Disposing then

'Hosted

END IF

'Unmanaged resource release

End Sub

Protected overrides sub firmize ()

Me.Dispose (false)

Mybase.finalize ()

End Sub

END CLASS

Public Class Derive

Inherits Base

Protected Overloads Overrides Sub Dispose (Byval Disposing as Boolean)

IF Disposing then

'Hosted

END IF

'Unmanaged resource release

Mybase.dispose (Disposing)

End Sub

END CLASS

Why do you design this? Let me explain it later. Now let's talk about several guidelines for achieving this Dispose method:

It can't throw any errors, and repeated calls cannot throw out errors. That is, if I have called an object's Dispose, the program should not be wrong when I call Dispose for the second time, simply say that the program will not do anything when calling Dispose for the second time. These can be implemented by a FLAG or multiple IF judgment. An object's Dispose is to release all resources of this object. Take a inheritance class as an example, in the inheritance class, use the non-hosting resources so it implements the IDisposable interface. If the inherited base class is also used, the base class is also released, the base class has to be released, how to inherit the class Is it released? Of course, through a Virtual / Overridable method, we can guarantee that each Dispose is called. That's why our design has a Virtual / Overridable Dispose method. Note We first release the resource of the inherited class and release the resources of the base class.

Because the non-hosting resources must be protected correctly, we want to define a Finalizer to avoid programmers forgetting the situation of calling Dispose. The above design uses this form. If we manually call the Dispose method, it is not necessary to keep Finalizer, so we use GC.SupressFinalize to remove objects from the Finalizer table in Dispose, which will be faster.

Then what is the disposing and "managed classes"? This is true: Write all the managed code you want to release when calling Dispose. I still remember that we didn't know when the hosted code was released? Here we just get off the reference to the member object, let it be recycled, not directly release memory. In "Hosting", we also have to write all members objects that implements IDisposable because they also have Dispose, so they need to call their Dispose in the object's Dispose, so that the second guidelines can be guaranteed. Disposing is to distinguish between DISPOSE calling method. If we manually call So for the second guideline "hosted class" section, it is of course executed, but if it is a Finalizer called Dispose, this time there is no reference, that is, the member of the object Naturally, there is no existence (no reference), and there is no need to implement a "managed class" part because they are already in a state of recycling. Ok, this is all the IDisposable interface. Now let's recall, we may think that there is a Dispose memory will be released immediately, which is wrong. Only non-hosting memory will be released immediately, and the release of managed memory is managed by GC, we don't have to manage.

Weak reference

A = B, we call this reference called strong reference, GC is to determine if an object can be recycled by checking strong references. In addition, there is a reference called Weakreference, which does not affect GC recycling, which is where it is located. You will ask what is used in the end. Now let's assume that we have a very fat object, which means it takes a lot of memory. We have used this object, intended to get its reference to let GC can recycle memory, but don't have this object, there is no way, create an instance, how to create so slow? Is there any way to solve such a problem? Yes, keep the object in memory is not fast! However, we don't want this to always account for memory, and we don't want to always create a new instance of such fat, because this time consuming. What should I do ...? Smart friends must have guessed that I have to say that the solution is weak reference. Yes, it is it. We can create a weak reference for this fat object so that the GC can recycle when the memory is not enough, does not affect memory usage, and we can use the object again before recycling by GC. Here is an example: [C #]

Public class fat {

Public int data;

Public fat (int data) {

THIS.DATA = DATA;

}

}

Public class main {

Public static void main () {

FAT OFAT = New FAT (1);

Weakreference OFATREF = New WeakReference (OFAT);

// From here, the FAT object can be reclaimed.

OFAT = NULL;

IF (OFATREF.IALIVE) {

Console.writeline ((FAT) OfATREF.TARGET .DATA); // 1

}

// Force recycling.

Gc.collect ();

Console.writeline (OFATREF.IAlive); // False

Console.readline ();

}

}

[Visual Basic]

Public Class Fat

Public Data As Integer

Public Sub New (ByVal Data As Integer)

Me.Data = data

End Sub

END CLASS

Public Module Main

Sub main ()

DIM OFAT AS New FAT (1)

DIM OFATREF As New Weakreference (OFAT)

'Start here, the FAT object can be recovered.

OFAT = Nothing

IF OFATREF.IALIVE THEN

Console.Writeline (Directcast (OFATREF.TARGET, FAT) .DATA) '1

END IF

'Forced recycling.

Gc.collect ()

Console.writeline (OfATREF.IAlive) 'False

Console.readline ()

End Sub

End module

Here our FAT is actually not very fat, but it can reflect the original meaning of example: how to use weak references. What if Fat has Finalizer? If FAT has Finalizer, then we may use another constructor of WeakReference, and a parameter is called TrackResurRection. If it is true, as long as the FAT's memory is not released, we can use it, that is, we still say that Fat's Finalizer is executed You can restore FAT (which can restore FAT after the first recycle operation); if TrackResurRection is false, then the FAT object cannot be recovered after the first reclaimed operation. to sum up

I have written the main points of the articles here:

An object is only recovered if there is no reference.

The memory of an object is not immediately released, and the GC will reclaim it at any time.

Never forced recycling work in general.

If there is no special need, don't write Finalizer.

Don't write some time logical code in Finalizer.

Implement an IDisposable interface in any class with a non-hosting resource or a member of Dispose.

Follow the DISPOSE design to write your own DISPOSE code.

The use of weak references can be considered when using a fat object.

Ok, let me talk about it, I hope that the understanding of GC will make your code more stable, more simple, and more accelerated! More important, there is no longer have memory management issues, whether it is hosted or non-hosting!

Reference Information

1, GC Class

MS-Help: //ms.vscc/ms.msdnvs.2052/cpref/html/frlrfsystemgcclasstopic.htm

2, Programming for Garbage Collection, PROGRAMMING

MS-help: //ms.vscc/ms.msdnvs.2052/cpguide/html/cpconprogramminessentialsforgarbagecollection.htm

3, Curso de Erik, Septima Entrega (Spanish)

http://guille.costasol.net/net/cursocsharperik/entrega7/ENTREGA7.HTM

About author

Author: Yuan Wei (Kefroth)

转载请注明原文地址:https://www.9cbs.com/read-37344.html

New Post(0)