12. Disposal and Garbage Collection || C#10 Flashcards

1
Q

What is a disposal and garbage disposal in c#?

A

Disposal - Its a specific code required by some objects to release resources such as open files, locks, operating system handles, and unmanaged objects. It is supported by IDisposable
Garbage collection - the release of managed memory that was occupied by unused objects. It is performed by the CLR.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Are disposal and garbage collection the same thing in c#?

A

Disposal differs from garbage collection in that disposal is usually explicitly instigated; garbage collection is tottaly automatic. In other words, the programmer takes care of such things as releasing file handles, locks and operating system resources, while the CLR takes care of releasing memory.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

What is a good example of disposal code?

A

Good example is a try/finally block, where the finally block ensure that the Dispose method is called even when an exception is thrown or the code exits the block early.
Another example is the using... statement like:
using FileStream fs = new FileStream("myFile.txt", FileMode.Open);
The following syntax ensures exposal as soon as fs goes out of scope (does not use fs anymore)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Name de facto rules of disposal logic (its not hardwired, just a logic to define a cosistent protocol)

A
  1. After an object has been disposed, it’s beyond redeption. It cannot be reactivated, and calling its methods or properties (other than Dispose) throws an ObjectDisposedException;
  2. Calling an object’s Dispose method repeatedly causes no error;
  3. If disposable object x “owns” disposable object y, x’s Dispose method automatically calls y’s Dispose method - unless instructed otherwise;
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Some objects may have both Dispose and Close methods. Is there a difference between them?

A

The .NET BCL is not completely consistent on the semantics of a Close method, although in nearly all cases it’s either of the following:
1. Functionally identical to Dispose
2. A functional subset of Dispose
An example might be a DbConnection: a closed connection may be re-opened but a disposed one can never be re-disposed

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

There is this safe rule - “if in doubt, dispose” - but are there scenarios when we shouldnt dispose the object?

A

There are 3 scenarios for not disposing:
1. When you don’t “own” the object - for example, when obtaining a shared object via a static field or property;
2. When an object’s Dispose method does something that you don’t want;
3. When an object’s Dispose method is unnecessary by design and disposing that object would add complexity to your program;

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What is an anonymous disposal pattern?

A

Let’s say we want to implement IDisposable without having to write a class for it. Instead we can just write this:
~~~
public class Disposable: IDisposable
{
public static Disposable Create (Action onDispose) => new Disposable (onDispose);
Action _OnDispose
Disposable (Action onDispose) => _onDispose = onDispose;
public void Dispose()
{
_onDispose?.Invoke(); // Execute if not null
_onDispose = null; // Ensure it can’t execute a second time
}
}
~~~
In here the class was created, but it serves as a factory for creating instances of disposable objects. By using this pattern, you can create disposable objects without explicitly defining a separate class for each disposable instance. Instead, you can use the Create method to create instances and specify the disposal code using an Action delegate, making the implementation more concise and flexible.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

How does the Garbage Collection (GC) work, in general?

A

Regardless of whether an object requires a Dispose method for custom tear-down logic, at some point the memory it occupies on the heap must be freed. The CLR handles this side of it entirely automatically via an automatic Garbage Collection (GC). You never deallocate managed memory yourself.
If we have a method that contains a local variable, after this method is used and exits, the local variable pops out of scope, meaning that nothing is left to reference the array on the memory heap. The orphaned array then becomes eligible to be reclaimed in GC.
In debug mode with optimization disabled, the lifetime of an object referenced by a local variable extends to the end of the code block to ease debugging. Otherwise, it becomes eligible for collection at the earliest point at which it’s no longer used.
Garbage collection does not happen immediately after an object is orphaned - it happens periodically, but not to a fixed schedule. The CLR decides when to collect upon a number of factors: available memory, the amount of memory allocation and the time since the last collection. The timespac between when an object was orphaned until when it is collected can span from nanoseconds to days.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

What is a ‘root’?

A

A root is something that keeps an object alive. If an object is not directly or indirectly referenced by a root it will be eligible for garbage collection.
A root is one of the following:
* A local variable or parameter in an executing method (or in any method in its call stack);
* A static variable;
* An object on the queue that stores objects ready for finalization
To put it in another way, objects that cannot be accessed by following the references from a root object are unreachable - and therefore subject to collection

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What is a finalizer and how it is related to garbage collection?

A

Prior to an object being released from memory, its finalizer runs, if it has one. A finalizer is declared like. constructor but it is prefixed with ~ symbol:
~~~
class Test
{
~Test()
{
// Finalizer logic
}
}
~~~
Although similar in declaration to a constructor, finalizers cannot be declared as public or static, cannot have parameters and cannot call the base class.
Finalizers are possible because garbage collection works in distinct phases. First, the GC identifies the unused objects ripe for deletion. Those without finalizers are deleted immediately. Those with pending (unrun) finalizers are kept alive (for now) and are put onto a special queue.
At that point garbage collection is complete and your program continues executing. The finalizer thread then kicks in and starts running in parallel to your program, picking objects off that special queue and running their finalization methods. Prior to each objects finalizer running, it’s still very mutch alive - that queue acts as a root object. After it’s been dequeued and the finalizer executed the object becomes orphaned and will be deleted in the next collection.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

What are the conditions/restrictions that the finalizers bring?

A
  • Finalizers slow the allocation and collection of memory (the GC needs to keep track of which finalizers have run);
  • Finalizers prolong the life of the object and any reffered objects (they must wait for the next garbage collection for actual deletion).
  • It’s imposible to predict in what order the finalizers for a set of objects will be called.
  • You have limited control over when the finalizer for an object will be called.
  • If code in a finalizer blocks,other objects cannot be finalized.
  • Finaliezers can be circumvented altogether if an application fails to unload cleanly;

In summary, finalizers are somewhat like lawyers - although there are cases in which you really need them, in general you don’t want to use them unless absolutely necessary. If you do use them, you need to be 100% sure you understand what they are doing for you.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

What are general guidelines for implementing finalizers?

A
  • Ensure that your finalizer executes quickly;
  • Never block in your finalizer;
  • Don’t reference other finalizable objects;
  • Don’t throw exceptions
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Describe a pattern where Dispose method is called from the Finalizer

A

This makes sense when cleanup is not urgent and calling Dispose is more of an optimization than a necessity. In this pattern we couple memort deallocation and resource deallocation - two things with potentially divergent interests (unless the resource is memory itself). This pattern also serves as a backup when a consumer simply forgets to call Dispose()

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Describe pattern implementation where Dispose method is called from the Finalizer

Describe both scenarios where the disposing flag is true/false

A

Dispose() is overloaded to accept a bool disposing flag. The parametless version is not declared as virtual and simply calls the enhanced version with true.
The enhanced version contains the actual disposal logic and is protected and virtual; this provides a safe point for subclasses to add their own disposal logic. The disposing flag means its being called “properly” from the Dispose method rather than in “last-resort mode” from the finalizer. The idea is that when called with disposing set to “false”, this method should not, in general, reference other objects with finalizers (because such objects might themselves have been finalized and so be in an unpredictable state).
Here are a couple of tasks that the Dispose method can still perform in last-resort mode, when disposing flag is false:
* Releasing any direct references to OS resource (obtained, perhaps, via a P/Invoke call to the Win32 API);
* Deleting a temporary file created on construction;

Notice that we call GC.SuppressFinalize in the parametless Dispose method - this prevents the finalizer from running when GC later catches up with it. Technically this is unnecessary given than Dispose methods must tolerate repeated calls. However, doing so improves performance because it allows the object (and its referenced objects) to be garbage collected in a single cycle.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

What is a ‘resurrection’ in Disposal and Garbage Collection?

A

Suppose a finalizer modifies a living object that refers back to the dying object. When the next garbage collection happens (for the object’s generation) the CLR will see the previously dying object as no longer orphaned - and so it will evade garbage collection. This is an advanced scenario and is called resurrection

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Lets say we want to write a class that manages a temporary file. When an instance of that class is garbage collected, we’d like the finalizer to delete the temporary file. Now we have a class written like so; Describe what might go wrong and how can we avoid it?

A

File.Delete might throw an exception (due to a lack of permissions, perhaps, or the file being in use, or having already deleted). Such an exception would take down the entire application (as well as preventing other finalizers from running). We could simplu ‘swallow’ the exception with an emplty catch block, but then we’d never know that anything went wrong. Calling some elaborate error reporting API would also be undesirable because it would burden the finalizer thread, hindering garbage collection for other objects. We want to restrict finalization action to those that are simple, reliable and quick.

A better option is to record the failure to a static collection.
Enqueuing the object to the static FailedDeletions collection gives the object another referee, ensuring that it remains alive until the object is eventually dequeued.
ConcurrentQueue<T> is a thread-safe version of Queue<T>. There are a couple of reasons for using a thread-safe collection. First, the CLR reserves the right to execute finalizers on more than one thread in parallel. This means that when accessing shared state such as a static collection, we must consider the possibility of two objects being finalized at once. Second at some point we’re going to want to dequeue items from FailedDeletions so that we can do something about them. This also must be done in a thread-safe fashion because it could happen while the finalizer is concurently enqueuing another object.

17
Q

What purpose does GC.ReRegisterForFinalize method serve?

A

A resurrected object’s finalizer will not run a second finalizer - unless you call GC.ReRegisterForFinalize. This method sgould be included in the catch block so if the deletion fails, we register the object to try again in the next garbage collection.
But be careful to call GC.ReRegisterForFinalize just once in the finalizer method. If you call it twice, the object will be registered twice and will have to undergo two more finalizations.

18
Q

How the GC works?

A

The standard CLR use a generational mark-and-compact GC that performs automatic memory management for objects stored in the managed heap. The GC is considered to be a tracing GC in that it doesn’t interfere with every access to an object, but rather wakes up intermittently and traces the graph of objects stored on the managed heap to determine which objects can be considered garbage and therefore collected.
The GC initiates a garbage collection upon performing a memory allocation (via the new keyword), either after a certain threshhold of memory has been allocated or at other times to reduce the application’s memory footprint. This process can also be initiated manually by calling System.GC.Collect. During a garbage collection, all threads can be frozen.
The GC begins with its root object references and walks the object graph, marking all the objects it touches as reachable. When the process is complete, all objects that have not been marked are considered unused and are subjects to garbage collection.
Unused objects without finalizers are immediately discarded, unused objects with finalizers are enqueued for processing on the finalizer thread after the GC is complete. These objects the become eligible for collection on next GC for the object’s generation (unless resurrected).
The remaining ‘live’ objects are then shifted to the start of the heap (compacted), freeing space for more objects.

19
Q

What is the purpose of compacting ‘live’ objects after unused ones are discarded?

A

It prevents memory fragmentation, and it allows the GC to employ a very simple strategy when allocating new objects, which is to always allocate memory at the end of the heap. This prevents the potentially time-consuming task of maintaining a list of free memory segments.

20
Q

What happens if there is insufficient space to allocate memory for the new objects after GC?

A

If there is insufficient space to allocate memory for the new objects after GC and the OS is unable to grant further memory, an OutOfMemoryException is thrown.

21
Q

Describe the Generational Collection

A

This optimization technique takes advantage of the fact that although many objects are allocated and discarded rapidly, certain objects are long-lived and thus don’t need to be traced during every allocation.
Basically, the GC divides the managed heap into 3 generations. Objects that have just been allocated are in Gen0, and objects that have survived 1 collection cycle are in Gen1; all other objects are in Gen2. Gen0 and Gen1 are known as ephemeral (short-lived) generations.
The CLR keeps the Gen0 section relatively small (few hundreds KB - few MB). When the Gen0 section fills up, the GC instigates a Gen0 collection - which happens relatively often. Th GC applies a similar memory threshold to Gen1 (which acts as a buffer to Gen2), and so Gen1 collections are relatively quick and frequent, too. Full collections that include Gen2, however, take much longer and so happens infrequently.

22
Q

What is the size of Gen2 and how long does it take compared to Gen0 or Gen1?

A

The size of Gen2 is unbounded and it might take as long as 100 ms on a program with large object graph (Gen0 might take less that one millisecond)

23
Q

What is a Large Object Heap? Define some things that are related to it

A

The GC uses a separate heap called Large Object Heap (LOH) for objects largen than a certain threshold (currently 85,000 bytes). This prevents the cost of compacting large objects and prevents excessive Gen0. collections - without the LOH, allocating a series of 16MB objects might trigger a Gen0 collection after every allocation.
By default, LOH is not subject to compaction, because moving large blocks of memory during garbage collection would be prohibitively expensive. This has two consiquenses:
1. Allocations can be slower, because the GC can’t always simply allocate objects at the end of the heap - it must also look in the middle for gaps, and this requires maintaining a linked list of free memory blocks.
2. The LOH is subject to fragmentation. This means that the freeing of an object can create a hole in the LOH that can be difficult to fill later. For instance, a hole left by an 86,000 byte object can be filled only by an object of between 85,000 and 86,000 bytes (unless joined by another hole).

24
Q

Is Large Object Heap generational?

A

No. All obejcts ar treated as Gen2

25
Q

What are the 2 garbage collection modes?

A
  1. Workstation 2. Server
    Workstation is default, but we can switch to server by adding this to project’s .csproj file:
26
Q

What is the difference between workstation and server garbage collection?

A

When server collection is enabled, the CLR allocates a separate heap and GC to each core. This speeds up collection but consumes additional memory and CPU resources (because each core requires its own thread). Should the machine be running many other processes with server collection enabled, this can lead to CPU oversubscribtion which is particularly harmful on workstation because it makes the OS as a whole feel unresponsive.
Server collection is available on multicore systems only;

27
Q

What is a background collection?

A

The GC must freeze (block) your execution threads for periods during a collection. Background collection minimizes these periods of latency, making your application more responsive. This consumes slightly more CPU and memory, but by disabling background collection the following is accomplished:
1. Slightly reduced CPU and melory usage;
2. Increase the pauses (or latency) when a garbage collection occurs;
Background collection works by allowing your application code to run in parallel with a Gen2 collection.

28
Q

What is the difference between background collection and concurrent collection?

A

Background collection is an improved version of what was formerly called concurrent collection: it removes a limitation whereby a concurrent collection would cease to be concurrent if the Gen0 section filled up while a Gen2 collection was running. This allows applications that continually allocate memory to be more responsive

29
Q

Can we manually trigger Garbage Collection? Is it better or worse than letting the GC run automatically?

A

Garbage Collection can be forced manually anytime by running GC.Collect.
GC.Collect() - full collection;
GC.Collect(0) - fast Gen0 collection
In general, you get the best performance by allowing the GC to decide when to collect: forcing collection can hurt performance by unnecessarily promoting Gen0 objects to Gen1 and Gen1 to Gen2. It can also upset the GC’s self-tuning ability, whereby the GC dinamically tweaks the threshholds for each generation to maximize performance as the application executes.
A good example for when to execute manual garbage collection is 1) when the application goes to sleep for a while and wont do any GC, but will continue to consume memory even when asleep.
2) when testing a class that has a finalizer

30
Q

What is an ‘array pooling’?

A

A way to avoid garbage collection overhead and it works by “renting” an array, which you later return to a pool for reuse.
To allocate an array indicating the size of the array:
int[] pooledArray = ArrayPool<int>.Shared.Rent(100); // 100 bytes
This allocates an array of (at least) 100 bytes from the global shared array pool. The pool manager might give you an array that’s larger that what you’ve asked for.
When finished with array , call Return. This releases array to the pool, alowing the same array to be rendered again:
ArrayPool<int>.Shared.Return(pooledArray);

31
Q

How can managed memory leaks happen in managed language like c# if CLR does automatic garbage collection?

A

Large and complex .NET applications can exibit an example where application consumes more and more memory over its lifetime, until eventually must be restarted.
Managed memory leaks are caused by unused objects remaining aliveby virtue of unused or forgotten references. A common candidate is event handler where it might be left hooked to the thing that was created. And the best way to deal with it would be to implement IDisposable to the created items.

32
Q

What is a ‘weak reference’?

A

A reference to an object that’s ‘invisible’ to the GC in terms of keeping the object alive.
If a target is referenced only by one or more weak references, the GC will consider the target eligible for collection. When target is collected, the Target property of the WeakReference will be null:
~~~
var sb = new StringBuilder(“this is a test”);
var weak = new WeakReference(sb);
Console.WriteLine(weak.Target); // This is a test
—–
var weak = GetWeakRef();
GC.Collect();
Console.WriteLine(weak.Target); // null
~~~
If you dont want the target to be collected in GC, assign it to local variable - now it has a strong root and so cannot be collected while the variable is in use.

33
Q

What is the use for the weak references?

A

In esence it is used to track objects without preventing them from being collected.
One use for weak references is to cache large object graphs. This allows memory - intensive data to be cached briefly without causing excessive memory consumption.