diff --git a/Readme.md b/Readme.md
index 127face..1449848 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,3 +1,4 @@
+[![](https://habrastorage.org/webt/m6/y8/g6/m6y8g69h1spogkxkjibwb9t0cgu.jpeg)](https://habr.com/ru/company/clrium/blog/460635/)
# This book is available in:
- **English**: If you want to show that you like this book or to express your gratitude to author, Star this project, Fork it and Pull Requests to it.
diff --git a/bin/CLR book.docx b/bin/CLR book.docx
index 33ffc30..d3c2ea4 100644
Binary files a/bin/CLR book.docx and b/bin/CLR book.docx differ
diff --git a/bin/CLR book.pdf b/bin/CLR book.pdf
index 8f2c3c7..4507948 100644
Binary files a/bin/CLR book.pdf and b/bin/CLR book.pdf differ
diff --git a/book/en/ExceptionalFlow/4-Exceptions-Types.md b/book/en/ExceptionalFlow/4-Exceptions-Types.md
new file mode 100644
index 0000000..17d7347
--- /dev/null
+++ b/book/en/ExceptionalFlow/4-Exceptions-Types.md
@@ -0,0 +1,534 @@
+### CLR Exceptions
+
+> [A link to the discussion](https://github.com/sidristij/dotnetbook/issues/51)
+
+There are some exceptional situations that, let’s say, more exceptional than others. In terms of classification, as it was said at the beginning of this chapter, we can divide them into ones that belong to a .NET application and others that belong to the unsafe world. The latter group consists of two subcategories: the exceptions of the CLR core (which is essentially unsafe) and any unsafe code of external libraries.
+
+[todo]
+
+#### ThreadAbortException
+
+Although it may seem not so obvious, there are four types of Thread Abort.
+
+ - Rough `ThreadAbort` which gets unstoppable when activated and which doesn’t call exception handlers at all, including `finally` sections.
+ - `Thread.Abort()` method, invoked on a current thread.
+ - Asynchronous `ThreadAbortException` which is thrown from some other thread.
+ - `ThreadAbort` invoked on the threads which are initiated when the `AppDomain` is being unloaded and which contains running methods compiled for this domain.
+
+> It should be noted that `ThreadAbortException` is often used in _big_ .NET Framework, yet doesn’t exist for CoreCLR, .NET Core or for Windows 8 "Modern app profile". Let’s figure out why.
+
+For the second, third or fourth variant of a thread abort, when we are still able to do something, a virtual machine starts going through all exception handlers and look up those that correspond to the type of a thrown exception or for a higher exception level. In our case these are: `ThreadAbortException`, `Exception` and `object` (don’t forget that Exception is essentially a data set and our search may end up with any type of exception, even with `int`). By handling *all* suitable `catch` blocks, the virtual machine successively forwards `ThreadAbortException` along the entire chain of exception handling and enters all `finally` blocks. In general, the situations described below are identical:
+
+```csharp
+var thread = new Thread(() =>
+{
+ try {
+ // ...
+ } catch (Exception ex)
+ {
+ // ...
+ }
+});
+thread.Start();
+//...
+thread.Abort();
+
+var thread = new Thread(() =>
+{
+ try {
+ // ...
+ } catch (Exception ex)
+ {
+ // ...
+ if(ex is ThreadAbortException)
+ {
+ throw;
+ }
+ }
+});
+thread.Start();
+//...
+thread.Abort();
+```
+
+Sometimes, we definitely may expect `ThreadAbort` and have an understandable desire to handle it. For these cases there is `Thread.ResetAbort()` method which does what we need: stops forwarding an exception through the chain of handlers, thus making this exception handled:
+
+```csharp
+void Main()
+{
+ var barrier = new Barrier(2);
+
+ var thread = new Thread(() =>
+ {
+ try {
+ barrier.SignalAndWait(); // Breakpoint #1
+ Thread.Sleep(TimeSpan.FromSeconds(30));
+ }
+ catch (ThreadAbortException exception)
+ {
+ "Resetting abort".Dump();
+ Thread.ResetAbort();
+ }
+
+ "Caught successfully".Dump();
+ barrier.SignalAndWait(); // Breakpoint #2
+ });
+
+ thread.Start();
+ barrier.SignalAndWait(); // Breakpoint #1
+
+ thread.Abort();
+ barrier.SignalAndWait(); // Breakpoint #2
+}
+
+Output:
+Resetting abort
+Catched successfully
+```
+
+But should we really use it? And should we be mad at CoreCLR developers because they got rid of this code? Assume you are a user of code and you think it is "stuck". You have a sharp desire to call `ThreadAbortException`. When you want to terminate a thread, all you really want is that this thread really stops running. Moreover, it rarely happens when an algorithm aborts the thread and leaves it. Normally, it waits for correct termination of operations. Alternatively, it decides that the thread is dead, decrements some internal counters and forgets that there is some multithread processing of some code. You’ll never know which one is worse. Moreover, after many years of being a programmer I still can’t offer a great way to call and handle it. Imagine—you throw `ThreadAbort` not _right away_ but in any case some time after having understood that the situation is hopeless. So sometimes you hit `ThreadAbortException` handler and sometimes you miss it: "stuck code" may be in fact not stuck but a too long executed one. And in that very moment you wanted to kill it, this code could have started working correctly again, i.e. exited `try-catch(ThreadAbortException) { Thread.ResetAbort(); }` block. What will we get in this case? An aborted thread that happened to stuck through no fault of its own. For example, a janitor could have passed by, unplugged the cable and brought the network down. A method was waiting for a timeout and when the janitor has plugged the cable back, everything started working again, but your controlling code had already killed the thread. Is it OK? No. Can we safeguard from this? No. But let’s get back to our idea of legalizing `Thread.Abort()`: we have thrown a hammer at the thread and now expect the latter to be inevitably aborted, but it may never happen. Firstly, it is not clear how to abort it in this case. Things may be more complicated here: a stuck thread can have logic inside which catches `ThreadAbortException`, terminates it using `ResetAbort`, but remains stuck because of broken logic. What’s then? Should we use `thread.Interrupt()`? It seems like an attempt to bypass a program logic error using brute force methods. Also, I guarantee you will get resource leaks: `thread.Interrupt()` won’t call `catch` and `finally` which means you can’t clean up resources. Your thread will simply disappear and if you are in an adjacent thread you won’t know the references to all resources, allocated to the aborted thread. Also, note that if `ThreadAbortException` misses `catch(ThreadAbortException) { Thread.ResetAbort(); }` you will get resource leaks too.
+
+I hope that after having read the information above you will feel some confusion and intention to reread the paragraph. And this would be a good idea that proves you mustn’t use `Thread.Abort()` as well as `thread.Interrupt();`. Both methods make the behavior of your application uncontrolled. They violate the integrity principle, which is the main principle of the .NET Framework.
+
+However, to understand why developers introduced this method you should have a look at the .NET Framework source code and find where `Thread.ResetAbort()` is invoked. Because this method makes `thread.Abort()` legitimate.
+
+**ISAPIRuntime class** [ISAPIRuntime.cs](https://referencesource.microsoft.com/#System.Web/Hosting/ISAPIRuntime.cs,192)
+
+```csharp
+try {
+
+ // ...
+
+}
+catch(Exception e) {
+ try {
+ WebBaseEvent.RaiseRuntimeError(e, this);
+ } catch {}
+
+ // Have we called HSE_REQ_DONE_WITH_SESSION? If so, don't re-throw.
+ if (wr != null && wr.Ecb == IntPtr.Zero) {
+ if (pHttpCompletion != IntPtr.Zero) {
+ UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion);
+ }
+ // if this is a thread abort exception, cancel the abort
+ if (e is ThreadAbortException) {
+ Thread.ResetAbort();
+ }
+ // IMPORTANT: if this thread is being aborted because of an AppDomain.Unload,
+ // the CLR will still throw an AppDomainUnloadedException. The native caller
+ // must special case COR_E_APPDOMAINUNLOADED(0x80131014) and not
+ // call HSE_REQ_DONE_WITH_SESSION more than once.
+ return 0;
+ }
+
+ // re-throw if we have not called HSE_REQ_DONE_WITH_SESSION
+ throw;
+}
+```
+
+This example calls some external code and if it was terminated incorrectly with `ThreadAbortException`, we mark the thread under certain circumstances as non-abortable anymore. In fact, we handle `ThreadAbort`. Why do we abort `Thread.Abort` in this case? Because here, we deal with server code which must return correct error codes to a calling party regardless of our errors. A thread abort would prevent a server to return a necessary error code to user, which is absolutely inappropriate. Also, there is a comment about `Thread.Abort()` during `AppDomain.Unload()` which is an extreme situation for `Thread.Abort` as you can’t stop such a process even if you use `Thread.ResetAbort`. Though it will stop abort handling, it won’t stop the unloading of a thread inside a domain: a thread can’t execute code instructions from the domain being currently unloaded.
+
+**HttpContext class** [HttpContext.cs](https://referencesource.microsoft.com/#System.Web/HttpContext.cs,1864)
+
+```csharp
+internal void InvokeCancellableCallback(WaitCallback callback, Object state) {
+ // ...
+
+ try {
+ BeginCancellablePeriod(); // request can be cancelled from this point
+ try {
+ callback(state);
+ }
+ finally {
+ EndCancellablePeriod(); // request can be cancelled until this point
+ }
+ WaitForExceptionIfCancelled(); // wait outside of finally
+ }
+ catch (ThreadAbortException e) {
+ if (e.ExceptionState != null &&
+ e.ExceptionState is HttpApplication.CancelModuleException &&
+ ((HttpApplication.CancelModuleException)e.ExceptionState).Timeout) {
+
+ Thread.ResetAbort();
+ PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_TIMED_OUT);
+
+ throw new HttpException(SR.GetString(SR.Request_timed_out),
+ null, WebEventCodes.RuntimeErrorRequestAbort);
+ }
+ }
+}
+```
+
+This is a great example of transfer from unmanaged asynchronous `ThreadAbortException` to managed `HttpException` with recording the case to the Performance Counters Log.
+
+**HttpApplication class** [HttpApplication.cs](https://referencesource.microsoft.com/#System.Web/HttpApplication.cs,2270)
+
+```csharp
+ internal Exception ExecuteStep(IExecutionStep step, ref bool completedSynchronously)
+ {
+ Exception error = null;
+
+ try {
+ try {
+
+ // ...
+
+ }
+ catch (Exception e) {
+ error = e;
+
+ // ...
+
+ // This might force ThreadAbortException to be thrown
+ // automatically, because we consumed an exception that was
+ // hiding ThreadAbortException behind it
+
+ if (e is ThreadAbortException &&
+ ((Thread.CurrentThread.ThreadState & ThreadState.AbortRequested) == 0)) {
+ // Response.End from a COM+ component that re-throws ThreadAbortException
+ // It is not a real ThreadAbort
+ // VSWhidbey 178556
+ error = null;
+ _stepManager.CompleteRequest();
+ }
+ }
+ catch {
+ // ignore non-Exception objects that could be thrown
+ }
+ }
+ catch (ThreadAbortException e) {
+ // ThreadAbortException could be masked as another one
+ // the try-catch above consumes all exceptions, only
+ // ThreadAbortException can filter up here because it gets
+ // auto rethrown if no other exception is thrown on catch
+ if (e.ExceptionState != null && e.ExceptionState is CancelModuleException) {
+ // one of ours (Response.End or timeout) -- cancel abort
+
+ // ...
+
+ Thread.ResetAbort();
+ }
+ }
+}
+```
+
+You can see an interesting scenario here—to expect a false `ThreadAbort` (I feel sympathy with CLR and .NET Framework team in how unbelievably many unusual scenarios they have to handle). This time a scenario is handled in two stages: initially, using an internal handler, we catch `ThreadAbortException` and then check whether our thread is marked for abort. If it is not, the `ThreadAbortException` is false. We should address these scenarios accordingly: catch an exception and handle it. If we get a true `ThreadAbort`, it will go to an external `catch` because `ThreadAbortException` should enter all suitable handlers. If it meets necessary conditions, it will also be handled by removing the `ThreadState.AbortRequested` flag using the `Thread.ResetAbort()`.
+
+In terms of `Thread.Abort()` calls, all the examples of code in .NET Framework may be rewritten without using it. Just one example for clarity:
+
+**QueuePathDialog class** [QueuePathDialog.cs](https://referencesource.microsoft.com/#System.Messaging/System/Messaging/Design/QueuePathDialog.cs,364)
+
+```csharp
+protected override void OnHandleCreated(EventArgs e)
+{
+ if (!populateThreadRan)
+ {
+ populateThreadRan = true;
+ populateThread = new Thread(new ThreadStart(this.PopulateThread));
+ populateThread.Start();
+ }
+
+ base.OnHandleCreated(e);
+}
+
+protected override void OnFormClosing(FormClosingEventArgs e)
+{
+ this.closed = true;
+
+ if (populateThread != null)
+ {
+ populateThread.Abort();
+ }
+
+ base.OnFormClosing(e);
+}
+
+private void PopulateThread()
+{
+ try
+ {
+ IEnumerator messageQueues = MessageQueue.GetMessageQueueEnumerator();
+ bool locate = true;
+ while (locate)
+ {
+ // ...
+ this.BeginInvoke(new FinishPopulateDelegate(this.OnPopulateTreeview), new object[] { queues });
+ }
+ }
+ catch
+ {
+ if (!this.closed)
+ this.BeginInvoke(new ShowErrorDelegate(this.OnShowError), null);
+ }
+
+ if (!this.closed)
+ this.BeginInvoke(new SelectQueueDelegate(this.OnSelectQueue), new object[] { this.selectedQueue, 0 });
+}
+```
+
+##### ThreadAbortException during AppDomain.Unload
+
+Let’s unload AppDomain while executing code that is loaded into it. To do this we create an unnatural, yet interesting situation in terms of running code. Here we have two threads: one is `main` and the other one is created to get `ThreadAbortException`. In the `main` thread, we create a new domain and start a new thread in it. This thread must go to the `main` domain, so that child domain methods remain just in Stack Trace. Then, the `main` domain unloads the child one:
+
+```csharp
+class Program : MarshalByRefObject
+{
+ static void Main()
+ {
+ try
+ {
+ var domain = ApplicationLogger.Go(new Program());
+ Thread.Sleep(300);
+ AppDomain.Unload(domain);
+
+ } catch (ThreadAbortException exception)
+ {
+ Console.WriteLine("Main AppDomain aborted too, {0}", exception.Message);
+ }
+ }
+
+ public void InsideMainAppDomain()
+ {
+ try
+ {
+ Console.WriteLine($"InsideMainAppDomain() called inside {AppDomain.CurrentDomain.FriendlyName} domain");
+
+ // AppDomain.Unload will be called while this Sleep
+ Thread.Sleep(-1);
+ }
+ catch (ThreadAbortException exception)
+ {
+ Console.WriteLine("Subdomain aborted, {0}", exception.Message);
+
+ // This sleep to allow user to see console contents
+ Thread.Sleep(-1);
+ }
+ }
+
+ public class ApplicationLogger : MarshalByRefObject
+ {
+ private void StartThread(Program pro)
+ {
+ var thread = new Thread(() =>
+ {
+ pro.InsideMainAppDomain();
+ });
+ thread.Start();
+ }
+
+ public static AppDomain Go(Program pro)
+ {
+ var dom = AppDomain.CreateDomain("ApplicationLogger", null, new AppDomainSetup
+ {
+ ApplicationBase = AppDomain.CurrentDomain.BaseDirectory,
+ });
+
+ var proxy = (ApplicationLogger)dom.CreateInstanceAndUnwrap(typeof(ApplicationLogger).Assembly.FullName, typeof(ApplicationLogger).FullName);
+ proxy.StartThread(pro);
+
+ return dom;
+ }
+ }
+
+}
+```
+
+Exciting things happen here. The code that unloads a domain also looks up methods called in this domain that didn’t finish their work yet, including those in the depth of the method call stack, and invokes `ThreadAbortException` on these threads. This is important, yet not obvious. If a domain is unloaded, it’s impossible to return to a method from which the main domain method is called, but which exists in the unloaded domain. In other words, `AppDomain.Unload` may get rid of those threads that run code from other domains. In this case, it is not possible to abort `Thread.Abort`: you can’t execute the code of an unloaded domain which means `Thread.Abort` will finish its work even if you call `Thread.ResetAbort`.
+
+##### Conclusions on ThreadAbortException
+
+ - It’s an asynchronous exception, therefore it can appear anywhere in your code (though you should make efforts for it).
+ - Normally, code handles only expected errors, such as a file access error, a string parsing error, etc. An asynchronous exception (which can appear anywhere) creates a scenario when `try-catch` may not be handled: you can’t be ready for ThreadAbort at every place in an application. It turns out this exception will cause resource leaks, anyway.
+ - A thread may be aborted because of some domain unloading. If there are calls of unloaded domain methods in Stack Trace of a thread, this thread will get `ThreadAbortException` without `ResetAbort`.
+ - In general, there shouldn’t be situations when you need to call `Thread.Abort()` as the result is almost always unpredictable.
+ - CoreCLR doesn’t have a `Thread.Abort()` manual call option: it was eliminated from the class. But it doesn’t mean you can’t get it.
+
+#### ExecutionEngineException
+
+This exception is marked as `Obsolete` and has the following comment:
+
+> This type previously indicated an unspecified fatal error in the runtime. The runtime no longer raises this exception so this type is obsolete
+
+And this is not true. I guess the author of this comment would really want this to become true. However, let’s get back to the example of an exception in `FirstChanceException` and see it’s not true:
+
+```csharp
+void Main()
+{
+ var counter = 0;
+
+ AppDomain.CurrentDomain.FirstChanceException += (_, args) => {
+ Console.WriteLine(args.Exception.Message);
+ if(++counter == 1) {
+ throw new ArgumentOutOfRangeException();
+ }
+ };
+
+ throw new Exception("Hello!");
+}
+```
+
+The execution of this code will cause `ExecutionEngineException`, though I would expect the `ArgumentOutOfRangeException` by Unhandled Exception from the `throw new Exception("Hello!")` instruction. Maybe this seemed strange to the core developers, and they thought it would be more correct to throw `ExecutionEngineException`.
+
+Another simple way to get `ExecutionEngineException` is to customize marshaling to the unsafe world incorrectly. If you get wrong sizes of types or pass more than it is necessary, ruining, for example, the stack of a thread, you will get `ExecutionEngineException`. This is expectable, as here the CLR entered the state which it finds inconsistent. And it doesn’t know how to recover consistency. As a result, you get `ExecutionEngineException`.
+
+Another thing that needs special attention is the diagnostics of `ExecutionEngineException`. Why is this exception thrown? If it suddenly appeared in your code, answer several questions.
+
+ - Does your application use unsafe libraries? Is it you or third-party who use them? First, try to find where this error occurs in the application. If the code goes to the unsafe world and gets `ExecutionEngineException` there, carefully check the convergence of methods signatures both in your code and in imported one. Remember that if modules written in Delphi or other variants of Pascal are imported, arguments should be reversed (configured in `DllImport`: `CallingConvention.StdCall`).
+ - Are you subscribed to `FirstChanceException`? Its code might cause an exception. In this case, just wrap a handler in `try-catch(Exception)` and write events to the error log.
+ - Is it possible that your application is built partially for one platform and partially for another? Try to clear the nuget packages cache and rebuild the application from scratch with obj/bin folders emptied manually.
+ - Is there a problem with the framework itself? This may happen in the early versions of .NET Framework 4.0. In this case, test a separate piece of code that causes an error in a newer version of the framework.
+
+In general, don’t be afraid of this exception: it’s so rare that you may even forget about it till you meet it next time.
+
+#### NullReferenceException
+
+> TODO
+
+#### SecurityException
+
+> TODO
+
+#### OutOfMemoryException
+
+> TODO
+
+### Corrupted State Exceptions
+
+Once the platform was established and gets popular, programmers started massively migrating from C/C++ and MFC (Microsoft Foundation Classes) to more easy-to-develop environments. Besides .NET Framework, those environments included Qt, Java and С++ Builder—the mainstream was a movement towards virtualized execution of application code. Eventually, the thoughtfully designed .NET Framework started filling up its niche. Matured over the years, the platform turned from a shy newcomer into a key player. If previously we mostly had to deal with far too many components written in COM/ATL/ActiveX (Do you remember dragging COM/ActiveX components onto icon forms in Borland C++ Builder?), now life became much easier. Today the corresponding technologies are _quite_ rare to worry about and there is a chance to make them slightly uncomfortable so that people get rid of them and use state-of-the-art .NET Framework. We perceive old technologies that still exist and do well as archaic, forgotten, "faulty”, and old-fashioned. That’s why it is possible to make another step towards a closed sandbox: make it more impenetrable, more managed.
+
+One of these steps is to introduce the notion of `Corrupted State Exceptions` which declares some exceptional situations illegitimate. Let’s see which exceptions are these and trace the history once again based on one of them – `AccessViolationException`:
+
+**Util.cpp file** [util.cpp](https://github.com/dotnet/coreclr/blob/479b1e654cd5a13bb1ce47288cf78776b858bced/src/utilcode/util.cpp#L3163-L3197)
+
+```cpp
+BOOL IsProcessCorruptedStateException(DWORD dwExceptionCode, BOOL fCheckForSO /*=TRUE*/)
+{
+ // ...
+
+ // If we have been asked not to include SO in the CSE check
+ // and the code represent SO, then exit now.
+ if ((fCheckForSO == FALSE) && (dwExceptionCode == STATUS_STACK_OVERFLOW))
+ {
+ return fIsCorruptedStateException;
+ }
+
+ switch(dwExceptionCode)
+ {
+ case STATUS_ACCESS_VIOLATION:
+ case STATUS_STACK_OVERFLOW:
+ case EXCEPTION_ILLEGAL_INSTRUCTION:
+ case EXCEPTION_IN_PAGE_ERROR:
+ case EXCEPTION_INVALID_DISPOSITION:
+ case EXCEPTION_NONCONTINUABLE_EXCEPTION:
+ case EXCEPTION_PRIV_INSTRUCTION:
+ case STATUS_UNWIND_CONSOLIDATE:
+ fIsCorruptedStateException = TRUE;
+ break;
+ }
+
+ return fIsCorruptedStateException;
+}
+```
+
+Let’s look at the description of our exceptional situations:
+
+| Error code | Description |
+|------------------------------------|------------------------------------------------------------------------------------------------|
+| STATUS_ACCESS_VIOLATION | A quite frequent error when you try to work with a memory range without access rights. Though memory is linear in terms of a process, it’s not possible to work with its entire range. You can use only those "portions” allocated by an operating system or ranges that you have access rights to (there are ranges owned by an operating system exclusively or available for code execution, yet not for reading) |
+| STATUS_STACK_OVERFLOW | Everybody knows this error: there is not enough memory on the thread stack to call another method |
+| EXCEPTION_ILLEGAL_INSTRUCTION | Another code, read by a processor from a method body, wasn’t recognized as an instruction |
+| EXCEPTION_IN_PAGE_ERROR | A thread attempted to work with a non-existing memory page |
+| EXCEPTION_INVALID_DISPOSITION | An exception handling mechanism returned a wrong handler. Such an exception should never appear in programs written in high-level languages (e.g. С++) |
+| EXCEPTION_NONCONTINUABLE_EXCEPTION | A thread attempted to continue the execution of a program after an exception prohibited the execution. This is not about `catch/fault/finally` blocks. It's rather about something like exception filters that allowed to fix an error (which caused an exception) and then tried to execute that code again |
+| EXCEPTION_PRIV_INSTRUCTION | An attempt to execute a privileged instruction of a processor |
+| STATUS_UNWIND_CONSOLIDATE | An exception connected with the stack unwind, which is beyond the scope of our discussion |
+
+Note that only two of these exceptions are worth catching: `STATUS_ACCESS_VIOLATION` and `STATUS_STACK_OVERFLOW`. Other errors are exceptional even for exceptional situations. They are rather fatal errors and we can’t consider them. So, let’s discuss only these two errors in detail.
+
+#### AccessViolationException
+
+This exception is bad news which you don’t want to get. But if you get it, it’s not clear what to do with it. `AccessViolationException` indicates that you "missed" a memory range allocated for an application and is thrown when you try to read from or write to a protected region of memory. The word "protected” rather denotes an attempt to work with a range of memory which hasn’t been allocated yet or which has been already cleared. Here I am not talking about a garbage collector that allocates and clears memory. It just assigns the chunks of allocated memory for your and its own needs. Memory has a layered structure in a way. First, there is the layer of memory management by a garbage collector. Next is the layer used by CoreCLR libraries to manage the allocation of memory which is followed by the layer used by OS to manage memory allocation from the pool of available fragments of linear address space. Thus, this exception appears when an application misses its memory range and attempts to work with a region which isn’t yet allocated or intended for this application. In this situation you don’t have many variants for analysis.
+
+ – If `StackTrace` goes deep into CLR, you are very unlucky, because it’s probably a core error. However, this almost never happens. You can bypass the error by updating the core version or act in another way.
+ – If `StackTrace` goes into the unsafe code of some library, you either got marshaling wrong or there is a serious error in the unsafe library. Check method arguments carefully: it’s possible that native method arguments have another bit size, another order or just another size. Check that structures are passed by value or by reference where appropriate.
+
+To catch this exception at the moment you should show a JIT-compiler it’s really necessary. Otherwise, it won’t be caught and you will get a broken application. However, you should catch this exception only if you can handle it properly: it may indicate a leak of memory if it was allocated by an unsafe method between calling this method and throwing `AccessViolationException`. At this point, an application may still function but its work may be incorrect because if you catch an error of a method call you will definitely try to call this method again. In this case, nobody knows what may go wrong: you can’t know how the state of an application was violated previously. However, if you still want to catch this exception, pay attention to the table of possible options for doing this in different versions of .NET Framework:
+
+.NET Framework version | AccessViolationExeception
+----------------------|-----------------------------------------------------------
+1.0 | NullReferenceException
+2.0, 3.5 | AccessViolation can be caught
+4.0+ | AccessViolation can be caught, but an adjustment is needed
+.NET Core | AccessViolation *can’t* be caught
+
+In other words, if you have a very old application, working under .NET Framework 1.0, ~~show it to me~~ you will get NRE which will be a sort of deception: you passed a pointer with a value greater than zero and got `NullReferenceException`. However, I think this behavior is justified: if you are in the world of managed code, you don’t want to learn the error types of unmanaged code, and NRE—which is essentially "a bad pointer to an object” in the world of .NET—is suitable here. However, things are not that simple. Users needed this type of exception in real life and it was introduced in .NET Framework 2.0. For years this was an exception you can catch. Then, it lost this capability, but a special structure appeared that allowed activating the catch option. This sequence of the CLR team decisions at every stage looks justified. See it for yourself:
+
+ - `1.0` Missing allocated memory ranges should be an exceptional situation because if an application works with an address it got it from some place. In the managed world this place is the `new` operator. In the unmanaged world, every piece of code can be the place for such an error. Though these two exceptions are opposed in terms of essence (NRE works with an uninitialized pointer and AVE works with an incorrectly initialized pointer), incorrectly initialized pointers don’t exist from the .NET ideology point of view. Both cases can be reduced to an incorrectly initialized pointer. So, let’s do it and throw `NullReferenceException` in both cases.
+ - `2.0` During the early stages of .NET Framework existence it turned out that there is more code which is inherited via COM libraries than native code: there is a huge code base of commercial components for networking, UI, DB, and other subsystems. It means that the possibility of getting `AccessViolationException` exactly is real: the wrong diagnostics of a problem can make it more expensive to catch. Therefore, `AccessViolationException` was introduced in .NET Framework.
+ - `4.0` .NET Framework took hold and squeezed low-level programming languages. The number of COM components decreased sharply: almost all major tasks are solved within the framework already and working with unsafe code is treated as something incorrect. In these conditions we can get back to the ideology, introduced in the framework from the very beginning: .NET is for .NET only. Unsafe code is not a norm but a least-evil state; therefore catching `AccessViolationException` contradicts the ideology of the “framework as a platform” notion, i.e. of a full-fledged simulated sandbox with its own rules. However, we still use this platform and have to catch this exception in many cases: we introduce a special catch mode only if a corresponding configuration is used.
+ - `.NET Core` The dream of the CLR team has come true: working with unsafe code beyond the law in .NET anymore, and therefore the existence of `AccessViolationException` is not legitimate even at the configuration level. .NET is mature enough to make its own rules. Now the existence of this exception in an application will crash it. That’s why any unsafe code, i.e. CLR itself, must be safe in terms of this exception. If it appears in an unsafe library, nobody will use it. It means that developers writing third-party components in unsafe languages should be careful and handle this exception on their side.
+
+This is how we can follow up how .NET has become a platform using the example of one exception: from playing by imposed rules to establishing its own rules.
+
+With all things said I should only show how to activate the handling of this exception in a particular method in `4.0+`. You should:
+
+ - add the following code to the `configuration/runtime` section: ``
+ - add two attributes for each method where it is *necessary* to handle `AccessViolationException`: `HandleProcessCorruptedStateExceptions` and `SecurityCritical`. These attributes allow activating the handling of Corrupted State Exceptions for _particular_ methods only. This procedure is particularly appropriate as you should be sure that you want to handle these exceptions and should know where: sometimes it is more appropriate to bring an application down.
+
+Let’s look at the following code to see the activating of the `CSE` handler and the example of a trivial handling:
+
+```csharp
+[HandleProcessCorruptedStateExceptions, SecurityCritical]
+public bool TryCallNativeApi()
+{
+ try
+ {
+ // Activating a method which can throw AccessViolationException
+ }
+ catch (Exception e)
+ {
+ // Logging, exit
+ System.Console.WriteLine(e.Message);
+ return false;
+ }
+
+ return true;
+}
+```
+
+#### StackOverflowException
+
+It is the last type of exception we should talk about. It appears when a memory array allocated for the stack runs out. We already discussed the structure of the stack in the corresponding chapter ([Thread stack](./ThreadStack.md)). So, here we will discuss only the error itself.
+
+When there is not enough memory for the thread stack (or the next memory range is occupied and we can’t allocate the next page of virtual memory) or a thread used the allowed memory range, there is an attempt to access the address space which is called Guard page. This range is actually a trap and doesn’t take any physical memory. Instead of real writing or reading, a processor calls a special abort which should request a new memory range from an OS to accommodate the growth of the stack. If the absolute maximum value is reached the OS generates `STATUS_STACK_OVERFLOW` exception instead of allocating a new range. This exception, forwarded in .NET via the `Structured Exception Handling` mechanism, destroys the current thread as inconsistent.
+
+Note that although this exception is `Corrupted State Exception`, you can’t catch it with `HandleProcessCorruptedStateExceptions`. I mean the following code won’t work:
+
+```csharp
+// Main.cs
+[HandleProcessCorruptedStateExceptions, SecurityCritical]
+static void Main()
+{
+ try
+ {
+ Recursive();
+ } catch (Exception exception)
+ {
+ Console.WriteLine("Catched Stack Overflow!");
+ }
+}
+
+static void Recursive()
+{
+ Recursive();
+}
+
+// app.config:
+
+
+
+
+
+```
+
+You can’t catch this exception because stack overflow can be caused by two reasons. The first is an intentional call of a recursive method which doesn’t control its own depth carefully. Here you may want to fix the situation by catching the exception. However, we therefore legalize this situation and allow it to happen again which means we are short-sighted, rather than careful. The second reason is accidental when `StackOverflowException` appears during a usual call. Just the depth of the stack at that moment was too critical. In this case, catching exceptions looks like something inappropriate at all: an application worked normally, everything was good and suddenly the legal call of a method with algorithms working correctly caused an exception, followed by unwinding stack up to the section of code that expects such behavior. Well... Once again: we expect that nothing will run in the next section as the stack will be out of memory. I think this is absurd.
diff --git a/book/en/Links.md b/book/en/Links.md
new file mode 100644
index 0000000..1cee78d
--- /dev/null
+++ b/book/en/Links.md
@@ -0,0 +1,6 @@
+# Links and references
+
+ 1. [CLR via C#](https://www.amazon.com/CLR-via-4th-Developer-Reference/dp/0735667454) by [Jeffrey Richter](https://github.com/JeffreyRichter)
+ 1. [Matt Warren blog](https://mattwarren.org/)
+ 1. [JetBrains Lifetime](https://www.jetbrains.com/help/resharper/sdk/Platform/Lifetime.html)
+ 1. [Pro .NET Memory Management](https://prodotnetmemory.com/) by [Konrad Kokosa](https://twitter.com/konradkokosa)
\ No newline at end of file
diff --git a/book/en/ThreadStack.md b/book/en/ThreadStack.md
new file mode 100644
index 0000000..4eeca59
--- /dev/null
+++ b/book/en/ThreadStack.md
@@ -0,0 +1,632 @@
+# A thread stack
+
+> [A link to the discussion](https://github.com/sidristij/dotnetbook/issues/58)
+
+## Basic structure, x86 platform
+
+There is a memory region that is rarely mentioned. However, it probably takes a primary role in the work of an application. This is the most frequently used and a quite limited region with instantaneous memory allocation and cleanup. This region is called "a thread stack". And since a pointer to a thread stack is encoded essentially by processor registers which are in the thread context, each thread has its own stack during execution. Why is it necessary?
+
+Let’s review a simple example of code:
+
+```csharp
+void Method1()
+{
+ Method2(123);
+}
+
+void Method2(int arg)
+{
+ // ...
+}
+```
+
+Nothing significant happens in this code. However, we wouldn’t skip it and instead will examine it carefully . When any `Method1` calls any `Method2`, any such call (not only in .NET but in other platforms too) goes through the following steps:
+
+ 1. First, a JIT-compiled code saves method parameters to the stack (starting with a third one). The first two parameters are passed through registers. It is important to remember that the first passed parameter of instance methods is a pointer to an object that the method works with, i.e. `this` pointer. So, in these (almost all) cases only one parameter goes to registers, while the stack is used for the rest.
+ 2. Next, a compiler puts the `call` method call instruction which puts the method return address to the stack. This is the address of an instruction that follows the `call` instruction. Thus, each method knows where it should return to, so that calling code could continue its work.
+ 3. After passing all parameters and calling the method we should understand how to restore the stack if we exiting the method and don’t want to calculate bytes occupied in the stack. To do this, we save the value of the EBP register, which always stores a pointer to the beginning of a current stack frame (i.e. a range where the information for a specific called method is stored). Saving the value of this register at each call, we create a single linked list of stack frames. Note that in fact they follow each other precisely without gaps. However, to simplify memory cleanup from a frame and to debug an application (a debugger uses these pointers to show Stack Trace) a single linked list is built.
+ 4. The last thing to do during the method call is to allocate a memory range for local variables. As a compiler knows in advance how much memory is needed, it allocates it immediately by moving a pointer to the top of the stack (SP/ESP/RSP) by a necessary number of bytes.
+ 5. The fifth step is the execution of a method code, i.e. useful operations.
+ 6. While exiting the method, the top of the stack is restored from the EBP which is the place where the beginning of a current stack frame is stored.
+ 7. And finally, we use `ret` instruction to exit the method. It takes the return address that was put on the stack by the `call` instruction and makes `jmp` based on this address.
+
+The figure shows these processes:
+
+![](../imgs/ThreadStack/AnyMethodCall.png)
+
+Note that the stack grows starting from higher and finishing with lower addresses, i.e. in reverse.
+
+Looking at this you may think that if not the majority but at least the half of all operations a processor deals with are to service the structure of a program and not its payload. I mean servicing method calls, checking if one type can be cast to another one, compiling Generic variations, searching methods in interface tables... Especially if we remember that the major part of the modern code is written from the perspective of working through interfaces and implementing a multitude of small methods, each performing its own functions... The work often involves base types and casting types to an interface or to an inheritor. With all these input conditions you might make a conclusion about the lavishness of infrastructure code. The only thing I can tell you is that compilers, including JIT, have a lot of techniques that allow them to create more productive code. Where possible they insert an entire method body instead of calling it and rather than searching a method in an interface VSD, compilers call it directly. The saddest thing is that it’s hard to measure the load on infrastructure: it’s necessary that a JITter or any other compiler inserts some metrics before and after infrastructure code running. That is before a method call and after initializing a stack frame inside this method. Before and after exiting a method. Before compilation and after it. And so on. But let’s not talk about sad things, but discuss what we can do with the obtained information.
+
+## Some words about exceptions in terms of the x86 platform
+
+If we look inside the code of methods, we will see another structure, working with a thread stack. See for yourself:
+
+```csharp
+void Method1()
+{
+ try
+ {
+ Method2(123);
+ } catch {
+ // ...
+ }
+}
+
+void Method2(int arg)
+{
+ Method3();
+}
+
+void Method3()
+{
+ try
+ {
+ //...
+ } catch {
+ //...
+ }
+}
+```
+
+If an exception appears in any method, called from `Method3`, the control will be returned to the `catch` block of `Method3`. If handling of this exception failed, its handling will start in `Method1`. However, if nothing happens, `Method3` will finish its work and control will be passed to `Method2` where an exception can also be thrown. However, naturally, it will be handled in `Method1` and not in `Method3`. The idea of such convenient automation is that data structures that form the chains of exception handlers are also put to the method stack frame where they are declared. We will talk about exceptions separately. Here, I should only note that the model of exceptions in .NET Framework CLR is different from Core CLR. CoreCLR has to be different for various platforms. That’s why it has a distinctive model of exceptions which is offered through PAL (Platform Adaption Layer) in different implementations depending on the platform. Big .NET Framework CLR doesn’t need this as it exists in Windows ecosystem that for many years has been using a common mechanism for exception handling called SEH (Structured Exception Handling). This mechanism is used by almost all programming languages (during final compilation) because it ensures end-to-end exception handling between modules, written in different languages. It works like this:
+
+ 1. When a program enters a `try` block, the mechanism puts the structure to the stack , the first field of which indicates a previous block of exception handling (e.g. a calling method which also has try-catch), the type of the block, the exception code and the handler address.
+ 1. The address of highest-priority exception handler for the Thread Environment Block (TEB)—basically, a thread context—within the chain of handlers is changed to the address created by us. Thus, we added our block to the chain.
+ 1. After the execution of the `try` block, the mechanism performs a reverse operation: the previous highest-priority handler is registered in TEB, thus pushing our handler out of the chain.
+ 1. If an exception occurs, the highest-priority handler is taken from TEB and handlers in the chain are called in turn. Each of them checks whether this exception matches its particular type. If it does, the handling block is activated (e.g., `catch`).
+ 1. The address of SEH structure which was in the stack BEFORE the exception handler method is restored in TEB.
+
+As you can see, it’s not so difficult. However, all this information also exists on the stack.
+
+## Basic information about x64 and AMD64 platforms [In Progress]
+
+> TODO
+
+## Basic information about exception handling on x64 and AMD64 platforms [In Progress]
+
+> TODO
+
+## A little about the imperfection of a thread stack
+
+Let’s think about a security issue and possible problems that can appear theoretically. To do this, let’s look again at the structure of a thread stack which is essentially a usual array. The memory range in which frames are built grows from the end to the beginning. It means that more recent frames are assigned initial addresses. As it was said earlier frames are connected through a single linked list. This is because the size of a frame is not fixed and should be "read" by any debugger. A processor, in this case, doesn’t delineate frames from each other: any method can read the entire memory region. And assuming that we are in virtual memory that is divided into ranges of really allocated memory, we can use special WinAPI function and any address on the stack to get an allocated memory range where this address exists. Dealing with a single linked list is easy:
+
+```csharp
+ // the variable is on the stack
+ int x;
+
+ // Get information about a memory range, allocated for the stack
+ MEMORY_BASIC_INFORMATION *stackData = new MEMORY_BASIC_INFORMATION();
+ VirtualQuery((void *)&x, stackData, sizeof(MEMORY_BASIC_INFORMATION));
+```
+
+Thus we can get and modify all data which exists as local variables in methods that called us. If an application doesn’t adjust the settings of a sandbox used for calling third-party libraries, which enhance the functionality of the application, the third-party library can get data, even if the API you are using doesn’t provide for it. This technique may seem purely artificial, but in the world of C/C++ where there is no AppDomain with configured rights, the attack on the stack is a typical way to hack applications. Moreover, we can use reflection to look at the type we need, replicate its structure on our side and replace the VMT address with our address, using the reference from the stack to an object, and redirect the whole work with a particular instance to our side. By the way, SEH is also actively used to hack applications. Using an exception handler, you can change its address and make OS execute malware code. However, the solution is simple: always adjust the settings of a sandbox when you want to work with libraries, extending the functionality of your application. I mean different plug-ins, add-ons and other extensions.
+
+## A big example: thread cloning on х86 platform
+
+To memorize the details of what we have read, we should look at the same topic from different perspectives. Which example can you really design for a thread stack? Call a method from another? Magic... Of course not: we do it many times every day. Instead, we will copy the execution thread. It means we will ensure that after calling a particular method we would get two threads instead of one: our thread and a new one that continues to execute code from the point of the clone method call as if it got here itself. It will look like this:
+
+```csharp
+void MakeFork()
+{
+ // To ensure everything was cloned, we create local variables:
+ // Their values in the new thread should be the same as in the parent one
+ var sameLocalVariable = 123;
+ var sync = new object();
+
+ // Clocking time
+ var stopwatch = Stopwatch.StartNew();
+
+ // Cloning a thread
+ var forked = Fork.CloneThread();
+
+ // From now on the code is executed by two threads.
+ // forked = true is for the child thread, false is for the parent one
+ lock(sync)
+ {
+ Console.WriteLine("in {0} thread: {1}, local value: {2}, time to enter = {3} ms",
+ forked ? "forked" : "parent",
+ Thread.CurrentThread.ManagedThreadId,
+ sameLocalVariable,
+ stopwatch.ElapsedMilliseconds);
+ }
+
+ // When exiting the method the parent thread will return control to the method
+ // that called MakeFork(), i.e. will continue work as usual,
+ // and the execution of the child thread will finish.
+}
+
+// Sample output:
+// in forked thread: 2, local value: 123, time to enter = 2 ms
+// in parent thread: 1, local value: 123, time to enter = 2 ms
+```
+
+Admit, this is an interesting concept. Of course, you may argue a lot about the appropriateness of such actions, but the main task of this example is to put a fine point to the explanations of working principles for this data structure. How do we clone threads? To answer this question, we should understand what things characterize a thread. Actually, it is characterized by the following structures and data regions:
+
+ 1. A set of CPU registers. All registers define the state of an execution thread: starting from the address of a current execution instruction to the addresses of a thread stack and the data it uses.
+ 1. [Thread Environment Block](https://en.wikipedia.org/wiki/Win32_Thread_Information_Block) or TIB/TEB stores system information about a thread, including the addresses of exception handlers.
+ 1. A thread stack, the address of which is defined by SS:ESP registers.
+ 1. The platform context of a thread that contains local data for the thread (a reference comes from TIB).
+
+And surely something else that we don’t know. But we don’t have to know everything in case of an example: this code is not for production use, but to understand the idea. That’s why it takes into account only the most important things. If we want this code to function in its basic form, we need to copy a set of registers to a new thread (changing SS:ESP as the stack will be new) and edit the stack itself, so it would contain what we need.
+
+Well, if a thread stack determines which methods were called and which data they use, it means that theoretically by changing these structures you can change local variables of methods and eliminate the call of some method from the stack, replace the method with another or your own method in any place of a chain. Ok, we have decided on this. Now, let’s look at some pseudocode:
+
+```csharp
+void RootMethod()
+{
+ MakeFork();
+}
+```
+
+When `MakeFork()` is called, what do we expect in terms of stack traces? We expect that nothing will change in a parent thread, and a child thread will be taken from the thread pool (for speed). The call of `MakeFork` method with its local data will be imitated in this thread, and the execution of code will continue not from the beginning of the method, but from the point after `CloneThread` call. Thus, the stack trace in our imagination will look like:
+
+```csharp
+// Parent Thread
+RootMethod -> MakeFork
+
+// Child Thread
+ThreadPool -> MakeFork
+```
+
+What do we have from the beginning? We have our thread. We also have an opportunity to create a new thread or schedule a task to an object pool, executing our code there. We also understand that the information about nested calls is stored in a call stack and we can manipulate it (for example, using C++/CLI). Also, if we implement agreements and put to the stack top the return address for the `ret` instruction as well as the EBP register value and allocate space for locals (if necessary), we can imitate the method call. Manual entries to a thread stack can be made from C#, but we will need registers and will have to use them very carefully. Thus, we can’t do without turning to C++. Here CLI/C++ comes to help us the first time in life (at least for me). It allows writing mixed code: a part of instructions is written in .NET, another part in C++, and the third part can even be written in assembly language. That’s exactly what we need.
+
+So, how will a thread stack look like, when our code will call `MakeFork` which will call `CloneThread` which will go to the unmanaged world of CLI/C++ and call the cloning method (the implementation itself) there? Let’s look at the figure (note once again that the stack is growing from higher to lower addresses. From left to right):
+
+![](../imgs/ThreadStack/step1.png)
+
+In order not to move the whole thing from figure to figure, let’s simplify it and keep only what we need:
+
+![](../imgs/ThreadStack/step2.png)
+
+When we create a thread or take a made-up thread from the pool, another stack appears in our figure. However, this stack is not initialized yet.
+
+![](../imgs/ThreadStack/step3.png)
+
+Now, we need to imitate the running of `Fork.CloneThread()` method in a new thread. To do this, we need to add a series of frames to the end of its thread stack: as if CLI/C++ method was called by a managed wrapper through a C++ wrapper from `Fork.CloneThread()` which was in turn called from a delegate transferred to `ThreadPool`. To do this, we will copy a necessary stack region to an array (note that the copies of EBP registers, used for building a frame chain, in the cloned region “overlook” the former region):
+
+![](../imgs/ThreadStack/step4.png)
+
+To ensure the integrity of stack after copying the previously cloned region, we estimate in advance which addresses will be taken by EBP fields in a new place and change them to copies immediately:
+
+![](../imgs/ThreadStack/step5.png)
+
+The last step is to copy our array to the end of the child thread stack carefully, using a minimal number of registers, and move our ESP and EBP registers to new places. We imitated the call of all these methods in terms of the stack:
+
+![](../imgs/ThreadStack/step6.png)
+
+But not in terms of code. In terms of code, we need to access those methods that we have just created. The simplest way is to imitate the exit from a method: restore `ESP` from `EBP`, put into `EBP` what it points to and call `ret` instruction, initiating the exit from a supposedly called C++ method for thread cloning. This will end up with returning to a real wrapper of the CLI/C++ call, which will pass control to `MakeFork()`, but in a child thread. This technique works.
+
+Now, let’s look at another code. The first thing we do is the opportunity for CLI/C++ code to create a .NET thread. To do this, we need to create in .NET:
+
+```csharp
+extern "C" __declspec(dllexport)
+void __stdcall MakeManagedThread(AdvancedThreading_Unmanaged *helper, StackInfo *stackCopy)
+{
+ AdvancedThreading::Fork::MakeThread(helper, stackCopy);
+}
+```
+
+Don’t yet pay attention to the types of parameters. They are required to transfer information about which stack region should be copied from a parent stack to a child one. The method of thread creation wraps the call of unmanaged method in a delegate, transfers data and put the delegate into a queue for processing by the thread pool.
+
+```csharp
+[MethodImpl(MethodImplOptions::NoInlining | MethodImplOptions::NoOptimization | MethodImplOptions::PreserveSig)]
+static void MakeThread(AdvancedThreading_Unmanaged *helper, StackInfo *stackCopy)
+{
+ ForkData^ data = gcnew ForkData();
+ data->helper = helper;
+ data->info = stackCopy;
+
+ ThreadPool::QueueUserWorkItem(gcnew WaitCallback(&InForkedThread), data);
+}
+
+[MethodImpl(MethodImplOptions::NoInlining | MethodImplOptions::NoOptimization | MethodImplOptions::PreserveSig)]
+static void InForkedThread(Object^ state)
+{
+ ForkData^ data = (ForkData^)state;
+ data->helper->InForkedThread(data->info);
+}
+```
+
+And at last the cloning method itself (its .NET part actually):
+
+```csharp
+[MethodImpl(MethodImplOptions::NoInlining | MethodImplOptions::NoOptimization | MethodImplOptions::PreserveSig)]
+static bool CloneThread()
+{
+ ManualResetEvent^ resetEvent = gcnew ManualResetEvent(false);
+ AdvancedThreading_Unmanaged *helper = new AdvancedThreading_Unmanaged();
+ int somevalue;
+
+ // *
+ helper->stacktop = (int)(int *)&somevalue;
+ int forked = helper->ForkImpl();
+ if (!forked)
+ {
+ resetEvent->WaitOne();
+ }
+ else
+ {
+ resetEvent->Set();
+ }
+ return forked;
+}
+```
+
+To understand where this method exists in a chain of stack frames, we have saved the address of a stack variable (*). We will use this address in a cloning method, which will be discussed further. Also, for you to understand what is it all about I will show the code of a structure that is necessary to store information about the stack copy:
+
+```csharp
+public class StackInfo
+{
+public:
+ // The copy of registers values
+ int EAX, EBX, ECX, EDX;
+ int EDI, ESI;
+ int ESP;
+ int EBP;
+ int EIP;
+ short CS;
+
+ // The address of a stack copy
+ void *frame;
+
+ // The size of a copy
+ int size;
+
+ // The address ranges of the original stack are necessary
+ // to replace the addresses on the stack, if exist, with new ones
+ int origStackStart, origStackSize;
+};
+```
+
+The work of the algorithm is divided into two stages: firstly, we prepare data in a parent thread to represent necessary stack frames in a child thread. Secondly, the child thread data is restored and overlays its own execution thread stack, thus imitating the calls of methods that were never called.
+
+### A method of preparation for copying
+
+I will describe the code step-by-step. To do so, I will divide the entire code into parts and comment on each part separately. Let’s start.
+
+External code calls `Fork.CloneThread()` through an internal wrap over the unmanaged code and through a series of additional methods if code runs while debugging (so-called debugger assistants). That’s why we saved the address of a variable on the stack in the .NET part: for a C++ method this address is a kind of label, and now we know which region of the stack we can copy.
+
+```csharp
+int AdvancedThreading_Unmanaged::ForkImpl()
+{
+ StackInfo copy;
+ StackInfo* info;
+```
+
+First of all, before any operation, we copy the registers locally to avoid corrupting them. Additionally, we need to save the address of code, where we will make `goto` when the stack will be imitated in a child thread and we will have to exit `CloneThread` in the child thread. As an "exit point” we choose `JmpPointOnMethodsChainCallEmulation` on purpose, because after saving this address "for the future” we additionally put number 0 on the stack.
+
+```csharp
+ // Save ALL registers
+ _asm
+ {
+ mov copy.EAX, EAX
+ mov copy.EBX, EBX
+ mov copy.ECX, ECX
+ mov copy.EDX, EBX
+ mov copy.EDI, EDI
+ mov copy.ESI, ESI
+ mov copy.EBP, EBP
+ mov copy.ESP, ESP
+
+ // Save CS:EIP for far jmp
+ mov copy.CS, CS
+ mov copy.EIP, offset JmpPointOnMethodsChainCallEmulation
+
+ // Save mark for this method to indicate the point it was called from
+ push 0
+ }
+```
+
+Then, after `JmpPointOnMethodsChainCallEmulation` we retrieve this number from the stack and check if it’s still `0`. If it is, then we are still in the same thread which means we have a lot of things to do and we go to `NonCloned`. If the value is `1`, it means that the child thread added all necessary components to the thread stack, put number `1` on the stack and made `goto` to this point (note that `goto` is made from another method). This means it’s time to exit `CloneThread` from the child tread, the call of which was imitated.
+
+```csharp
+JmpPointOnMethodsChainCallEmulation:
+
+ _asm
+ {
+ pop EAX
+ cmp EAX, 0
+ je NonCloned
+
+ pop EBP
+ mov EAX, 1
+ ret
+ }
+NonCloned:
+```
+
+OK, we made sure that we have a lot to do and we need to prepare data for the child thread. We will work with the structure of previously saved registers not to go to the assembler language level again. First, let’s get the EBP register value out of this structure. Essentially, the register is the "Next” field in a single linked list of stack frames. Following the address that exists there, we access the frame of a method which called us. If we use the address which is contained in the first field there, we will access an even lower frame. This way we can get to the managed part of `CloneThread` as we saved the address of the variable in its stack frame and know exactly where to stop. The cycle shown below solves exactly this task.
+
+```csharp
+ int *curptr = (int *)copy.EBP;
+ int frames = 0;
+
+ //
+ // Calculate frames count between a current call and Fork.CloneTherad() call
+ //
+ while ((int)curptr < stacktop)
+ {
+ curptr = (int*)*curptr;
+ frames++;
+ }
+```
+
+Because we got the address of `CloneThread` managed method frame beginning we know how much we should copy to imitate the call of `CloneThread` from `MakeFork`. However, since we also need `MakeFork` (we need to exit into it exactly), it’s necessary to make another transition in a single linked list: `*(int *)curptr`. After that, we create an array to save the stack by simple copying.
+
+```csharp
+ //
+ // We need to copy the stack part from our method to a user code method including its locals on the stack
+ //
+ int localsStart = copy.EBP; // our EBP points to the EBP value for the parent method + saved ESI, EDI
+ int localsEnd = *(int *)curptr; // points to the end of a user's method's locals (additional leave)
+
+ byte *arr = new byte[localsEnd - localsStart];
+ memcpy(arr, (void*)localsStart, localsEnd - localsStart);
+```
+
+Another task to be solved is updating the addresses of variables, which are still on the stack and point to it. To solve this task, we get a range of addresses which were allocated by the operating system for the thread stack. We save the retrieved information and start the second part of a cloning process, preparing a delegate for the thread pool:
+
+```csharp
+ // Get information about stack pages
+ MEMORY_BASIC_INFORMATION *stackData = new MEMORY_BASIC_INFORMATION();
+ VirtualQuery((void *)copy.EBP, stackData, sizeof(MEMORY_BASIC_INFORMATION));
+
+ // fill StackInfo structure
+ info = new StackInfo(copy);
+ info->origStackStart = (int)stackData->BaseAddress;
+ info->origStackSize = (int)stackData->RegionSize;
+ info->frame = arr;
+ info->size = (localsEnd - localsStart);
+
+ // call managed ThreadPool.QueueUserWorkitem to make fork
+ MakeManagedThread(this, info);
+
+ return 0;
+}
+```
+
+### A method of restoration from a copy
+
+This method is consequentially called after the previous one quits running: we obtained a copy of the parent thread stack region and a full set of its registers. Our task is to add all calls, copied from the parent thread, to our thread, taken from a thread pool, as if we made these calls ourselves. After finishing its work `MakeFork` of the child thread will return to this method which will release the thread and get it back to the pool when work is over.
+
+```csharp
+void AdvancedThreading_Unmanaged::InForkedThread(StackInfo * stackCopy)
+{
+ StackInfo copy;
+```
+
+First, we save the values of working registers to restore them painlessly after `MakeFork` finishes its work. To have a minimal impact on registers in the future, we unload the parameters transferred to us on our stack. They will be accessed through `SS:ESP` only , which will be predictable for us.
+
+```csharp
+ short CS_EIP[3];
+
+ // Save original registers to restore
+ __asm pushad
+
+ // safe copy w-out changing registers
+ for(int i = 0; i < sizeof(StackInfo); i++)
+ ((byte *)©)[i] = ((byte *)stackCopy)[i];
+
+ // Setup FWORD for far jmp
+ *(int*)CS_EIP = copy.EIP;
+ CS_EIP[2] = copy.CS;
+```
+
+Our next task is to correct the `EBP` values in a stack copy that form a single linked list of frames, so they would reflect their future positions. To do this, we calculate the offset between the address of our thread stack and the parent thread stack, the offset between the copy of the parent thread stack range and the parent thread itself.
+
+```csharp
+ // calculate ranges
+ int beg = (int)copy.frame;
+ int size = copy.size;
+ int baseFrom = (int) copy.origStackStart;
+ int baseTo = baseFrom + (int)copy.origStackSize;
+ int ESPr;
+
+ __asm mov ESPr, ESP
+
+ // target = EBP[ - locals - EBP - ret - whole stack frames copy]
+ int targetToCopy = ESPr - 8 - size;
+
+ // the offset between the parent stack and the current stack;
+ int delta_to_target = (int)targetToCopy - (int)copy.EBP;
+
+ // the offset between the parent stack start and its copy;
+ int delta_to_copy = (int)copy.frame - (int)copy.EBP;
+```
+
+Using this data we follow the copy of the stack within a cycle and correct the addresses to their future values.
+
+```csharp
+ // In the stack copy we have many saved EPBs which form a single linked list.
+ // We need to fix the copy to make these pointers appropriate for our thread stack.
+ int ebp_cur = beg;
+ while(true)
+ {
+ int val = *(int*)ebp_cur;
+
+ if(baseFrom <= val && val < baseTo)
+ {
+ int localOffset = val + delta_to_copy;
+ *(int *)ebp_cur += delta_to_target;
+ ebp_cur = localOffset;
+ }
+ else
+ break;
+ }
+```
+
+When the single linked list is corrected, we should update the values of registers in their copy, so that if it contains references to the stack, they will be updated too. The algorithm here is not precise at all. Because if an inappropriate value from the stack address range appears there by some reason, it will be corrected by mistake. However, our task is just to understand the functioning of a thread stack and that’s why this technique is suitable for this purpose.
+
+```csharp
+ CHECKREF(EAX);
+ CHECKREF(EBX);
+ CHECKREF(ECX);
+ CHECKREF(EDX);
+
+ CHECKREF(ESI);
+ CHECKREF(EDI);
+```
+
+Now, the most important part. When we add the copy of the parent stack to the end of our stack, everything will be OK until `MakeFork` in the child thread tries to exit (make `return`). We need to show it where it should exit to. To do this, we also imitate the call of `MakeFork` itself from this method. We put the address of `RestorePointAfterClonedExited` label on the stack as if the `call` processor instruction puts the return address and the current `EBP` to the stack, imitating the building of a single linked list of method frames chains. Then we put a copy of the parent stack on the stack using the usual `push` operation, thus representing all methods, called in the parent stack from the `MakeFork` method including itself. The stack is ready!
+
+```csharp
+ // prepare for __asm nret
+ __asm push offset RestorePointAfterClonedExited
+ __asm push EBP
+
+ for(int i = (size >> 2) - 1; i >= 0; i--)
+ {
+ int val = ((int *)beg)[i];
+ __asm push val;
+ };
+```
+
+Next, we should restore registers.
+
+```
+ // restore registers, push 1 for Fork() and jmp
+ _asm {
+ push copy.EAX
+ push copy.EBX
+ push copy.ECX
+ push copy.EDX
+ push copy.ESI
+ push copy.EDI
+ pop EDI
+ pop ESI
+ pop EDX
+ pop ECX
+ pop EBX
+ pop EAX
+```
+
+Now, it’s time to remember the strange code where we put `0` on stack and then checked for `0`. In this thread, we put `1` and make a long `jmp` to the `ForkImpl` method code. Because we are exactly there according to the stack, but in fact, we are still here. When we get there `ForkImpl` will recognize the change of a thread and will exit to the `MakeFork` method, which will get to the `RestorePointAfterClonedExited` after finishing its work because earlier we imitated the call of `MakeFork` from this point. By restoring the registers to the state of “just called from `ThreadPool`” we finish work, giving the thread to the pool.
+
+```csharp
+ push 1
+ jmp fword ptr CS_EIP
+ }
+
+RestorePointAfterClonedExited:
+
+ // Restore original registers
+ __asm popad
+ return;
+ }
+```
+
+Let’s check. This screenshot is made before a thread cloning call:
+
+![](../imgs/ThreadStack/ForkBeforeEnter.png)
+
+This screenshot is made after:
+
+![](../imgs/ThreadStack/ForkAfterEnter.png)
+
+As we see now, there are two threads in `ForkImpl` instead of one. Both of them exited this method.
+
+# A few words about a lower level
+
+If we look at an even lower level, we will recall that the memory is, in fact, virtual and it is divided into 8 and 4 Kb pages. Each of these pages can either exist or not exist physically. If it exists, it can be mapped to a file or real RAM. It is exactly this virtualization mechanism that allows applications to have separate memory and provides security levels between an application and an operating system. What does this have to do with a thread stack? As any other random access memory of an application a thread stack is its part and also consists of 4 and 8 Kb pages. On the sides of a range allocated for the stack, there are two pages. An attempt to access these pages will produce an exception, notifying an OS that an application tries to address an unallocated memory range. The actually allocated ranges inside this region are those pages which the application addressed: if the application allocates 2 Mb of memory for a thread, that doesn’t mean they will be allocated straight away. They will be allocated on demand: if a thread stack will grow up to 1 Mb, that will mean that the application got exactly 1 Mb of RAM for the stack.
+
+When an application allocates memory for local variables, two things happen: the value of ESP register grows and the memory for variables is set to null. That’s why when you write a recursive method which enters infinite recursion, you will get `StackOverflowException`: acquiring the entire memory allocated for the stack (the entire available region), you will run into a special Guard Page the access to which will make an operating system initiate `StackOverflow` at the OS level. It will pass to .NET and will be caught resulting in `StackOverflowException` for a .NET application.
+
+# The memory allocation on the stack: `stackalloc`
+
+In C# there is a quite interesting and rarely used keyword `stackalloc`. It is so rarely used in code (I’d rather say "never" instead of "rarely") that it’s hard to find a suitable example of its use and it’s even harder to create an example because if it’s seldom used the experience of working with it is too little. Why? Because for those who try to figure out what this instruction does, `stackalloc` becomes more threatening than useful, as the dark side of `stackalloc` is unsafe code. The result it returns is not a managed pointer: the value is a usual pointer to the range of unprotected memory. And if you make an entry at this address after a method finishes its work, you will start writing to local variables of some method or will overwrite the return address from the method, and an application will finish work with an error. However, our task is to get to hidden corners and figure out what they hide. Also, we should understand that this instrument was created on purpose and not just to spill our water and slip on it. Quite opposite: we were giving this instrument to use it and create really quick software. Did I inspire you? Then, let’s start.
+
+To find the appropriate use cases for this keyword, we should turn to its authors, i.e. Microsoft and see how they use it. We can do this with the help of a full-text search in the [coreclr repository](https://github.com/dotnet/coreclr). Besides different tests of the keyword itself, we will find no more than 25 use cases of this keyword in the library code. I hope I motivated you enough in the previous paragraph and you won’t stop reading my work when you see such a small number. To say the truth, the CLR team is more far-sighted and professional than the .NET Framework team. And if they created something, it should help us a lot. But if it is not used in .NET Framework... Well, we can suppose that not all engineers there know there is such a powerful optimization instrument. Otherwise, its would be more widely used.
+
+**Interop.ReadDir class**
+[/src/mscorlib/shared/Interop/Unix/System.Native/Interop.ReadDir.cs](https://github.com/dotnet/coreclr/blob/b29f6328510207970763580d6f4db864e4b198af/src/mscorlib/shared/Interop/Unix/System.Native/Interop.ReadDir.cs#L71-L83)
+
+```csharp
+unsafe
+{
+ // s_readBufferSize is zero when the native implementation does not support reading into a buffer.
+ byte* buffer = stackalloc byte[s_readBufferSize];
+ InternalDirectoryEntry temp;
+ int ret = ReadDirR(dir.DangerousGetHandle(), buffer, s_readBufferSize, out temp);
+ // We copy data into DirectoryEntry to ensure there are no dangling references.
+ outputEntry = ret == 0 ?
+ new DirectoryEntry() { InodeName = GetDirectoryEntryName(temp), InodeType = temp.InodeType } :
+ default(DirectoryEntry);
+
+ return ret;
+}
+```
+
+What is `stackalloc` used here for? As we see the code goes to the unsafe method after memory allocation to fill the created buffer with data. That means a memory range that the unsafe method needs for a record is allocated right on the stack dynamically. It’s a great optimization, given that alternatives are to get a memory range from Windows or fixed (pinned) .NET array that besides burdening the heap also burdens the GC because the array is fixed, so the GC wouldn’t move it when accessing its data. By allocating memory on the stack we risk nothing: the allocation is almost instantaneous and we can easily fill it with data and exit this method. And together with the exit from the method, its stack frame will disappear. As a result, we save a lot of time.
+
+Let’s look at another example:
+
+**Class Number.Formatting::FormatDecimal**
+[/src/mscorlib/shared/System/Number.Formatting.cs](https://github.com/dotnet/coreclr/blob/efebb38f3c18425c57f94ff910a50e038d13c848/src/mscorlib/shared/System/Number.Formatting.cs#L287-L311)
+
+```csharp
+public static string FormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info)
+{
+ char fmt = ParseFormatSpecifier(format, out int digits);
+
+ NumberBuffer number = default;
+ DecimalToNumber(value, ref number);
+
+ ValueStringBuilder sb;
+ unsafe
+ {
+ char* stackPtr = stackalloc char[CharStackBufferSize];
+ sb = new ValueStringBuilder(new Span(stackPtr, CharStackBufferSize));
+ }
+
+ if (fmt != 0)
+ {
+ NumberToString(ref sb, ref number, fmt, digits, info, isDecimal:true);
+ }
+ else
+ {
+ NumberToStringFormat(ref sb, ref number, format, info);
+ }
+
+ return sb.ToString();
+}
+```
+
+This is an example of number formatting which is based on an even more interesting example of a class [ValueStringBuilder](https://github.com/dotnet/coreclr/blob/efebb38f3c18425c57f94ff910a50e038d13c848/src/mscorlib/shared/System/Text/ValueStringBuilder.cs), working on the basis of `Span`. The essence of this code section is that it doesn’t use the allocation of memory for the character accumulation buffer to collect the textual representation of a formatted number as quickly as possible. This wonderful code allocates memory right in the method stack frame, ensuring no need for the GC to collect garbage in StringBuilder instances if the method worked on its basis. The method run time also decreases: the memory allocation in the heap also takes time. Besides, using `Span` type instead of bare pointers make the work of `stackalloc` based code look safer.
+
+Also, before making conclusions I should show what you mustn’t do. In other words, I will show which code can work well but will fail at the worst possible time. Let’s look at the example:
+
+```csharp
+void GenerateNoise(int noiseLength)
+{
+ var buf = new Span(stackalloc int[noiseLength]);
+ // generate noise
+}
+```
+
+This little code can cause a lot of troubles: you can’t just pass the size to allocate memory on the stack from the outside. If you need a size which is set externally, accept the buffer itself:
+
+```csharp
+void GenerateNoise(Span noiseBuf)
+{
+ // generate noise
+}
+```
+
+This code is more informative as it makes a user think and be careful when choosing numbers. In case of bad luck, the first variant can produce `StackOverflowException` with a method not being quite deep into a thread stack: it is enough to pass a big number as a parameter. The second variant when you can actually accept the size is when this method is called in specific cases and the calling code "knows" the working algorithm of this method. Without knowing the internal structure of a method there is no specific idea about the possible range for `noiseLength` and as a result errors can occur.
+
+The second example which I see is that if we misdetermined the size of a buffer which we allocated on the stack and we don’t want to decrease the efficiency of work, we can take a number of ways: allocate more memory on the stack or allocate it in the heap. The second variant will probably be more appropriate in most cases (this is what was done in the case of `ValueStringBuffer`) as it is safer in respect of getting `StackOverflowException`.
+
+## Conclusions on `stackalloc`
+
+So, what is `stackalloc` best used for?
+
+ - To work with unmanaged code when it is necessary to fill some data buffer with an unmanaged method or accept some data buffer from an unmanaged method which will be used during the method body lifetime.
+
+ - For methods that need an array, but again while these methods work. The example with formatting is very good: this method can be called too often to avoid allocating temporary arrays in the heap.
+
+The use of this allocator can significantly increase the performance of your applications.
+
+# Conclusions on section
+
+Of course in general terms, we don’t need to edit the stack in production code: only if we want to spend our time solving an interesting problem. However, understanding its structure shows us that it’s apparently simple to edit it and get data from it. I mean if you create an API to extend the functionality of your application and if this API doesn’t give access to some data, that doesn’t mean that this data can’t be accessed. That’s why always check that your application is hack proof.
\ No newline at end of file
diff --git a/book/en/readme.md b/book/en/readme.md
index 0cee9e9..4f0b042 100644
--- a/book/en/readme.md
+++ b/book/en/readme.md
@@ -1,6 +1,5 @@
![](../../bin/BookCover.png)
-
# Table of contents
1. Common Language Runtime
@@ -33,7 +32,7 @@
1. [Introduction to exceptional situations](./ExceptionalFlow/1-Exceptions-Intro.md)
2. [Architecture](./ExceptionalFlow/2-Exceptions-Architecture.md)
3. [Exceptions events](./ExceptionalFlow/3-Exceptions-Events.md)
- 4. Types of exceptional situations
+ 4. [Types of exceptional situations](./ExceptionalFlow/4-Exceptions-Types.md)
# License
diff --git a/book/ru/ExceptionalFlow/1-Exceptions-Intro.md b/book/ru/Execution/2-ExceptionalFlow/1-Exceptions-Intro.md
similarity index 100%
rename from book/ru/ExceptionalFlow/1-Exceptions-Intro.md
rename to book/ru/Execution/2-ExceptionalFlow/1-Exceptions-Intro.md
diff --git a/book/ru/ExceptionalFlow/2-Exceptions-Architecture.md b/book/ru/Execution/2-ExceptionalFlow/2-Exceptions-Architecture.md
similarity index 100%
rename from book/ru/ExceptionalFlow/2-Exceptions-Architecture.md
rename to book/ru/Execution/2-ExceptionalFlow/2-Exceptions-Architecture.md
diff --git a/book/ru/ExceptionalFlow/3-Exceptions-Events.md b/book/ru/Execution/2-ExceptionalFlow/3-Exceptions-Events.md
similarity index 100%
rename from book/ru/ExceptionalFlow/3-Exceptions-Events.md
rename to book/ru/Execution/2-ExceptionalFlow/3-Exceptions-Events.md
diff --git a/book/ru/ExceptionalFlow/4-Exceptions-Types.md b/book/ru/Execution/2-ExceptionalFlow/4-Exceptions-Types.md
similarity index 100%
rename from book/ru/ExceptionalFlow/4-Exceptions-Types.md
rename to book/ru/Execution/2-ExceptionalFlow/4-Exceptions-Types.md
diff --git a/book/ru/ExceptionalFlow/imgs/CommonScheme.png b/book/ru/Execution/2-ExceptionalFlow/imgs/CommonScheme.png
similarity index 100%
rename from book/ru/ExceptionalFlow/imgs/CommonScheme.png
rename to book/ru/Execution/2-ExceptionalFlow/imgs/CommonScheme.png
diff --git a/book/ru/ExceptionalFlow/imgs/CoreCLRCommonScheme.png b/book/ru/Execution/2-ExceptionalFlow/imgs/CoreCLRCommonScheme.png
similarity index 100%
rename from book/ru/ExceptionalFlow/imgs/CoreCLRCommonScheme.png
rename to book/ru/Execution/2-ExceptionalFlow/imgs/CoreCLRCommonScheme.png
diff --git a/book/ru/ExceptionalFlow/imgs/SerializationError.png b/book/ru/Execution/2-ExceptionalFlow/imgs/SerializationError.png
similarity index 100%
rename from book/ru/ExceptionalFlow/imgs/SerializationError.png
rename to book/ru/Execution/2-ExceptionalFlow/imgs/SerializationError.png
diff --git a/book/ru/ExceptionalFlow/imgs/StackUnroll.png b/book/ru/Execution/2-ExceptionalFlow/imgs/StackUnroll.png
similarity index 100%
rename from book/ru/ExceptionalFlow/imgs/StackUnroll.png
rename to book/ru/Execution/2-ExceptionalFlow/imgs/StackUnroll.png
diff --git a/book/ru/ExceptionalFlow/imgs/StackUnroll2.png b/book/ru/Execution/2-ExceptionalFlow/imgs/StackUnroll2.png
similarity index 100%
rename from book/ru/ExceptionalFlow/imgs/StackUnroll2.png
rename to book/ru/Execution/2-ExceptionalFlow/imgs/StackUnroll2.png
diff --git a/book/ru/ExceptionalFlow/imgs/StackWOutUnrolling.png b/book/ru/Execution/2-ExceptionalFlow/imgs/StackWOutUnrolling.png
similarity index 100%
rename from book/ru/ExceptionalFlow/imgs/StackWOutUnrolling.png
rename to book/ru/Execution/2-ExceptionalFlow/imgs/StackWOutUnrolling.png
diff --git a/book/ru/AppDomains/1-AppDomains-Intro.md b/book/ru/Execution/A-AppDomains/1-AppDomains-Intro.md
similarity index 100%
rename from book/ru/AppDomains/1-AppDomains-Intro.md
rename to book/ru/Execution/A-AppDomains/1-AppDomains-Intro.md
diff --git a/book/ru/AppDomains/2-AppDomains-Isolation.md b/book/ru/Execution/A-AppDomains/2-AppDomains-Isolation.md
similarity index 100%
rename from book/ru/AppDomains/2-AppDomains-Isolation.md
rename to book/ru/Execution/A-AppDomains/2-AppDomains-Isolation.md
diff --git a/book/ru/Links.md b/book/ru/Links.md
index 50f9a18..fc53da5 100644
--- a/book/ru/Links.md
+++ b/book/ru/Links.md
@@ -2,4 +2,8 @@
1. [CLR via C#](https://www.amazon.com/CLR-via-4th-Developer-Reference/dp/0735667454) by [Jeffrey Richter](https://github.com/JeffreyRichter)
2. [Matt Warren blog](https://mattwarren.org/)
-3. [JetBrains Lifetime](https://www.jetbrains.com/help/resharper/sdk/Platform/Lifetime.html)
\ No newline at end of file
+3. [JetBrains Lifetime](https://www.jetbrains.com/help/resharper/sdk/Platform/Lifetime.html)
+4. [Pro .NET Memory Management](https://prodotnetmemory.com/) by [Konrad Kokosa](https://twitter.com/konradkokosa)
+5. [Konrad Kokosa blog](http://tooslowexception.com)
+5. [Yi Zhang](https://yizhang82.dev/)
+6. [.NET official blog](https://devblogs.microsoft.com/dotnet/)
\ No newline at end of file
diff --git a/book/ru/MemoryManagementBasics.md b/book/ru/Memory/01-00-MemoryManagement-Intro.md
similarity index 100%
rename from book/ru/MemoryManagementBasics.md
rename to book/ru/Memory/01-00-MemoryManagement-Intro.md
diff --git a/book/ru/Memory/01-02-MemoryManagement-Basics.md b/book/ru/Memory/01-02-MemoryManagement-Basics.md
new file mode 100644
index 0000000..eae309d
--- /dev/null
+++ b/book/ru/Memory/01-02-MemoryManagement-Basics.md
@@ -0,0 +1,167 @@
+# Управление памятью
+
+## Пара слов перед стартом
+
+Когда я разговаривал с различными людьми и рассказывал, как работает Garbage Collector (для меня по началу это было большим и странным увлечением), то весь рассказ умещался максимум минут на 15. После чего, мне задавали один вопрос: «а зачем это знать? Ведь работает как-то и работает». После чего в голове начиналась путаница: с одной стороны я понимал, что они в большинстве случаев правы. И рассказывал про то самое меньшинство случаев, где эти знания прекрасно себя чувствуют и используются. Но поскольу таких случаев было всё-таки меньшинство, в глазах собеседника оставалось некоторое чувство недоверия.
+
+На уровне тех знаний, которые нам давали раньше в немногочисленных источниках, люди, которых собеседуют на позицию разработчика обычно говорят: есть три поколения, пара хипов больших и малых объектов. Еще максимум можно услышать - про наличие неких сегментов и таблицы карт. Но обычно дальше поколений и хипов люди не уходят. И все почему? Ведь **вовсе не потому**, что чего-то не знают, а потому, что действительно **не понятно, зачем это знать**. Ведь та информация, которая нам давалась, выглядела как рекламный буклет к чему-то большому, закрытому. Ну знаем мы про три поколения, ну и что?.. Всё это, согласитесь, какое-то эфемерное.
+
+Сейчас же, когда Microsoft открыли исходники, я ожидал нескольких бенефитов от этого. Первый бенефит - это то, что сообщество накинется и начнет какие-то баги исправлять. И оно накинулось! И исправило все грамматические ошибки в комментариях. Много запятых исправлено и опечаток. Иногда даже написано, что, например, свойство `IsEnabled` возвращает признак того, что что-то включено. Можно даже подать на членство в .NET Foundation, опираясь на множесто вот таких вот комментариев (и, понятное дело, не получить). Сейчас же можно: дорога открыта для граммар-наци. Второй бенефит, который ожидался - это предложение нового и полезного функционала в готовом виде. Этот бенефит, насколько я знаю, время от времени также срабатывает, но это очень редкие кейсы. Например, один разработчик очень ускорил получение символа по индексу в строке. Как выяснилось, ранее это работало не очень эффективно.
+
+Наш рассказ про менеджмент памяти будет идти от общего к частному. Т.е. для начала мы посмотрим на алгоритмы с высоты птичьего полёта, не сильно вдаваясь в подробности. Ведь если мы начнем сразу с подробностей, придётся делать отсылки к будущим главам, а оттуда - обратно - в ранние главы. Это крайне не удобно как для написания, так и в чтении. Напротив, сделав вводную, мы поймем все основы. А потом - начнем погружаться в детали.
+
+## Введение в управление памятью
+
+Мы пишем разные программы: консольные, сервисы, web-сервисы и другие. Все они работают примерно одинаково. Но есть очень важное отличие - это стиль работы с памятью. Консольные приложения, скорее всего, работают в рамках базовой, выделенной при старте приложения, памяти. Такое приложение всю или частично ее использует и больше ничего не запросит: запустилось и вышло. Иногда речь идет о сервисах, которые долго работают, перерабатывают память постоянно. И делают это не по изолированным запросам, в отличие от сервисов ASP.NET и WCF (которые мы вызвали, из базы что-то достали и забыли). А именно как какой-то расчётный сервис: есть поток данных на вход, с которыми сервис работает и так может работать очень долго, выделяя и освобождая память. И это уже совершенно другой стиль расхода памяти: ведь в этом случае память необходимо контролировать, смотреть как она расходуется, течет или не течет.
+
+А если это ASP.NET, то это уже третий способ управления памятью. Надо понимать, что нас вызвал внешний код, мы отработаем достаточно быстро и исчезнем. Отсюда, если мы во время запроса выделяем некоторую память, можно сделать всё так, чтобы не волноваться по поводу её освобождения: ведь метод завершит свою работу и все объекты потеряют свои корни: локальные переменные метода, обрабатывающего запрос.
+
+Как же этим всем управлять? С точки зрения разработки Garbage Collector'а, с точки зрения системы менеджмента памяти у нас есть совершенно разные стили и мы должны в них идеально хорошо работать. У нас же может быть машина, на которой запустилось консольное приложение, а есть машина, на которой приложение забирает 256 Гб. Эти системы помимо различия в объёме пожираемой памяти также отличаются по ряду других признаков: например, в стиле её выделения и освобождения путём обнуления ссылок (или их ненужности. при выходе из метода локальные переменные более не нужны, но они не обнуляются). Поэтому, для начала надо как-то классифицировать эту память: а от классификации памяти танцевать в сторону оптимизации её выделения и освобождения в зависимости от того, с каким классом памяти мы в данный момент работаем.
+
+## Возможные классификации памяти исходя из логики
+
+Как можно классифицировать память? Чисто интуитивно можно разделять выделяемые участки памяти исходя из размеров объекта, который выделяется. Например, понятно, что если мы говорим о больших структурах данных, то управлять ими надо совершенно по-другому, нежели маленькими: потому что они тяжелые и их трудно перемещать при надобности. А маленькие, соотвественно, занимают мало места и из-за того, что они образуют группы, перемещать легко. Однако из-за того что их намного больше, ими тяжелее управлять: знать о положении в памяти каждого из них. А значит, для них без всякой статистики и так понятно, что должен быть другой подход.
+
+Если разделять по времени жизни, то тут тоже возникают идеи. Например, если объекты короткоживущие, то, возможно, к ним надо чаще присматриваться, чтобы побыстрее от них избавляться (желательно, сразу, как только они стали не нужны). Если объекты долгоживущие, то можно уже посмотреть на статистику. Например, можно пофантазировать и решить, что эту область памяти анализировать на предмет ненужных объектов можно и пореже: ведь большие объекты редко создают траффик в памяти. А если смотреть редко, это сокращает время на собрку мусора сумме, но увеличивает - каждый вызов GC.
+
+Или же по типу данных. Можно легко предположить, что все типы, которые отнаследованы от типа `Attribute` или в зоне `Reflection`, будут жить почти всегда вечно. Или строки, которые представляют собой массив символов: к ним тоже может быть свой подход.
+
+Видов может быть сколько угодно много и в зависимости от классификаций может оказаться, что управление памятью для конкретной классификации может быть более эффективно, если учитывать её особенности.
+
+Когда создавали архитектуру нашего GC, то выбрали первые два вида классификаций: размер и время жизни (хотя, если присмотреться к делению типов на классы и структуры, то можно подумать, что классификации на самом деле три. Однако, различие свойств классов и структур можно свести к размеру и времени жизни).
+
+## Как это работает у нас. Обоснование выбора архитекторов
+
+Если мы с вами будем досконально разбираться, почему были выбраны именно эти два алгоритма управления памятью: Sweep и Compact, нам для этого придётся рассматривать десятки алгоритмов управления памятью, которые существуют в мире: начиная обычными словарями, заканчивая очень сложными lock-free структурами. Вместо этого, оставив голову мыслям о полезном, мы просто *обоснуем* выбор и тем самым *поймём*, почему выбор был сделан именно таким. Мы более не смотрим в рекламный буклет ракеты-носителя: у нас на руках полный набор документации.
+
+> Я выбрал формат рассуждения чтобы вы почувствовали себя архитекторами платформы и сами пришли к тем же самым выводам, к каким пришли реальные архитекторы в штаб-квартире Microsoft в Рэдмонде.
+
+Определимся с терминологией: менеджмент памяти - это структура данных и ряд алгоритмов, которые позволяют "выделять" память и отдавать её внешнему потребителю и освобождать её, регистрируя как свободный участок. Т.е. если взять, например, какой-то массив байт (линейный кусок памяти), написать алгоритмы разметки массива на объекты .NET (запросили новый объект: мы подсчитали его размер, пометили у себя что этот вот кусок и есть новый объект, отдали указатель на объект внешней стороне) и алгоритмы освобождения памяти (когда нам говорят, что объект более не нужен, а потому память из-под него можно выдать кому-то другому).
+
+Исходя из классификации выделяемых объектов на основании их размера можно разделить места под выделение памяти на два больших раздела: на место с объектами размером ниже определенного порога и на место с размером выше этого порога и посмотреть, какую разницу можно внести в управление этими группами (исходя из их размера) и что из этого выйдет. Рассмотрим каждую категорию в отдельности.
+
+Если рассматривать вопросы **управления условно** "*маленьких*" объектов, то можно заметить, что если придерживаться идеи сохранения информации о каждом объекте, нам будет очень дорого поддерживать структуры данных управления памятью, которые будут хранить в себе ссылки на каждый такой объект. В конечном счёте может оказаться, что для того, чтобы хранить информацию об одном объекте понадобится столько же памяти, сколько занимает сам объект. Вместо этого стоит подумать: если при сборке мусора мы будем помечать достижимые объекты обходом графа объектов (понять это легко, зная, откуда начинать обход графа), а линейный проход по куче нам понадобится *только* для идентификации всех остальных, т.е. мусорных объектов, так ли нам необходимо в алгоритмах менеджмента памяти хранить информацию о каждом объекте? Ответ очевиден: надобности в этом нет никакой. Ведь если мы будем размещать объекты друг за другом и при этом сделать возможным узнать размер каждого из них, сделать итератор кучи очень просто:
+
+```csharp
+var current = memory_start;
+
+while(current < memory_end)
+{
+ var size = current.typeInfo.size;
+ current += size;
+}
+```
+
+ А значит, можно попробовать исходить из того, что такую информацию мы хранить не должны: пройти кучу мы можем линейно, зная размер каждого объекта и смещая указатель каждый раз на размер очередного объекта.
+
+> В куче нет дополнительных структур данных, которые хранят указатели на каждый объект, которым управляет куча.
+
+Однако, тем не менее, когда память нам более не нужна, мы должны её освобождать. А при освобождении памяти нам становится трудно полагаться на линейное прохождение кучи: это долго и не эффективно. Как следствие, мы приходим к мысли, что надо как-то хранить информацию о свободных участках памяти.
+
+> В куче есть списки свободных участков памяти: набор указателей на их начала + размер.
+
+Если, как мы решили, хранить информацию о свободных участках, и при этом при освобождении памяти из под объектов эти участки оказались слишком малы для размещения в них чего-либо полезного, то во-первых мы приходим к той-же проблеме хранения информации о свободных участках, с которой столкнулись при рассмотрении занятых: хранить информацию о таких малышах может оказаться слишком дорого. Это снова звучит расточительно, согласитесь: не всегда выпадает удача освобождения группы объектов, следующих друг за другом. Обычно они освобождаются в хаотичном порядке, образуя небольшие просветы свободной памяти, где сложно выделить что-либо ещё. Но всё-таки в отличии от занятых участков, которые нам нет надобности линейно искать, искать свободные участки нам необходимо потому что при выделении памяти они нам снова могут понадобиться. А потому возникает вполне естественное желание уменьшить фрагментацию и сжать кучу, переместив все занятые участки на места свободных, образовав тем самым большую зону свободного участка, где можно совершенно спокойно выделять память.
+
+> Отсюда рождается идея алгоритма сжатия кучи Compacting.
+
+Но, подождите, скажите вы. Ведь эта операция может быть очень тяжёлой. Представьте только, что вы освободили объект в самом начале кучи. И что, скажете вы, надо двигать вообще всё?? Ну конечно, можно пофантазировать на тему векторных инструкций CPU, которыми можно воспользоваться для копирования огромного занятого участка памяти. Но это ведь только начало работы. Надо ещё исправить все указатели с полей объектов на объекты, которые подверглись передвижениям. Эта операция может занять дичайше длительное время. Нет, надо исходить из чего-то другого. Например, разделив весь отрезок памяти кучи на сектора и работать с ними по отдельности. Если работать отдельно в каждом секторе (для предсказуемости времени работы алгоритмов и масштабирования этой предсказмуемости - желательно, фиксированных размеров), идея сжатия уже не кажется такой уж тяжёлой: достаточно сжать отдельно взятый сектор и тогда можно даже начать рассуждать о времени, которое необходимо для сжатия одного такого сектора.
+
+Теперь осталось понять, на основании чего делить на сектора. Тут надо обратиться ко второй классификации, которая введена на платформе: разделение памяти, исходя из времени жизни отдельных её элементов.
+
+Деление простое: если учесть, что выделять память мы будем по мере возрастания адресов, то первые выделенные объекты (в младших адресах) становятся самыми старыми, а те, что находятся в старших адресах - самыми молодыми. Далее, проявив смекалку, можно прийти к выводам, что в приложениях объекты делятся на две группы: те, что создали для долгой жизни и те, которые были созданы жить очень мало. Например, для временного хранения указателей на другие объекты в виде коллекции. Или те же DTO объекты. Соответственно, время от времени сжимая кучу мы получаем ряд долгоживущих объектов - в младших адресах и ряд короткоживущих - в старших.
+
+> Таким образом мы получили *поколения*.
+
+Разделив память на поколения, мы получаем возможность реже заглядывать за сборкой мусора в объекты старшего поколения, которых становится всё больше и больше.
+
+Но возникает еще один вопрос: если мы будем иметь всего два поколения, мы получим проблемы:
+
+ - Либо мы будем стараться, чтобы GC отрабатывал маскимально быстро: тогда размер *младшего поколения мы будем стараться делать минимальных размеров*. Как результат - недавно созданные объекты при вызове GC будут случайно уходить в старшее поколение (если GC сработал "прям вот сейчас, во время яростного выделения памяти под множество объектов"), хотя если бы он сработал чуть позже, они бы остались в младшем, где были бы собраны за короткие сроки.
+ - Либо, чтобы минимизировать такое случайное "проваливание", мы *увеличим размер младшего поколения*. Однако, в этом случае GC на младшем поколении будет работать достаточно долго, замедляя и подтормаживая тем самым всё приложение.
+
+Выход - введение "среднего" поколения. Подросткового. Суть его введения сводится к получению баланса между *получением минимального по размеру младшего поколения* и *максимально-стабильного старшего поколения*, где лучше ничего не трогать. Это - зона, где судьба объектов еще не решена. Первое (не забываем, что мы считаем с нуля) поколение создается также небольшим, но чуть крупнее, чем младшее и потому GC туда заглядывает реже. Он тем самым дает возможность объектам, которые находятся во временном, "подростковом" поколении, не уйти в старшее поколение, которое собирать крайне тяжело.
+
+> Так мы получили идею трёх поколений.
+
+Следующий слой оптимизации - попытка отказаться от сжатия. Ведь если его не делать, мы избавляемся от огромного пласта работы. Вернемся к вопросу свободных участков.
+
+Если после того, как мы израсходовали всю доступную в куче память и был вызван GC, возникает естественное желание отказаться от сжатия в пользу дальнейшего выделения памяти внутри освободившихся участков, если их размер достаточен для размещения некоторого количества объектов. Тут мы приходим к идее второго алгоритма освобождения памяти в GC, который называется `Sweep`: память не сжимаем, для размещения новых объектов используем пустоты от освобожденных объектов.
+
+> Так мы описали и обосновали все основы алгоритмов GC.
+
+Далее спускаться мы не будем, иначе я не оставлю себе почвы для размышлений. Замечу только, что мы рассмотрели все предпосылки и выводы к существующим алгоритмам менеджмента памяти.
+
+## Как это работает у нас
+
+Теперь мы зайдём с другой стороны. Я буду выполировывать факты так, что даже если у вас плохая память на вычитке текста, вы всё равно запомните, как работает менеджмент памяти в .NET.
+
+Итак, мы знаем, что у нас существует два способа классифицировать память: исходя из времени жизни сущностей и исходя из их размера. Подумаем, что мы имеем, исходя из размеров объектов. Если у нас объекты имеют большие размеры, то нам не выгодно часто делать `Сompact`. Потому что в этом случае мы все объекты перетаскиваем на освободившиеся участки. То есть копируем их. А если объект огромен, то копировать дорого и каждый раз прибегая к сжатию кучи можно сильно просесть по производительности. В данной ситуации удобен только `Sweep`. Об этом способе мы подробно поговорим позже, но если совсем коротко, то память освобождается и свободный кусок сохраняется в список свободных участков и дальше переиспользуется при выделении объектов. Вызов `Compact` может быть выгоден в редких случаях: когда *есть понимание*, что из-за `Sweep` и траффика буферов большого размера существует некоторая фрагментация в зоне крупных объектов (Large Objects Heap. Давайте уже начнём называть вещи своими именами). И ручной вызов GC с указанием метода сбора мусора `Compact` в этом случае может нам помочь. Однако, давайте не будем углубляться: у нас для этого есть очень много времени.
+
+> Остановимся лишь на том, что в `LOH` метод сбора мусора `Sweep` имеет абсолютное преимущество перед `Sweep`
+
+А если объекты маленькие, то наоборот: хоть и не всегда, но удобен `Compact`. Например, у нас была куча объектов и мы потеряли ссылку на объекты через одного, и получается, что занятые участки и свободные чередуются, например по 24 байта. И может так оказаться, что такими маленькими участками мы воспользоваться не сможем потому что дальше мы будем аллоцировать более крупные объекты. Возникает дилемма: либо наращивать кучу либо избавиться от фрагментации. Поэтому тут, возможно, стоит сжать кучу. Однако, если объекты ушли на покой группой, то нам в данном случае по-прежнему выгоден `Sweep`, поскольку тот не сжимает кучу, а использует для дальнейшей аллокации освободившееся место.
+
+> Отсюда можно сделать простой вывод: в обоих кучах память управляется одинаково. Но в LOH в отличии от SOH отключен автоматический вызов `Compact`. Он доступен только для прямого вызова.
+
+Тут возникает проблема: у нас есть хип маленьких объектов и хип больших. Они, соответственно, разделены. Но по факту мы всегда аллоцируем маленькие объекты. Их в любом случае будет больше: возможно, миллионы. GC проходит через разные стадии: стадия планирования, стадия маркировки, сбора мусора. На 200гб памяти, если так получится, любая из стадий будет очень дорогой, а потому память надо как-то дополнительно сегментировать, чтобы оптимизировать работу с ними.
+
+Поэтому, второй тип сегментации работает исходя из времени жизни объектов. Куча растет у нас в одном направлении. Аллокация идет с младших адресов к старшим. Соответственно, при выделении памяти берется указатель на первый свободный участок и затем он сдвигается на размер выделенного объекта и всё: куча растет в одном направлении. Отсюда, используя наши ранние рассуждения можно сделать вывод о том, как легко поделить на три поколения. Нулевое поколение - место, где объекты аллоцируются. Первое поколение - это место, где объекты не были собраны Garbage Collector, но в них пока еще нет уверенности: мы хотим созранить стабильность во втором поколении и даём объектам еще один шанс быть собранными. И, соответственно, второе поколение, где объекты в идеале будут находиться без сборки мусора.
+
+Как я уже говорил, если объекты живут долго, то туда можно реже заглядывать, чтобы запускать GC. Поэтому, если мы сделаем нулевое поколение определенных, малых и заранее известных размеров, мы сможем обходить его за *гарантированно короткий* промежуток времени. И Microsoft гарантирует, что нулевое поколение будет собираться за какие-то определенные миллисекунды. Т.е. GC быстро что-то сделал и дальше пошел, а никто даже и не заметил, что он отработал.
+
+`LOH` имеет другую структуру. Сюда попадает все, что больше или равно 85 тысячам байт. Цифра странная, но нам полезно ее знать: её можно использовать, например, чтобы определить размер для аллоцируемого массива, чтобы он не ушел в кучу больших объектов. Если аллоцируете массив int-ов, то нужно грубо поделить на четыре и получится примерно 20 тысяч int-ов, которые туда прекрасно лягут и не уйдут в LOH. Также интересно, что в LOH уходят массивы double от тысячи элементов и выше.
+
+Проверить это все очень легко. Можно написать такой код:
+
+```csharp
+var arr1 = new double[999]; // -> Gen 0
+var arr2 = new double[1000]; // -> Gen 2
+```
+
+и первый массив пойдет в нулевое, а второй - во второе поколение. Какие еще классификации типов существуют для GC? У нас есть две основных, которые мы только что рассмотрели. Однако, хоть эта классификация в общем смысле нам и не доступна, она нам доступна в узком смысле: классификация по типу. Все мы знаем про интернированные строки. Если мы предполагаем, что какая-то строка будет часто встречаться, то ее стоит интернировать, тогда мы сэкономим на памяти.
+
+Как они хранятся? Если строка интернирована, то она хранится как обычная строка в куче. Но ее надо как-то найти, чтобы проверить, что точно такая же строка уже существует. Куча иногда может достигать нескольких сотен гигабайт и поиск будет очень дорогим решением. Поэтому интернированные строки хранятся отдельно (предполагается, что их будет не так много).
+
+У каждого домена при этом (системного или с базовым типом BaseDomain) есть внутренние таблицы, которая нам не доступны. Среди прочих существует Large Heap Handle Table. Существует два типа внутренних массивов, основанных на bucket-ах. Это массив статиков. Имеются ввиду статические члены классов, которые хранятся в массивах. У каждого домена есть ссылка на массив статиков. И дальше ячейками являются ссылки на значения. И еще одна - pinning handles. Это таблица запиненных элементов. Для тех случаев, когда вы пинуете объект в памяти, можете сделать это двумя путями. Первый - это через API, а второй - через ключевое слово fixed в C#. Это два совершенно разных механизма.
+
+Соответственно, исходя из времени жизни, из-за большого количества объектов, SOH разделен на части, чтобы им было проще управлять. Заполнение SOH идет линейно, поэтому старые объекты живут в младших адресах, свежие - в старших. Старые объекты, как правило, живут долго. Чем дольше объект существует, тем больше вероятность того, что он будет существовать все время работы приложения. Поэтому существует три поколения. Нулевое поколение - это между временем создания объекта и ближайшим GC. Объектов не успевает накопится слишком много и GC успевает их быстро убрать, не залезая в остальную кучу.
+
+Первое поколение живет между первым и вторым GC. Соответственно, для тех, кто не ушел во второе. Оптимизация какая: нулевое собирать быстро, первое - чуть подольше. Это последняя возможность GC собрать объект, прежде чем он ушел во второе, огромное, поколение. Если объект ушел во второе поколение, то, скорее всего, он будет жить долго. Туда можно редко обращаться. А первое - для объекта, который случайно ушел в первое поколение, но на самом деле он короткоживущий. Это такая оптимизация, чтобы его во втором не ловить. И второе поколение для тех, кто решил пожить подольше и если GC туда пришел, то он там останется работать надолго.
+
+Оранжевые кубики - это руты. Руты - это точки, относительно которых, если обходить граф, то гарантированно вы обойдете все объекты, которыми пользуется программа. Если бы фаза маркировки работала на всех поколениях, то она бы работала долго. Поэтому она работает максимально на самых младших. Если мы решили, что собираем нулевое поколение, то она только там и будет работать. Поэтому есть три типа ссылок. Первый тип - ссылка внутри одного поколения. Например, если мы с рута пришли в нулевое поколение, а дальше у нас ссылка из этого объекта, но опять в нулевое поколение. Это внутренняя ссылка. Второй тип ссылок - это ссылка из более старшего поколения в более младшее поколение. Older-to-younger. Он характеризуется тем, что объект, на который мы ссылаемся из первого поколения нет имеет ссылки с рута в нулевом поколении. Это значит, что если мы будем маркировать только нулевое поколение, чтобы на нем GC отработал, то мы пропустим этот объект. Мы должны знать о существовании ссылки с более старшего поколения.
+
+Третий вид ссылок - это ссылка из младшего в старшее поколение. Для нас она не важна. Если мы собираем нулевое поколение - она не имеет значение. При сборке более старших поколений, младшие тоже собираются. Почему? Если собираем, например, первое - оно больше, крупнее. И, поскольку, нулевое поколение собирается намного чаще, то есть высокая степень вероятности, что после сборки первого будет собрано и нулевое. И GC опять запустится. Для того, чтобы два раза не ходить за одним и тем же, пересобираются и более младшие поколения. В этом случае нам нужно знать ссылку из младшего в старшие? Нам это без разницы, она и так есть. При сборке второго поколения та же самая ситуация. С точки зрения фазы маркировки нам важны ссылки внутри поколения и ссылки с более старших на наше поколение.
+
+Если мы находимся в нулевом поколении - как узнать о том, что на нас есть ссылка с более старшего поколения. Есть механизм, который в начале изучения начинает немного пугать. Есть такой код (см. слайд) 35:19.
+
+Кстати, такие сценарии работы GC можно проверять таким способом: мы создаем объект, делаем GC.Collect(), отправляем его в первое поколение. Мы знаем, что он туда уйдет. Дальше создаем ссылку и тем самым фактически создаем ссылку из старшего поколения в младшее. Если дома захочется с чем-то поиграть, то с помощью метода GC.Collect() можно смоделировать такие ситуации.
+
+Что мы здесь видим? У нас есть x, GC.Collect(), инстанс класса Foo уходит в поколение один из нулевого. И дальше x.field присваиваем new Boo(). Это значит, что объект типа Foo начинает ссылаться на новый инстанс объекта типа Boo. То есть из первого в нулевого. Это у нас older-to-younger link.
+
+Что происходит в .NET. В месте присваивания, джиттер, то есть там не просто присваивание, а присваивание с проверкой. Эта техника называется Write Barrier и Remembered Set. В .NET она называется немного по-другому. Если у нас звезды сошлись, что нужно запомнить эту ссылку, то мы запоминаем ее во внутренней структуре джита.
+
+Что это за условие? Значение - это ссылка на экземпляр .NET класса. Точка присваивания находится в управляемой куче и имеет более старшее поколение, чем адрес присваиваемого объекта. Когда мы делаем вот так (см. слайд 37:29), джиттер дополнительно проверяет, что слева поколение старше, чем справа. Если старше, то он запоминает адреса во внутренних структурах, убеждается, что с левой части на правую есть ссылка. Это нужно для дальнейшей сборки мусора. На фазе маркировки отмечается выбранное поколение и если у нас есть ссылки в Remembered Set, если мы запомнили, что где-то сохраняли ссылку из первого в нулевое поколение, они тоже становятся корнями, чтобы пройти маркировку. Но хип получается в итоге огромный и становится страшновато.
+
+Поэтому используется более оптимизированный способ. Он называется механизм карточного стола. Знания об этом механизме не так распространены. Как он работает? Если взять адресное пространство всего огромного хипа, весь кусок памяти, то сбоку есть карточный стол. Это, грубо говоря, массив чисел. Где каждый бит массива отвечает за определенный диапазон памяти. Если бит выставлен в единицу, значит в этом диапазоне памяти есть ссылка на младшее поколение. То есть это массив признаков ссылок на младшее поколение. См. слайд - 40:12
+
+У нас есть память, внизу карточный стол. У нас появилась ссылка, слева на право. Это значит, что бит должен быть выставлен в единицу. Потому что от более старшего поколения пошла ссылка в более младшее. Каждый бит карточного стола отвечает за 128 байт в x86 и за 256 байт в x64. Это, по сути, 32 машинных слова. Машинное слово - это то, с чем работает процессор.
+
+Если учесть, что каждый пустой объект, максимально маленький (например, new Object) занимает четыре машинных слова в среднем, то получается, что один бит карточного стола перекрывает десять объектов. И если хотя бы с одного из них есть ссылка в младшее поколение, то GC должен при обходе в фазе маркировки зайти по этому адресу и просмотреть все десять объектов и найти те, которые ссылаются на младшее поколение. Один байт перекрывает уже 1,2 КБ оперативной памяти. Или 80 объектов. Четыре байта - 320 объектов. Это x86 архитектура. Получается жирновато, если ссылка появилась.
+
+Как это работает. Можно посмотреть код, который будет вызван при присваивании (см. слайд 43:24) по этому адресу на github. Там ассемблеровский код, он достаточно простой, разобраться в нем легко. Есть много комментариев, гораздо больше, чем кода.
+
+Реализация сильно зависит от особенностей. У нас есть Workstation GC, есть серверный GC. У Workstation есть две версии: до и после роста кучи. Как следствие, перемещается gen_1 gen_0. И Server GC: есть несколько хипов для SOH и несколько для LOH. Там свои реализации этих методов присваивания, потому что придется параметризовать для какой кучи идет вызов. А так он просто генерирует ставку для новой кучи и все хорошо. Плюс две реализации под x64. Если смотреть базовую, то будет примерно так (см. слайд 44:36). Регистр RCX - это адрес filed. Адрес таргета, куда мы присваиваем. RDX - это ссылка на объект. Когда мы присваивали, должна быть составлена эта инструкция и больше ничего. Но на самом деле нет. Присвоили и дальше этим кодом мы проверяем, находится ли правая часть присваивания внутри эфемерного сегмента (gen_0, gen_1). И если находится, то мы берем карточный стол, делим на 2048, получаем адрес ячейки и если флаг не выставлен, то выставить.
+
+Здесь есть две особенности. Первая - почему просто не выставить? Это будет очень долго. Операция записи намного дольше, чем чтения. Чтение происходит из кеша, а чтобы записать надо записать кроме кеша еще и в оперативную память. Поэтому их проверяем. Интересна процедура выставления флага. Он выставляется сразу же 0FF. То есть мы выставляем не один флаг, а сразу группой. Почему? Когда мы будем дальше проверять ссылку из старшего поколения в младшее, нам побитово будет долго проверять. Проще сразу словами делать проверку. Вместо того, чтобы смотреть, на каком бите ссылка и какие 2 КБ смотреть, все работает проще, система оперирует более значительными диапазонами.
+
+Код, получается, проверяет только поколение object, но не target. Target не интересует. Мы проверяем только то, что мы попали в нулевое или первое поколение. В любом этом случае выставляется бит в карточном столе. Когда мы проверяем нулевое поколение, будем проходится по карточному столу, который относится и к первому, и ко второму поколению. Нас устроит, что биты выставлены. Если первое поколение будем просматривать с нулевым, собирать там мусор и у первого и второго, то мы будем просматривать карточный стол второго поколения. Там тоже эти биты будут выставлены. Поэтому мы левую часть смотрим, и не имеет значения первого или второго поколения. Дальше фильтрация уже идет на стадии проверки.
+
+Однако, карточный стол в случае большого хипа (у LOH он может быть феерических размеров) тоже будет огромным. И по нему точно так же будет идти сканирование. Мы собираем нулевое поколение и должны уложиться в несколько миллисекунд, а у нас хип в несколько сотен ГБ. Например, это сервер. Карточный стол придется сканировать весь, а это долго. Выход - двухуровневый карточный стол. Называется это Cards Bundle Table. Это еще один массив, где бит отвечает за 32 слова карт. Этот массив оперирует огромными диапазонами. Получается, 1 бит Cards Bundle Table отвечает за 128 Кб на x86 и за 256 Кб на x64. Одна ячейка - 4 байта, это 8 Мб карт целевой памяти.
+
+Когда мы будем делать GC, собирая нулевое поколение, надо посмотреть, что с первого и второго есть что-то полезное и далее мы уходим в Cards Bundle Table. Сканиурем, и если где-то не ноль, то переходим на карточный стол, в соответствующий его диапазон и ищем ненулевую ячейку. И потом уже переходим в нужный диапазон памяти и сканиурем объекты, которые там находятся, в поисках ссылки со старшего на младшее поколение. И только тогда мы эту ссылку добавляем в руты и маркируем все объекты, на которые эта ссылка ведет.
+
+Есть маленькая оптимизация для Windows. Эта система построена, в первую очередь, на архитектуре x86, где используются механизмы вирутализации памяти процессора, которая основана на страницах памяти. Она поделена на зоны, на странички. Можно выставить флаг MEM_WRITE_WATCH. Это означает, что если кто-то будет писать по заданному диапазону, то можно подписаться на обновления. Мы сделали массив и если туда кто-то будет писать, у нас будет дергаться метод из winapi. Почему мы не видим этого когда в ассемблеровском методе расстановки карт? Когда мы записываем, выставляя карты, Windows получает нотификацию от страницы, куда мы пишем, и исходя из этого проставляет бит в Cards Bundle Table.
+
+Базовый вывод, который можно сделать уже сейчас, основываясь на карточных столах, что если вы хотите, чтобы GC протекал быстро и как по маслу, не стоит делать ссылок из старших поколений. Не надо делать вечноживущие массивы, аллоцировать объекты и ссылки на них складывать в эти древние массивы. Но если вы так делаете, надо контролировать, чтобы эти массивы располагались рядом, чтобы их аллоцировать друг за другом. Не распределять их по памяти. Самое неудачное, что можно сделать - это иметь кучу объектов старшего поколения и по какой-то причине выставить ссылки на младшее поколение, но не группой, а вразброс через всю память. Это самый тяжелый сценарий, потому что карточный стол будет забит единицами. А GC, собирая нулевое поколение, будет вынужден проходить все второе, все первое и искать, что там добавлено. Если вы делаете ссылку из старших поколений в младшие, то необходимо эти ссылки группировать: массив, который ссылается на объект младшего поколения. Поскольку это массив, который ссылается на объекты младшего поколения, все ячейки рядом и в карточном столе в идеале это будет просто единица. Дальше мы будем изучать более подробно.
+
+> Далее: [Ссылочные и значимые типы данных](../2-Basics/1-ReferenceTypesVsValueTypes.md)
diff --git a/book/ru/ThreadStack.md b/book/ru/Memory/01-04-MemoryManagement-ThreadStack.md
similarity index 99%
rename from book/ru/ThreadStack.md
rename to book/ru/Memory/01-04-MemoryManagement-ThreadStack.md
index 0a01cb9..bfcd4b3 100644
--- a/book/ru/ThreadStack.md
+++ b/book/ru/Memory/01-04-MemoryManagement-ThreadStack.md
@@ -32,7 +32,7 @@ void Method2(int arg)
Те же самые процессы можно посмотреть на изображении:
-![](../imgs/ThreadStack/AnyMethodCall.png)
+![](./imgs/ThreadStack/AnyMethodCall.png)
Также замечу, что стек "растёт", начиная со старших адресов и заканчивая младшими, т.е. в обратную сторону.
@@ -173,27 +173,27 @@ ThreadPool -> MakeFork
Итак, как будет выглядеть стек потока, когда наш код вызовет MakeFork, который вызовет CloneThread, который уйдёт в unmanaged мир CLI/C++ и вызовет метод клонирование (саму реализацию) - там? Давайте посмотрим на схему (ещё раз напомню, что стек растёт от старших адресов к младшим. Справа налево):
-![](../imgs/ThreadStack/step1.png)
+![](./imgs/ThreadStack/step1.png)
Ну а для того чтобы не тащить всю простыню со схемы на схему, упростим, отбросив то, что нам не нужно:
-![](../imgs/ThreadStack/step2.png)
+![](./imgs/ThreadStack/step2.png)
Когда мы создадим поток либо возьмём готовый из пула потоков, в нашей схеме появляется ещё один стек, пока ещё ничем не проинициализированный:
-![](../imgs/ThreadStack/step3.png)
+![](./imgs/ThreadStack/step3.png)
Теперь наша задача - сымитировать запуск метода `Fork.CloneThread()` в новом потоке. Для этого мы должны в конец его стека потока дописать серию кадров: как будто из делегата, переданного ThreadPool'у был вызван `Fork.CloneThread()`, из которого через враппер C++ кода managed обёрткой был вызван CLI/C++ метод. Для этого мы просто скопируем необходимый участок стека в массив (замечу, что со склонированного участка на старый "смотрят" копии регистров EBP, обеспечивающих построение цепочки кадров):
-![](../imgs/ThreadStack/step4.png)
+![](./imgs/ThreadStack/step4.png)
Далее чтобы обеспечить целостность стека после операции копирования склонированного на предыдущем шаге участка, мы заранее рассчитываем, по каким адресам будут находиться поля `EBP` на новом месте, и сразу же исправляем их, прямо на копии:
-![](../imgs/ThreadStack/step5.png)
+![](./imgs/ThreadStack/step5.png)
Последним шагом, очень аккуратно, задействуя минимальное количество регистров, копируем наш массив в конец стека дочернего потока, после чего сдвигаем регистры ESP и EBP на новые места. С точки зрения стека мы сымитировали вызов всех этих методов:
-![](../imgs/ThreadStack/step6.png)
+![](./imgs/ThreadStack/step6.png)
Но пока не с точки зрения кода. С точки зрения кода нам надо попасть в те методы, которые только что создали. Самое простое - просто сымитировать выход из метода: восстановить `ESP` до `EBP`, в `EBP` положить то, на что он указывает и вызвать инструкцию `ret`, инициировав выход из якобы вызванного C++ метода клонирования потока, что приведёт к возврату в реальный wrapper CLI/C++ вызова, который вернёт управление в `MakeFork()`, но в дочернем потоке. Техника сработала.
@@ -516,11 +516,11 @@ RestorePointAfterClonnedExited:
Проверим? Это - скриншот до вызова клонирования потока:
-![](../imgs/ThreadStack/ForkBeforeEnter.png)
+![](./imgs/ThreadStack/ForkBeforeEnter.png)
И после:
-![](../imgs/ThreadStack/ForkAfterEnter.png)
+![](./imgs/ThreadStack/ForkAfterEnter.png)
Как мы видим, теперь вместо одного потока внутри ForkImpl мы видим два. И оба - вышли из этого метода.
diff --git a/book/ru/Memory/01-06-MemoryManagement-EntitiesLifetime.md b/book/ru/Memory/01-06-MemoryManagement-EntitiesLifetime.md
new file mode 100644
index 0000000..136e25a
--- /dev/null
+++ b/book/ru/Memory/01-06-MemoryManagement-EntitiesLifetime.md
@@ -0,0 +1,56 @@
+# Время жизни сущностей
+
+Один из вопросов, которые могут очень сильно влиять на производительность наших приложений - это политика выбора типа сущности: класс или структура, и контроль за их временем жизни. Ведь архитекторы платформы не просто так выделили для нас эти два типа данных. Это деление в первую очередь обусловлено возможностями оптимизации приложений, архитектура которых учитывает особенности обоих групп типов.
+
+Как уже было сказано в главе [Ссылочные и значимые типы данных](../ReferenceTypesVsValueTypes.md), огромным преимуществом значимых типов данных является то, что их не надо аллоцировать. Т.е. другими словами если кто-то располагает экземпляр значимого типа в локальных переменных или параметрах метода, то расположение идёт на стеке (не выделяя дополнительной памяти в куче). Эта операция - та, о которой вам надо мечтать, т.к. именно она максимально быстра и эффективна. Если же структура располагается в полях ссылочного типа (класса), то под нее операция выделения памяти также не вызывается: ведь она является структурной частью этого ссылочного типа. Однако всё гораздо сложнее с ссылочными типами. Ведь, если речь идет о них, то мы имеем целый набор сложностей при выделении памяти под их экземпляры. Причём, что самое печальное, а может быть даже обидное - от нас почти никак не зависит, на какой из алгоритмов выделения памяти мы напоремся: на самый быстрый вариант из четырёх или же на самый тяжеловесный.
+
+## Ссылочные типы
+
+### Общий обзор
+
+Для целостности картины при дальнейшем чтении рассмотрим особенности времени жизни экземпляров ссылочных типов данных. Ссылочные типы обладают следующими свойствами в вопросе времени собственной жизни:
+
+ - У ссылочных типов в отличии от значимых детерменированное начало жизни. Другими словами они порождаются тогда и только тогда, когда кто-либо запросил их создание;
+ - Однако, они имеют недетерменированный освобождение: мы не знаем, когда произойдет освобождение памяти из под них. Мы не можем вызвать GC для конкретного экземпляра даже для случая с Large Objects Heap, где эта операция могла бы быть вполне уместна;
+
+Эти два свойства дают нам немного пищи для размышлений:
+
+ - экземпляры классов уничтожаются в случайное время в неопределенно отдаленном будущем;
+ - их уничтожение обуславливается утерей ссылок на них;
+ - поэтому с одной стороны это значит, что операция освобождения последней ссылки на объект превращается в детерменированную операцию "удаления" объекта из зоны видимости приложения. Он ещё есть, существует, но недосягаем для всего остального приложения;
+ - однако, с другой стороны мы далеко не всегда в курсе, какое именно обнуление ссылки будет последним, что лишает нас свойства детерменированности в обнулении последней ссылки.
+
+Еще одним очень важным свойством является наличие виртуального метода финализации объекта. Этот метод вызывается во время срабатывания сборщика мусора: т.е. в неопределенном будущем. И необходим данный метод для одного: корректного закрытия неуправляемых ресурсов, которыми владеет объект в тех и только тех случаях, когда что-то пошло не так (например, было выброшено исключение) и программа более не сможет самостоятено это сделать (код, который отвечает за освобождение данных ресурсов никогда более не вызовется вследствие срабатывания исключительной ситуации). И, поскольку время вызова данного метода ровно как и освобождение памяти из под объекта от нас не зависят, его вызов также не является детерменированным. Мало того, он является асинхронным, т.к. осуществяется в отдельном потоке во время исполнения приложения. Это важно помнить, т.к. если, например, ваше приложение имеет логику повторной попытки работы с ресурсом и если произошла какая-то ошибка (например, `ThreadAbortException`), в результате которой ресурсы "повисли" в очереди на финализацию, то это значит, что вы не сможете открыть этот ресурс (например, файл), пока не отработает очередь на финализацию, в которой этот ресурс будет освобождён.
+
+Однако, там где есть неопределенность, программисту всегда хочется внести определенность и как результат, возник интерфейс `IDisposable`, речь о котором пойдет чуть позже, в следующей главе. Я сейчас могу сказать лишь одно: он реализуется если необходимо, чтобы внешний код мог самостоятельно отдать команду на освобождение ресурсов объекта. Т.е. детерменированно сообщить объекту, что он более не нужен.
+
+### В защиту текущего подхода
+
+Мы никогда не задумывались (а может только я?) над тем, что было бы, будь всё по-другому: если бы память освобождалась детерменированно. Текущий подход с автоматической памятью, когда мы не задумываемся, где выделять объекты и когда их освобождать нам не всегда нравится: ведь бывают случаи, когда готовых к освобождению объектов накапливается слишком много и их освобождение тормозит всё приложение. Однако, в защиту текущего подхода давайте немного отвлечемся на сценарии, о которых иногда начинаешь задумываться, мечтая сменить текущий набор алгоритмов:
+
+ - если вместо того чтобы освобождать объекты по срабатыванию GC мы будем освобождать их с потерей последней ссылки, что произойдёт? Вот наш код присваивает некоторой переменной `null`. Тогда получается, что на каждом присвоении необходимо проверять, идёт ли присвоение `null` или какой-либо другой рабочей ссылки. Если да, то надо понять, последняя ли это была ссылка. Каждый раз считать входящие с кучи ссылки, перебирая все объекты SOH/LOH - дорого. Значит, надо чтобы каждый объект считал все входящие ссылки сам: инкрементируя и декрементируя счётчик на каждой операции. Это - дополнительное место + дополнительные действия. Плюс ко всему получается, что мы уже не можем сжимать кучу: после каждого присваивания это делать слишком дорого: подходит только метод `Sweep`. Как мы видим, уже на поверхности всплывает очень много проблем, не говоря уже о подробностях;
+ - если ввести оператор `delete`, чтобы как в C++ освобождать объекты по требованию, дополнительно воскрешает деструктор, как средство детерменированного освобождения памяти: ведь если мы освобождаем объект оператором `delete`, необходимо таким же образом освобождать те объекты, которыми этот объект владеет. Значит, необходим метод, который будет вызываться при разрушении объекта: деструктор экземпляра типа. Это приведет к увеличению сложности разработки и удорожанию сопровождения программ: утечки будут постоянно. Плюс ко всему прочему возникнет путаница при освобождении памяти: мы лишаемся возможности освобождать её в последней точке использования. Т.е. теперь мы должны это делать в строго отведенном месте.
+ - если вводить смешанный алгоритм: в целом чтобы работало как сейчас, но чтобы был оператор `delete`. Например, вы мне скажете, вам захочется освобождать массивы данных, которые были использованы под скачивание изображений ровно в определенный момент. Потому что если наше приложение качает изображения друг за другом и при этом они достаточно быстро становятся не нужны, то мы вхолостую выделяем кучу памяти, которая быстро копится и приводит к вызову GC. Это особенно актуально для мобильных приложений на Xamarin и элемента управления "виртуальный список", где при быстром скролле изображений они в больших количествах грузятся, а потом становятся ненужными. Если удалять их сразу, то не будет ситуации с большим GC, который испортит анимацию прокрутки. Однако, тут возникнут сложности для GC. При ручном освобождении памяти, последняя в свою очередь станет фрагментирована и может перестать вмещать в себя те массивы данных, которые вы запросите под следующие изображения. Как следствие - всё равно произойдет GC. Если блоки памяти с ручным управлением располагать в LOH, то ручное освобождение хорошо "ляжет" на его алгоритмы. Однако, всё также будет приводить к фрагментации и дальнейшему срабатыванию полного GC. Единственно верное решение - использовать пул массивов и Span/Memory для доступа к поддиапазону индексов. Но тогда зачем вводить `delete`?
+
+Тогда получается, что текущее решение - прекрасно и надо просто научиться им правильно пользоваться. Этим мы чуть позже и займемся.
+
+### Предварительные выводы
+
+Из всего сказанного можно увидеть, что у любого объекта есть некоторое время его существования. Это может показаться тривиальной мыслью, которая лежит на поверхности, но не все так однозначно:
+
+ - важно понимать, как, когда и при каких иных условиях *объект создается*. Ведь его создание занимает некоторое не всегда короткое время. И вы не можете заранее угадать, по какому алгоритму он будет создан: простым переносом указателя в случае наличия места в allocation context, вследтвие необходимости расширения или переноса allocation context, необходимости сжатия эфимерного сегмента или же необходимости создания нового эфимерного сегмента с его полным структурированием;
+ - также стоит понимать, насколько "популярным" будет объект *во время его жизни* и как долго он будет существовать: какое количество иных объектов будет на него ссылаться и как долго. Этот фактор влияет как на сборку мусора, фрагментацию кучи, время создания других объектов и что самое интересное - на время обхода графа объектов в фазе маркировки достижимых объектов сборщиком мусора;
+ - а также, что логично и очень важно: как объект будет *достигать* состояния освобождения (состояние выброшенности звучит грустно). Это значит, будет ли осуществляться детерменированное его разрушение или нет. Например, при помощи `IDisposable.Dispose`
+ - и освобождаться - быть подхваченным Garbage Collector'ом с дальнейшей возможностью вызова финализатора.
+
+Каждый из этих этапов определяет производительность приложения и логичность его архитектуры. Рассмотрим каждый этап в отдельности.
+
+## Создание объекта
+
+Все начинается с операции запроса памяти к подсистеме управления памятью:
+
+```csharp
+var x = new A();
+```
+
+Эта казалось бы самая простая операция платформы .NET кроет в себе огромный пласт работы, который будет описан в отдельной главе.
\ No newline at end of file
diff --git a/book/ru/ReferenceTypesVsValueTypes.md b/book/ru/Memory/01-08-MemoryManagement-RefVsValueTypes.md
similarity index 98%
rename from book/ru/ReferenceTypesVsValueTypes.md
rename to book/ru/Memory/01-08-MemoryManagement-RefVsValueTypes.md
index 105ddd2..bd965c5 100644
--- a/book/ru/ReferenceTypesVsValueTypes.md
+++ b/book/ru/Memory/01-08-MemoryManagement-RefVsValueTypes.md
@@ -1,8 +1,6 @@
-# Reference Types vs Value Types
+# Ссылочные и значимые типы данных
-> [Ссылка на обсуждение](https://github.com/sidristij/dotnetbook/issues/57)
-
-Давайте в первую очередь поговорим про Reference Types и Value Types. И если говорить про разницу между ними и про полезность каждого из типов, то первое, о чем я бы упомянул - так это о своих мыслях об их названии. На мой скромный взгляд, если бы в русскоязычном сегменте их назвали ссылочные и значимые типы вместо проговаривания Value Types и Reference Types, то с пониманием разницы между ними все бы встало на свои места.
+Теперь, когда мы укрепили свои знания основ в управлении памятью в .NET, давайте поговорим про Reference Types и Value Types. И если говорить про разницу между ними и про полезность каждого из типов, то первое, о чем я бы упомянул - так это о своих мыслях об их названии. На мой скромный взгляд, если бы в русскоязычном сегменте их назвали ссылочные и значимые типы вместо проговаривания Value Types и Reference Types, то с пониманием разницы между ними все бы встало на свои места.
> Очень часто при вопросе что такое ссылочные и значимые типы, люди отвечают, что ссылочные живут в куче, а значимые - в стеке. И это в корне неправильно. Это настолько маленькая часть правды, что правдой не может считаться в принципе
@@ -816,3 +814,5 @@ public struct Char : IComparable, IConvertible
## Ссылки
- [Библиотечка для получения чистого указателя на объект](https://github.com/mumusan/dotnetex/blob/master/libs/)
+
+> Далее: [Структура объектов в памяти](./2-ObjectsStructure.md)
\ No newline at end of file
diff --git a/book/ru/LifetimeManagement/2-Disposable.md b/book/ru/Memory/01-10-MemoryManagement-IDisposable.md
similarity index 94%
rename from book/ru/LifetimeManagement/2-Disposable.md
rename to book/ru/Memory/01-10-MemoryManagement-IDisposable.md
index d2ea4d4..c264fb4 100644
--- a/book/ru/LifetimeManagement/2-Disposable.md
+++ b/book/ru/Memory/01-10-MemoryManagement-IDisposable.md
@@ -17,19 +17,19 @@ public interface IDisposable
}
```
-Для чего же создан интерфейс? Ведь если у нас есть умный Garbage Collector, который за нас чистит всю память, делает так, чтобы мы вообще не задумывались о том, как чистить память, то становится не совсем понятно, зачем её вообще чистить. Однако есть нюансы. Существует некоторое заблуждение, что ```IDisposable``` сделан, чтобы освобождать неуправляемые ресурсы. И это только часть правды. Чтобы одномоментно понять, что это не так, достаточно вспомнить примеры неуправляемых ресурсов. Является ли неуправляемым класс ```File```? Нет. Может быть, ```DbContext```? Опять же - нет. Неуправляемый ресурс - это то, что не входит в систему типов .NET. То, что не было создано платформой, и находящееся вне её скоупа. Простой пример - это дескриптор открытого файла в операционной системе. Дескриптор - это некоторое число, которое однозначно идентифицирует открытый операционной системой файл. Не вами, а именно операционной системой. Т.е. всё управляющие структуры (такие как координаты файла на файловой системе, его фрагменты в случае фрагментации и прочая служебная информация, номера цилиндра, головки, сектора - в случае магнитного HDD) находятся не внутри платформы .NET, а внутри ОС. И единственным неуправляемым ресурсом, который уходит в платформу .NET, является IntPtr - число. Это число в свою очередь оборачивается FileSafeHandle, который в свою очередь оборачивается классом File. Т.е. класс File сам по себе неуправляемым ресурсом не является, но аккумулирует в себе, используя дополнительную прослойку в виде IntPtr, неуправляемый ресурс – дескриптор открытого файла. Как происходит чтение из такого файла? Через ряд методов WinAPI или ОС Linux.
+Для чего же создан интерфейс? Ведь если у нас есть умный Garbage Collector, который за нас чистит всю память и делает так, чтобы мы вообще не задумывались о том, как её чистить, то становится не совсем понятно, зачем её вообще заниматься этим вопросом. Однако есть нюансы. Существует некоторое заблуждение, что ```IDisposable``` сделан, чтобы освобождать неуправляемые ресурсы. И это только часть правды. Чтобы одномоментно понять, что это не так, достаточно вспомнить примеры неуправляемых ресурсов. Является ли неуправляемым класс ```File```? Нет. Может быть, ```DbContext```? И опять же - нет. Неуправляемый ресурс - это то, что не входит в систему типов .NET. То, что не было создано платформой, и находящееся вне её скоупа. Простой пример - это дескриптор открытого файла в операционной системе. Дескриптор - это некоторое число, которое однозначно идентифицирует открытый операционной системой файл. Не вами, а именно операционной системой (вы только просите, а открывает его всё-таки оперционная система). Т.е. все управляющие структуры (такие как координаты файла на файловой системе, его фрагменты в случае фрагментации и прочая служебная информация, номера цилиндра, головки, сектора - в случае магнитного HDD) находятся не внутри платформы .NET, а внутри ОС. И единственным неуправляемым ресурсом, который уходит в платформу .NET, является IntPtr - число. Это число в свою очередь оборачивается FileSafeHandle, который в свою очередь оборачивается классом File. Т.е. класс File сам по себе неуправляемым ресурсом не является, но аккумулирует в себе, используя дополнительную прослойку в виде IntPtr, неуправляемый ресурс – дескриптор открытого файла. Как происходит чтение из такого файла? Через ряд методов WinAPI или ОС Linux.
Вторым примером неуправляемых ресурсов являются примитивы синхронизации в многопоточных и мультипроцессных программах. Такие как мьютексы, семафоры. Или же массивы данных, которые передаются через P/Invoke.
> Стоит заметить, что ОС не просто передаёт приложению дескриптор неуправляемого ресурса, но дополнительно сохраняет его в таблице открытых дескрипторов процесса. Cохраняя при этом за собой возможность корректного закрытия этих ресурсов при завершении работы приложения. Т.е. другими словами при выходе из приложения ресурсы закрыты будут в любом случае. Однако время работы приложения может быть разным и как результат - можно получить заблокированный надолго ресурс.
-Хорошо. С неуправляемыми ресурсами разобрались. Зачем же IDisposable в этих случаях? Затем, что .NET Framework понятия не имеет о том, что происходит там, где его нет. Если вы открываете файл при помощи функций ОС, .NET ничего об этом не узнаёт. Если вы выделите участок памяти под собственные нужды (например, при помощи VirtualAlloc), .NET также ничего об этом не узнаёт. А если он ничего об этом не знает, он не освободит память, которая была занята вызовом VirtualAlloc. Или не закроет файл, открытый напрямую через вызов API ОС. Последствия этого могут быть совершенно разными и непредсказуемыми. Вы можете получить OutOfMemory, если выделяете слишком много памяти и не будете её освобождать (а, например, по старой памяти будете просто обнулять указатель) либо заблокируете на долгое время файл на файловой шаре, если он был открыт через средства ОС, но не был закрыт. Пример с файловыми шарами особенно хорош, потому что блокировка останется даже после закрытия соединения с сервером - на стороне IIS. А прав на освобождение блокировки у вас может не быть и придётся делать запрос администраторам на `iisreset` либо ручное закрытие ресурсов при помощи специализированного ПО. Таким образом, решение этой проблемы может стать не тривиальной задачей на удалённом сервере.
+Хорошо. С неуправляемыми ресурсами разобрались. Зачем же IDisposable в этих случаях? Затем, что .NET Framework понятия не имеет о том, что происходит там, где его нет. Если вы открываете файл при помощи функций ОС, .NET ничего об этом не узнаёт. Если вы выделите участок памяти под собственные нужды (например, при помощи VirtualAlloc), .NET также ничего об этом не узнает. А если он ничего об этом не знает, он не освободит память, которая была занята вызовом VirtualAlloc. Или не закроет файл, открытый напрямую через вызов API ОС. Последствия этого могут быть совершенно разными и непредсказуемыми. Вы можете получить OutOfMemory, если выделяете слишком много памяти и не будете её освобождать (а, например, по старой памяти будете просто обнулять указатель) либо заблокируете на долгое время файл на файловой шаре, если он был открыт через средства ОС, но не был закрыт. Пример с файловыми шарами особенно хорош, потому что блокировка останется даже после закрытия соединения с сервером - на стороне IIS. А прав на освобождение блокировки у вас может не быть и придётся делать запрос администраторам на `iisreset` либо ручное закрытие ресурсов при помощи специализированного ПО. Таким образом, решение этой проблемы может стать не тривиальной задачей на удалённом сервере.
Во всех этих случаях необходим универсальный и узнаваемый _протокол взаимодействия_ между системой типов и программистом, который однозначно будет идентифицировать те типы, которые требуют принудительного закрытия. Этот _протокол_ и есть интерфейс IDisposable. И звучит это примерно так: если тип содержит реализацию интерфейса IDisposable, то после того, как вы закончите работу с его экземпляром, вы обязаны вызвать Dispose().
И ровно по этой причине есть два стандартных пути его вызова. Ведь, как правило, вы либо создаёте экземпляр сущности, чтобы быстренько с ней поработать в рамках одного метода, либо в рамках времени жизни экземпляра этой сущности.
-Первый вариант - это когда вы оборачиваете экземпляр в ```using(...){ ... }```. Т.е. вы прямо указываете, что по окончании блока using объект должен быть уничтожен. Т.е. должен быть вызван Dispose(). Второй вариант - уничтожить его по окончании времени жизни объекта, который содержит ссылку на тот, который надо освободить. Но ведь в .NET кроме метода финализации нет ничего, что намекало бы на автоматическое уничтожение объекта. Правильно? Но финализация нам совсем не подходит по той причине, что она будет неизвестно когда вызвана. А нам надо освобождать именно тогда, когда необходимо: сразу после того, как нам более не нужен, например, открытый файл. Именно поэтому мы также должны реализовать IDisposable у себя и в методе Dispose вызвать Dispose у всех, кем мы владели, чтобы освободить и их тоже. Таким образом, мы соблюдаем _протокол_, и это очень важно. Ведь если кто-то начал соблюдать некий протокол, его должны соблюдать всё участники процесса: иначе будут проблемы.
+Первый вариант - это когда вы оборачиваете экземпляр в ```using(...){ ... }```. Т.е. вы прямо указываете, что по окончании блока using объект должен быть уничтожен. Т.е. должен быть вызван Dispose(). Второй вариант - уничтожить его по окончании времени жизни объекта, который содержит ссылку на тот, который надо освободить. Но ведь в .NET кроме метода финализации нет ничего, что намекало бы на автоматическое уничтожение объекта. Правильно? Но финализация нам совсем не подходит по той причине, что она будет неизвестно когда вызвана. А нам надо освобождать именно тогда, когда необходимо нам: сразу после того, как нам более не нужен, например, открытый файл. Именно поэтому мы также должны реализовать IDisposable у себя и в методе Dispose вызвать Dispose у всех, кем мы владели, чтобы освободить и их тоже. Таким образом, мы соблюдаем _протокол_, и это очень важно. Ведь если кто-то начал соблюдать некий протокол, его должны соблюдать все участники процесса: иначе будут проблемы.
## Вариации реализации IDisposable
@@ -49,7 +49,7 @@ public class ResourceHolder : IDisposable
}
```
-Т.е. для начала мы создаём экземпляр некоторого ресурса, который должен быть освобождён; этот ресурс и освобождается в методе Dispose(). Единственное, чего здесь нет и что делает реализацию не консистентной, - это возможность дальнейшей работы с экземпляром класса после его разрушения методом ```Dispose()```:
+Т.е. для начала мы создаём экземпляр некоторого ресурса, который должен быть освобождён: этот ресурс и освобождается в методе Dispose(). Единственное, чего здесь нет и что делает реализацию не консистентной, - это возможность дальнейшей работы с экземпляром класса после его разрушения методом ```Dispose()```:
```csharp
public class ResourceHolder : IDisposable
@@ -106,7 +106,7 @@ public class FileWrapper : IDisposable
}
```
-Так какая разница в поведении двух последних примеров? В первом варианте у нас описано взаимодействие управляемого ресурса с другим управляемым. Это означает, что в случае корректной работы программы ресурс будет освобождён в любом случае. Ведь ```DisposableResource``` у нас - управляемый, а значит, .NET CLR о нём прекрасно знает и, в случае некорректного поведения, освободит из-под него память. Заметьте, что я намеренно не делаю никаких предположений о том, что тип ```DisposableResource``` инкапсулирует. Там может быть какая угодно логика и структура. Она может содержать как управляемые, так и неуправляемые ресурсы. *Нас это волновать не должно*. Нас же не просят каждый раз декомпилировать чужие библиотеки и смотреть, какие типы что используют: управляемые или неуправляемые ресурсы. А если *наш тип* использует неуправляемый ресурс, мы не можем этого не знать. Это мы делаем в классе ```FileWrapper```. Так что же произойдёт в этом случае?
+Так какая разница в поведении двух последних примеров? В первом варианте у нас описано взаимодействие управляемого ресурса с другим управляемым. Это означает, что в случае корректной работы программы ресурс будет освобождён в любом случае. Ведь ```DisposableResource``` у нас - управляемый, а значит, .NET CLR о нём прекрасно знает и, в случае некорректного поведения освободит из-под него память. Заметьте, что я намеренно не делаю никаких предположений о том, что тип ```DisposableResource``` инкапсулирует. Там может быть какая угодно логика и структура. Она может содержать как управляемые, так и неуправляемые ресурсы. *Нас это волновать не должно*. Нас же не просят каждый раз декомпилировать чужие библиотеки и смотреть, какие типы что используют: управляемые или неуправляемые ресурсы. А если *наш тип* использует неуправляемый ресурс, мы не можем этого не знать. Это мы делаем в классе ```FileWrapper```. Так что же произойдёт в этом случае?
Если мы используем неуправляемые ресурсы, получается, что у нас опять же два варианта: когда всё хорошо и метод Dispose вызвался (тогда всё хорошо) и когда что-то случилось и метод Dispose отработать не смог. Сразу оговоримся, почему этого может не произойти:
@@ -200,7 +200,7 @@ public class FileWrapper : IDisposable
Однако в этом коде существует очень серьёзная проблема, которая не даст ему работать так, как задумали мы. Если мы вспомним, как отрабатывает процесс сборки мусора, то заметим одну деталь. При сборке мусора GC *в первую очередь* финализирует всё, что напрямую унаследовано от *Object*, после чего принимается за те объекты, которые реализуют *CriticalFinalizerObject*. У нас же получается, что оба класса, которые мы спроектировали, наследуют Object: и это проблема. Мы понятия не имеем, в каком порядке мы уйдём на "последнюю милю". Тем не менее, более высокоуровневый объект может пытаться работать с объектом, который хранит неуправляемый ресурс - в своём финализаторе (хотя это уже звучит как плохая идея). Тут нам бы сильно пригодился порядок финализации. И для того чтобы его задать - мы должны унаследовать наш тип, инкапсулирующий unmanaged ресурс, от `CriticalFinalizerObject`.
-Вторая причина имеет более глубокие корни. Представьте себе, что вы позволили себе написать приложение, которое не сильно заботится о памяти. Аллоцирует в огромных количествах без кеширования и прочих премудростей. Однажды такое приложение завалится с OutOfMemoryException. А когда приложение падает с этим исключением, возникают особые условия исполнения кода: ему нельзя что-либо пытаться аллоцировать. Ведь это приведёт к повторному исключению, даже если предыдущее было поймано. Это вовсе не обозначает, что мы не должны создавать новые экземпляры объектов. К этому исключению может привести обычный вызов метода. Например, вызов метода финализации. Напомню, что методы компилируются тогда, когда они вызываются в первый раз. И это обычное поведение. Как же уберечься от этой проблемы? Достаточно легко. Если вы унаследуете объект от *CriticalFinalizerObject*, то *всё* методы этого типа будут компилироваться сразу же, при загрузке типа в память. Мало того, если вы пометите методы атрибутом *[PrePrepareMethod]*, то они также будут предварительно скомпилированы и будут безопасными с точки зрения вызова при нехватке ресурсов.
+Вторая причина имеет более глубокие корни. Представьте себе, что вы позволили себе написать приложение, которое не сильно заботится о памяти. Аллоцирует в огромных количествах без кеширования и прочих премудростей. Однажды такое приложение завалится с OutOfMemoryException. А когда приложение падает с этим исключением, возникают особые условия исполнения кода: ему нельзя что-либо пытаться аллоцировать. Ведь это приведёт к повторному исключению, даже если предыдущее было поймано. Это вовсе не обозначает, что мы не должны создавать новые экземпляры объектов. К этому исключению может привести обычный вызов метода. Например, вызов метода финализации. Напомню, что методы компилируются тогда, когда они вызываются в первый раз. И это обычное поведение. Как же уберечься от этой проблемы? Достаточно легко. Если вы унаследуете объект от *CriticalFinalizerObject*, то *все* методы этого типа будут компилироваться сразу же, при загрузке типа в память. Мало того, если вы пометите методы атрибутом *[PrePrepareMethod]*, то они также будут предварительно скомпилированы и будут безопасными с точки зрения вызова при нехватке ресурсов.
Почему это так важно? Зачем тратить так много усилий на тех, кто уйдёт в мир иной? А всё дело в том, что неуправляемые ресурсы могут повиснуть в системе очень надолго. Даже после того, как ваше приложение завершит работу. Даже после перезагрузки компьютера: если пользователь открыл в вашем приложении файл с сетевого диска, тот будет заблокирован удалённым хостом и отпущен либо по тайм-ауту, либо когда вы освободите ресурс, закрыв файл. Если ваше приложение вылетит в момент открытого файла, то он не будет закрыт даже после перезагрузки. Придётся ждать достаточно продолжительное время для того, чтобы удалённый хост отпустил бы его. Плюс ко всему вам нельзя допускать выброса исключений в финализаторах - это приведёт к ускоренной гибели CLR и окончательному выбросу из приложения: вызовы финализаторов не оборачиваются *try .. catch*. Т.е. освобождая ресурс, вам надо быть уверенными в том, что он ещё может быть освобождён. И последний не менее интересный факт - если CLR осуществляет аварийную выгрузку домена, финализаторы типов, производных от *CriticalFinalizerObject*, также будут вызваны, в отличие от тех, кто наследовался напрямую от *Object*.
@@ -316,7 +316,7 @@ public abstract class SafeHandle : CriticalFinalizerObject, IDisposable
}
```
-Чтобы оценить полезность группы классов, производных от SafeHandle, достаточно вспомнить, чём хороши всё .NET типы: автоматизированностью уборки мусора. Т.о., оборачивая неуправляемый ресурс, SafeHandle наделяет его такими же свойствами, т.к. является управляемым. Плюс ко всему он содержит внутренний счётчик внешних ссылок, которые не могут быть учтены CLR. Т.е. ссылками из unsafe кода. Вручную увеличивать и уменьшать счётчик нет почти никакой необходимости: когда вы объявляете любой тип, производный от SafeHandle, как параметр unsafe метода, то при входе в метод счётчик будет увеличен, а при выходе - уменьшён. Это свойство введено по той причине, что когда вы перешли в unsafe код, передав туда дескриптор, то в другом потоке (если вы, конечно, работаете с одним дескриптором из нескольких потоков) обнулив ссылку на него, получите собранный SafeHandle. Со счётчиком же ссылок всё проще: SafeHandle не будет собран, пока дополнительно не обнулится счётчик. Вот почему вручную менять счётчик не стоит. Либо это надо делать очень аккуратно: возвращая его, как только это становится возможным.
+Чтобы оценить полезность группы классов, производных от SafeHandle, достаточно вспомнить, чем хороши все .NET типы: автоматизированностью уборки мусора. Т.о., оборачивая неуправляемый ресурс, SafeHandle наделяет его такими же свойствами, т.к. является управляемым. Плюс ко всему он содержит внутренний счётчик внешних ссылок, которые не могут быть учтены CLR. Т.е. ссылками из unsafe кода. Вручную увеличивать и уменьшать счётчик нет почти никакой необходимости: когда вы объявляете любой тип, производный от SafeHandle, как параметр unsafe метода, то при входе в метод счётчик будет увеличен, а при выходе - уменьшён. Это свойство введено по той причине, что когда вы перешли в unsafe код, передав туда дескриптор, то в другом потоке (если вы, конечно, работаете с одним дескриптором из нескольких потоков) обнулив ссылку на него, получите собранный SafeHandle. Со счётчиком же ссылок всё проще: SafeHandle не будет собран, пока дополнительно не обнулится счётчик. Вот почему вручную менять счётчик не стоит. Либо это надо делать очень аккуратно: возвращая его, как только это становится возможным.
Второе назначение счётчика ссылок - это задание порядка финализации ```CriticalFinalizerObject```, которые друг на друга ссылаются. Если один SafeHandle-based тип ссылается на другой SafeHandle-based тип, то в конструкторе ссылающегося необходимо дополнительно увеличить счётчик ссылок, а в методе ReleaseHandle - уменьшить. Таким образом, ваш объект не будет уничтожен, пока не будет уничтожен тот, на который вы сослались. Однако чтобы не путаться, стоит избегать таких ситуаций.
@@ -522,7 +522,7 @@ public class FileWrapper : IDisposable
Второе, и на мой взгляд, самоё важное. Мы допускаем ситуацию одновременного разрушения объекта с возможностью поработать с ним ещё разок. На что мы вообще должны надеяться в данном случае? Что не выстрелит? Ведь если сначала отработает Dispose, то дальнейшее обращение с методами объекта обязано привести к ```ObjectDisposedException```. Отсюда возникает простой вывод: синхронизацию между вызовами Dispose() и остальными публичными методами типа необходимо делегировать обслуживающей стороне. Т.е. тому коду, который создал экземпляр класса ```FileWrapper```. Ведь только создающая сторона в курсе, что она собирается делать с экземпляром класса и когда она собирается его разрушать.
-С другой стороны по требованиям к архитектуре классов, реализующих IDisposable вызов Dispose должен выкидывать только критические ошибки (такие как `OutOfMemoryException`, но не IOException, например). Это в частности значит, что если Dispose вызовется более чём из одного потока одновременно, то может произойти ситуация, когда разрушение сущности будет происходить одновременно из двух потоков (проскочим проверку `if(_disposed) return;`). Тут зависит от ситуации: если освобождение ресурсов *может* идти несколько раз, то никаких дополнительных проверок не потребуется. Если же нет, необходима защита:
+С другой стороны по требованиям к архитектуре классов, реализующих IDisposable вызов Dispose должен выкидывать только критические ошибки (такие как `OutOfMemoryException`, но не IOException, например). Это в частности значит, что если Dispose вызовется более чем из одного потока одновременно, то может произойти ситуация, когда разрушение сущности будет происходить одновременно из двух потоков (проскочим проверку `if(_disposed) return;`). Тут зависит от ситуации: если освобождение ресурсов *может* идти несколько раз, то никаких дополнительных проверок не потребуется. Если же нет, необходима защита:
```csharp
// Я намеренно не привожу весь шаблон, т.к. пример будет большим
@@ -580,16 +580,16 @@ public class Disposable : IDisposable
}
```
-Что здесь не так и почему мы ранее в этой книге никогда так не писали? На самом деле шаблон хороший и без лишних слов охватывает всё жизненные ситуации. Но его использование повсеместно, на мой взгляд, не является правилом хорошего тона: ведь реальных неуправляемых ресурсов мы в практике почти никогда не видим, и в этом случае полшаблона работает в холостую. Мало того, он нарушает принцип разделения ответственности. Ведь он одновременно управляет и управляемыми ресурсами и неуправляемыми. На мой скромный взгляд, это совершенно не правильно. Давайте взглянем на несколько иной подход. *Disposable Design Principle*. Если коротко, то суть в следующем:
+Что здесь не так и почему мы ранее в этой книге никогда так не писали? На самом деле шаблон хороший и без лишних слов охватывает все жизненные ситуации. Но его использование повсеместно, на мой взгляд, не является правилом хорошего тона: ведь реальных неуправляемых ресурсов мы в практике почти никогда не видим, и в этом случае полшаблона работает в холостую. Мало того, он нарушает принцип разделения ответственности. Ведь он одновременно управляет и управляемыми ресурсами и неуправляемыми. На мой скромный взгляд, это совершенно не правильно. Давайте взглянем на несколько иной подход. *Disposable Design Principle*. Если коротко, то суть в следующем:
Disposing разделяется на два уровня классов:
- Типы Level 0 напрямую инкапсулируют неуправляемые ресурсы
- Они являются либо абстрактными, либо запакованными
- - Всё методы должны быть помечены:
+ - Все методы должны быть помечены:
- PrePrepareMethod, чтобы метод был скомпилирован вместе с загрузкой типа
- SecuritySafeCritical, чтобы выставить защиту на вызов из кода, работающего под ограничениями
- - ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success / MayFail)] чтобы выставить CER на метод и всё его дочерние вызовы
+ - ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success / MayFail)] чтобы выставить CER на метод и все его дочерние вызовы
- Могут ссылаться на типы нулевого уровня, но должны увеличивать счётчик ссылающихся объектов, чтобы гарантировать порядок выхода на "последнюю милю"
- Типы Level 1 инкапсулируют только управляемые ресурсы
- Наследуются только от типов Level 1 либо реализуют IDisposable напрямую
@@ -603,7 +603,7 @@ Disposing разделяется на два уровня классов:
## Как ещё используется Dispose
-Идеологически IDisposable был создан для освобождения неуправляемых ресурсов. Но как и для многих других шаблонов оказалось, что он очень полезен и для других задач. Например, для освобождения ссылок на управляемые ресурсы. Звучит как-то не очень полезно: освобождать управляемые ресурсы. Ведь нам же объяснили, что управляемые ресурсы - они на то и управляемые, чтобы мы расслабились и смотрели в сторону разработчиков C/C++ с едва заметной ухмылкой. Однако всё не совсем так. Мы всегда можем получить ситуацию, когда мы теряем ссылку на объект и думаем, что всё хорошо: GC соберёт мусор, а вместе с ним и наш объект. Однако, выясняется, что память растёт, мы лезем в программу анализа памяти и видим, что на самом деле этот объект удерживается чём-то ещё. Всё дело в том, что как в платформе .NET, так и в архитектуре внешних классов может присутствовать логика неявного захвата ссылки на вашу сущность. После чего, ввиду не явности захвата, программист может пропустить необходимость её освобождения и получить на выходе утечку памяти.
+Идеологически IDisposable был создан для освобождения неуправляемых ресурсов. Но как и для многих других шаблонов оказалось, что он очень полезен и для других задач. Например, для освобождения ссылок на управляемые ресурсы. Звучит как-то не очень полезно: освобождать управляемые ресурсы. Ведь нам же объяснили, что управляемые ресурсы - они на то и управляемые, чтобы мы расслабились и смотрели в сторону разработчиков C/C++ с едва заметной ухмылкой. Однако всё не совсем так. Мы всегда можем получить ситуацию, когда мы теряем ссылку на объект и думаем, что всё хорошо: GC соберёт мусор, а вместе с ним и наш объект. Однако, выясняется, что память растёт, мы лезем в программу анализа памяти и видим, что на самом деле этот объект удерживается чем-то ещё. Всё дело в том, что как в платформе .NET, так и в архитектуре внешних классов может присутствовать логика неявного захвата ссылки на вашу сущность. После чего, ввиду не явности захвата, программист может пропустить необходимость её освобождения и получить на выходе утечку памяти.
### Делегаты, events
@@ -690,7 +690,7 @@ public event Action OnDisposed {
}
```
-Механизм сообщений в C# скрывает внутреннее устройство event'ов и удерживает всё объекты, которые подписались на обновления через `event`. Если что-то пойдёт не так, ссылка на чужой объект останется в `OnDisposed` и будет его удерживать. Получается странная ситуация: архитектурно мы имеем понятие "источник событий", которое по своей логике не должно что-либо удерживать. По факту мы имеем неявное удерживание объектов, подписавшихся на обновления. При этом не имеем возможности что-либо менять внутри этого массива делегатов: хоть сущность и является частью нас, нам на это прав не давали. Единственное что мы можем - это затереть весь список полностью, присвоив источнику событий null. Второй способ - явно реализовать методы `add`/`remove` чтобы ввести управление над коллекцией делегатов.
+Механизм сообщений в C# скрывает внутреннее устройство event'ов и удерживает все объекты, которые подписались на обновления через `event`. Если что-то пойдёт не так, ссылка на чужой объект останется в `OnDisposed` и будет его удерживать. Получается странная ситуация: архитектурно мы имеем понятие "источник событий", которое по своей логике не должно что-либо удерживать. По факту мы имеем неявное удерживание объектов, подписавшихся на обновления. При этом не имеем возможности что-либо менять внутри этого массива делегатов: хоть сущность и является частью нас, нам на это прав не давали. Единственное что мы можем - это затереть весь список полностью, присвоив источнику событий null. Второй способ - явно реализовать методы `add`/`remove` чтобы ввести управление над коллекцией делегатов.
> Кстати, тут возникает ещё одна неявная ситуация: может показаться, что если вы присвоите источнику событий null, то дальнейшая подписка на события приведёт к `NullReferenceException`. И на мой скромный взгляд это было бы логичнее. Однако это не так: если внешний код подпишется на события, после того как источник событий будет очищен, FCL создаст новый экземпляр класса Action и положит его в `OnDisposed`. Эта неясность в языке C# может запутать программиста: работа с обнулёнными полями должна вызывать в вас не чувство спокойствия, а скорее тревогу. Тут же демонстрируется подход, когда излишняя расслабленность может привести программиста к утечкам памяти.
@@ -730,7 +730,7 @@ subscription.Dispose();
### Защита от ThreadAbort
-Когда разрабатывается библиотека для внешнего разработчика, вы никак не можете гарантировать, как она себя поведёт в чужом приложении. Иногда остаётся только догадываться, что такого с нашей библиотекой сделал чужой программист, что появился тот или иной результат её работы. Один из примеров - работа в многопоточной среде, когда вопрос целостности очистки ресурсов может встать достаточно остро. Причём, если при написании кода `Dispose()` метода сами мы можем дать гарантии на отсутствие исключительных ситуаций, то мы не можем гарантировать, что прямо во время работы метода `Dispose()` не вылетит `ThreadAbortException`, который отключит наш поток исполнения. Тут стоит вспомнить тот факт, что когда бросается `ThreadAbortException`, то в любом случае выполняются всё catch/finally блоки (в конце catch/finally ThreadAbort бросается дальше). Таким образом, чтобы что-то сделать гарантированно (гарантировав неразрывность при помощи Thread.Abort), надо обернуть критичный участок в `try { ... } finally { ... }`. В этом случае даже если бросят ThreadAbort, код будет выполнен.
+Когда разрабатывается библиотека для внешнего разработчика, вы никак не можете гарантировать, как она себя поведёт в чужом приложении. Иногда остаётся только догадываться, что такого с нашей библиотекой сделал чужой программист, что появился тот или иной результат её работы. Один из примеров - работа в многопоточной среде, когда вопрос целостности очистки ресурсов может встать достаточно остро. Причём, если при написании кода `Dispose()` метода сами мы можем дать гарантии на отсутствие исключительных ситуаций, то мы не можем гарантировать, что прямо во время работы метода `Dispose()` не вылетит `ThreadAbortException`, который отключит наш поток исполнения. Тут стоит вспомнить тот факт, что когда бросается `ThreadAbortException`, то в любом случае выполняются все catch/finally блоки (в конце catch/finally ThreadAbort бросается дальше). Таким образом, чтобы что-то сделать гарантированно (гарантировав неразрывность при помощи Thread.Abort), надо обернуть критичный участок в `try { ... } finally { ... }`. В этом случае даже если бросят ThreadAbort, код будет выполнен.
```csharp
void Dispose()
@@ -775,8 +775,8 @@ void Dispose()
Минусов шаблона я вижу намного больше, чем плюсов:
- 1. С одной стороны получается, что любой тип, реализующий этот шаблон, отдаёт тем самым команду всём, кто его будет использовать: используя меня, вы принимаете публичную оферту. Причём так неявно это сообщает, что, как и в случае публичных оферт, пользователь типа не всегда в курсе, что у типа есть этот интерфейс. Приходится, например, следовать подсказкам IDE (ставить точку, набирать Dis.. и проверять, есть ли метод в отфильтрованном списке членов класса). И если Dispose замечен, реализовывать шаблон у себя. Иногда это может случиться не сразу, и тогда реализацию шаблона придётся протягивать через систему типов, которая участвует в функционале. Хороший пример: а вы знали что ```IEnumerator``` тянет за собой ```IDisposable```?
- 2. Зачастую, когда проектируется некий интерфейс, встаёт необходимость вставки IDisposable в систему интерфейсов типа: когда один из интерфейсов вынужден наследовать IDisposable. На мой взгляд, это вносит "кривь" в те интерфейсы, которые мы спроектировали. Ведь когда проектируется интерфейс, вы прежде всего проектируете некий протокол взаимодействия. Тот набор действий, которые можно сделать *с чём-либо*, скрывающимся под интерфейсом. Метод Dispose() - метод разрушения экземпляра класса. Это входит в разрез с сущностью *протокол взаимодействия*. Это по сути - подробности реализации, которые просочились в интерфейс;
+ 1. С одной стороны получается, что любой тип, реализующий этот шаблон, отдаёт тем самым команду всем, кто его будет использовать: используя меня, вы принимаете публичную оферту. Причём так неявно это сообщает, что, как и в случае публичных оферт, пользователь типа не всегда в курсе, что у типа есть этот интерфейс. Приходится, например, следовать подсказкам IDE (ставить точку, набирать Dis.. и проверять, есть ли метод в отфильтрованном списке членов класса). И если Dispose замечен, реализовывать шаблон у себя. Иногда это может случиться не сразу, и тогда реализацию шаблона придётся протягивать через систему типов, которая участвует в функционале. Хороший пример: а вы знали что ```IEnumerator``` тянет за собой ```IDisposable```?
+ 2. Зачастую, когда проектируется некий интерфейс, встаёт необходимость вставки IDisposable в систему интерфейсов типа: когда один из интерфейсов вынужден наследовать IDisposable. На мой взгляд, это вносит "кривь" в те интерфейсы, которые мы спроектировали. Ведь когда проектируется интерфейс, вы прежде всего проектируете некий протокол взаимодействия. Тот набор действий, которые можно сделать *с чем-либо*, скрывающимся под интерфейсом. Метод Dispose() - метод разрушения экземпляра класса. Это входит в разрез с сущностью *протокол взаимодействия*. Это по сути - подробности реализации, которые просочились в интерфейс;
3. Несмотря на детерминированность, Dispose() не означает прямого разрушения объекта. Объект всё ещё будет существовать после его *разрушения*. Просто в другом состоянии. И чтобы это стало правдой, вы обязаны вызывать CheckDisposed() в начале каждого публичного метода. Это выглядит как хороший такой костыль, который отдали нам со словами: "плодите и размножайте!";
4. Есть ещё маловероятная возможность получить тип, который реализует ```IDisposable``` через *explicit* реализацию. Или получить тип, реализующий IDisposable без возможности определить, кто его должен разрушать: сторона, которая выдала, или вы сами. Это породило антипаттерн множественного вызова Dispose(), который, по сути, позволяет разрешать разрушенный объект;
5. Полная реализация сложна. Причём она различна для управляемых и неуправляемых ресурсов. В этом плане попытка облегчить жизнь разработчикам через GC выглядит немного нелепо. Можно, конечно, вводить некий тип DisposableObject, который реализует весь шаблон, отдав `virtual void Dispose()` метод для переопределения, но это не решит других проблем, связанных с шаблоном;
@@ -786,7 +786,7 @@ void Dispose()
## Выгрузка домена и выход из приложения
-Если вы сюда дошли, значит, вы стали как минимум увереннее в успешности последующих собеседований. Однако мы обсудили ещё не всё вопросы, связанные с этим, казалось бы, простым шаблоном. Последним вопросом у нас идёт вопрос: отличается ли поведение приложения при простом GC, GC во время выгрузки домена и GC во время выхода из приложения? Процедуры `Dispose()` этот вопрос касается, только если по касательной... Но `Dispose()` и финализация идут рука об руку, и редко когда мы можем видеть реализации класса, в котором есть финализация, но нет метода `Dispose()`. Потому давайте договоримся так: саму финализацию мы опишем в разделе, посвящённом финализации, а здесь лишь добавим несколько важных пунктов.
+Если вы сюда дошли, значит, вы стали как минимум увереннее в успешности последующих собеседований. Однако мы обсудили ещё не все вопросы, связанные с этим, казалось бы, простым шаблоном. Последним вопросом у нас идёт вопрос: отличается ли поведение приложения при простом GC, GC во время выгрузки домена и GC во время выхода из приложения? Процедуры `Dispose()` этот вопрос касается, только если по касательной... Но `Dispose()` и финализация идут рука об руку, и редко когда мы можем видеть реализации класса, в котором есть финализация, но нет метода `Dispose()`. Потому давайте договоримся так: саму финализацию мы опишем в разделе, посвящённом финализации, а здесь лишь добавим несколько важных пунктов.
Когда выгружается домен приложения, то выгружаются как сборки, которые были загружены в домен, так и все объекты, которые были созданы в рамках выгружаемого домена. Это значит, что, по сути, происходит очищение (сборка GC) этих объектов, и для них будут вызваны финализаторы. Если наша логика финализатора ждёт финализации других объектов, чтобы быть уничтоженным в правильном порядке, то возможно стоит обратить внимание на свойство `Environment.HasShutdownStarted`, обозначающее, что приложение в данный момент находится в состоянии выгрузки из памяти, и метод `AppDomain.CurrentDomain.IsFinalizingForUnload()`, который говорит о том, что данный домен выгружается, что и является причиной финализации. Ведь если наступили эти события, то в целом становится всё равно, в каком порядке мы должны финализировать ресурсы. Задерживать выгрузку домена и приложения мы не можем: наша задача всё сделать максимально быстро.
@@ -812,7 +812,7 @@ if (!Environment.HasShutdownStarted &&
## Типичные ошибки реализации
-Итак, как я вам показал, общего, универсального шаблона для реализации IDisposable не существует. Мало того, некоторая уверенность в автоматизме управления памятью заставляет людей путаться и принимать запутанные решения в реализации шаблона. Так, например, весь .NET Framework пронизан ошибками в его реализации. И чтобы не быть голословными, рассмотрим эти ошибки именно на примере .NET Framework. Всё реализации доступны по ссылке: [IDisposable Usages](http://referencesource.microsoft.com/#mscorlib/system/idisposable.cs,1f55292c3174123d,references)
+Итак, как я вам показал, общего, универсального шаблона для реализации IDisposable не существует. Мало того, некоторая уверенность в автоматизме управления памятью заставляет людей путаться и принимать запутанные решения в реализации шаблона. Так, например, весь .NET Framework пронизан ошибками в его реализации. И чтобы не быть голословными, рассмотрим эти ошибки именно на примере .NET Framework. Все реализации доступны по ссылке: [IDisposable Usages](http://referencesource.microsoft.com/#mscorlib/system/idisposable.cs,1f55292c3174123d,references)
**Класс FileEntry** [cmsinterop.cs](http://referencesource.microsoft.com/#mscorlib/system/deployment/cmsinterop.cs,eeedb7095d7d3053,references)
diff --git a/book/ru/Memory/01-12-MemoryManagement-Finalizer.md b/book/ru/Memory/01-12-MemoryManagement-Finalizer.md
new file mode 100644
index 0000000..5f28270
--- /dev/null
+++ b/book/ru/Memory/01-12-MemoryManagement-Finalizer.md
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/book/ru/Memory/01-14-MemoryManagement-Results.md b/book/ru/Memory/01-14-MemoryManagement-Results.md
new file mode 100644
index 0000000..5f28270
--- /dev/null
+++ b/book/ru/Memory/01-14-MemoryManagement-Results.md
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/book/ru/MemorySpan.md b/book/ru/Memory/02-02-MemoryManagement-MemorySpan.md
similarity index 100%
rename from book/ru/MemorySpan.md
rename to book/ru/Memory/02-02-MemoryManagement-MemorySpan.md
diff --git a/book/ru/Memory/03-02-MemoryManagement-Allocation.md b/book/ru/Memory/03-02-MemoryManagement-Allocation.md
new file mode 100644
index 0000000..19dd9d2
--- /dev/null
+++ b/book/ru/Memory/03-02-MemoryManagement-Allocation.md
@@ -0,0 +1,178 @@
+## Выделение памяти под объект
+
+> [In Progress] адаптация курса
+
+Мы только что поговорили про GC с высоты птичьего полета. Сейчас будем уходить в подробности. В первую очередь, хочется пообщаться про алгоритм выделения памяти.
+
+Управление памятью .NET разработчика интересует со стороны двух основных процессов: первое - это выделить память, второе - освободить. Про выделение памяти мы сейчас и поговорим.
+
+С точки зрения процессора память выглядит несколько сложнее. Если говорит про архитектуру управления памяти, я бы сказал, что она разделена на слои. Первый слой, который видим мы, слой .NET достаточно сложный, но верхоуровневый. То есть он в любом случае опирается на что-то другое, а именно на слои управления памятью Windows, который в свою очередь запущен на каком-то процессоре. Процессор по-своему интерпретирует эту память. Таким образом, у нас фактически существует три слоя управления памятью.
+
+Память с точки зрения процессора - это планка, физическая память, RAM. Сколько планок мы поставили, столько он и видит. Но у нас есть знания, что каждый запущенный в системе Windows процесс изолирован. И кроме себя он больше ничего не видит. Есть сам процесс, winapi и больше ничего вообще. Word не видит Exel. Exel не видит калькулятор. Все они изолированы полностью. Потому что на архитектуре Intel сделана виртуализация памяти.
+
+Как она работает. Есть таблицы глобальных дескрипторов, которые мы не видим, есть таблицы локальных дескрипторов, которые куда-то отсылаются. И все вместе это ссылается на Page Frame.
+
+У любого процесса есть некий диапазон памяти от нуля и до, например, 4Гб на x32 системе и до условной бесконечности на x64. Это диапазон виртуальной памяти. Процессор создает для конкретной программы некий виртуальный кусок, где есть только она. Эта область поделена на страницы: на кусочки по 4Кб. Каждый их них означает, что память в этом месте либо существует, либо ее там не существует, т.е. она физически в этом месте отсутствует полностью. Адрес есть, а памяти там нет. Это возникает в ситуациях, когда планка, например на 4Гб, вставлена в материнскую плату, а процессов у нас запущена сотня и Windows 32х разрядный. Значит, что у каждой программы 4Гб памяти, но при этом они друг от друга изолированы.
+
+Как 4 Гб поделить на сто и получить 4 каждому? Никак. Это значит, что у каждой программы на четырехгигабайтном участке есть места, где памяти не вообще, там только выделены кусочки адресов. Но где-то память есть и она замаплена на планку физическую. А если не замаплена - памяти нет. Если туда обратиться, что-то попытаться считать, вы получите не ноль, а AccessViolationException. Исключение, которое говорит о том, что вы пытаетесь работать с куском, который не существует, либо на который у вас нет прав. На данном уровне исключение различий не делает.
+
+Точно также работать с Wap. (см. слайд 06:30). На изображении выделены процессы в виде столбиков. Там, где белый шум - память выделена и заполнена. А там, где пропуски - памяти нет. Выделенные области могут быть замаплены как на физическую память, так и на жесткий диск.
+
+Если, например, программа много памяти выделила, но ей не пользуется, а соседней программе память нужна, при этом планка маленькая, то Windows неиспользуемую память отгружает на жесткий диск, а этот кусок отдает кому-то другому.
+
+Итак, в нашем .NET приложении часть памяти используется, а часть - нет. Как это можно посмотреть? Можно запустить утилиту, например есть Procmon. Они показывают, как приложение расходует память. Там видно строчки - это как раз выделенные участки. Первый столбец - это адрес участка, второй указывает хип, третий - размер участка. И еще один столбик - это commited. Что это значит? Если программа решила, что ей нужен хип, и пытается определить какой именно ей создавать. Она резервирует память под хип, и часть ее решает закоммитить. Виртуальное адресное пространство разделено на страницы. У страниц есть три статуса. Первый - свободная страница, на которой ничего нет. Ее можно брать и использовать как нужно. Второй статус - страница зарезервирована. Это значит, что какая-то часть программы под себя эту страницу зарезервировала, но там все еще нет памяти, она там отсутствует. Но страница зарезервирована для будущих нужд. Это делается для того, чтобы в аллоцированом хипе никто другой по середине себе ничего не выделил.
+
+Может показаться, что вы как хозяин своего приложения, как хотите, так и выделяете память. На самом деле нет. Если вы чисто в .NET - то может быть да. А вообще говоря, .NET часто вызывает Managed code, которому в свою очередь тоже нужна память. И он не будет выделять память в хипах от .NET - SOH и LOH. Он о них не имеет понятия.
+
+Он выделяет память в своих хипах. Если это С++, то это C++ Runtime Library. Но там есть свои механизмы построения хипов. Получается, что вы не полностью контролируете виртуальный адресный диапазон, который вам выдали. Чтобы обезопасить себя, вы сразу резервируете адресное пространство и внутри него уже начинаете аллоцировать память - коммитить ее, чтобы она стала существовать, чтобы туда можно было что-то писать.
+
+На слайде 11:50 видны свободные места и есть непрерывный участок, частично зарезервированный, частично - закоммиченный. Это место может быть в оперативной памяти, либо находится на жестком диск, потому что им давно не пользовались. Если вы обращаетесь к куску, который находится на жестком диске, то он автоматически подгружается и становится куском из оперативной памяти. Но вам это знать уже не надо.
+
+Зная все это, разберем как работает `var x = new A();`
+
+Вернемся к базовым знаниям, то, что мы обычно рассказываем на собеседованиях. Когда мы общаемся с программистами, нас спрашивают, как все работает. Мы рассказываем, что есть три поколения. Как память выделяется? Указатель смотрит на свободный участок и когда делается операция new, то мы этот адрес отдаем а указатель перемещаем на размер выделенного блока. А когда происходит GC, куча сжимается и этот указатель оказывается раньше, потому что куча сжалась. Примерно так и происходит. На слайде 13:31 у нас есть закоммиченная часть - слева до вертикальной черты. Эта память существует физически. Справа от черты - это зарезервированная за хипом часть, но ее пока не существует. Дальше мы должны вспомнить о таком понятии как allocation context. Это некое скользящее окно, внутри которого в данный момент менеджер памяти выделяет память под объекты. И уже внутри него у нас есть указатели на начало свободного места. Слева уже пошли объекты.
+
+Когда идет операция new A(), мы запоминаем этот адрес, размещаем там объект и передвигаем адрес начала свободного места, отдавая адрес в переменную x. Наш код выглядит примерно следующим образом (см.слайд 14:39). У нас есть метод Allocate(), мы попросили у него некий amount памяти. Он проверяет, есть ли у нас будущий адрес свободного места (адрес начала места плюс этот amount). Если он превысит heap size, у нас никак не получится новую память выделить, то мы бросаем OutOfMemoryException. Иначе мы спокойно отдаем этот адрес запрашивающей стороне, обновляя весь диапазон памяти, чтобы там не было лишних данных.
+
+Но может быть так, что allocation context у нас уже кончается. Тогда будет промах мимо этого контекста. Как действовать в этом случае? Аллокатор может действовать двумя путями. Первый - это выделить, например, по-минимуму, второй - выделить сколько-то до некоего максимума. Этот размер может быть от 1 до 8Кб. И выбирается он исходя из интенсивности выделения памяти. Если на данном потоке выделяется много памяти, то нам нет никакого смысла выделять маленький кусок. То есть allocation context расширяется исходя из текущих запросов.
+
+Но существует еще и многопоточность. Если она присутствует, может так получится, что несколько потоков одновременно начинают запрашивать выделения памяти. Значит, оператор new у нас должен быть потоко-безопасным, а значит и медленным. Сам по себе он быстрый, но если он выделяется без конкуренции с другими потоками, то получается, что он работает неоправданно медленно. Чтобы сделать new потоко-безопасным, надо чтобы каждый поток выделял память в каком-то своем месте. Поэтому в .NET существует по одному allocation context на каждый поток.
+
+Отсюда можно сделать еще один вывод - не стоит делать много лишних потоков. Вы делаете очень жирный хип нулевого поколения благодаря этому.
+
+Итак, много потоков у нас уже существует и каждый из них что-то выделяет. А еще у нас есть три поколения. Получается, что нулевое поколение, в котором идет выделение, начинает расти благодаря тому, что у нас растет количество потоков. Это плохо.
+
+Обратимся к идее списка свободных участков.
+
+Если мы говорим про классику, как везде heap устроен, то .NET - не исключение. Когда происходит sweep collection вместо сжатия кучи, у нас появляются свободные участки. Ими надо как-то управлять.
+
+Есть два алгоритма менеджмента свободных участков. Первый - best-fit. Когда выделяется память, среди всех свободных участков выделяется тот, который имеет либо такой же размер, либо максимально приближенный по размеру к тому, который просили. Это best-fit.
+
+Когда мы используем best-fit, нам нужно найти наилучший участок. Мы пробегаем по всем участкам, и запоминаем то, что мы прошли. Выполняется линейный поиск и наш участок может оказаться в конце. Но зато на выходе мы получаем минимальную фрагментацию - это здорово. Второй алгоритм - это first-fit. Это альтернатива. Мы идем вперед по списку и берем первый же участок, который нам подошел. Он может быть таким же по размеру, либо больше или существенно больше требуемого. Работает это быстро, но может сильно фрагментировать память. В .NET используется смесь этих подходов, организуется список свободных участков через корзины - бакеты. У каждого поколения есть список бакетов. Они группируют память по размеру от малого к большому. Бакет ссылается на односвязаный список свободных участков. Если посмотреть вниз, то от Head на свободный участок встали и дальше по односвязному списку можем идти дальше, перечисляя все свободные участки, которые относятся к этому бакету. Напомню, они организованы по размеру. И в данном бакете находятся участки с определенным диапазоном размеров. В следующем бакете будут свои диапазоны.
+
+Бакет - это first-fit, среди бакетов выбираем первый, который подходит, уходим внутрь этой корзины и там, по однозвязному списку мы идем по best-fit. Таким образом мы делаем очень хорошую оптимизацию.
+
+Что такое бакеты? Эта табличка (см. слайд 22:05) нам раскрывает глаза. Тут вспомним разницу между SOH и LOH: LOH организован только по принципу sweep collection, сжатие кучи там происходит только по запросу. Само по себе сжатие кучи там не запустится никогда. В SOH идет смесь.
+
+Посмотрим на LOH и второе поколение, которое является самым большим. Исходя из этого можно легко понять содержимое таблицы. Нулевое и первое поколение имеют количество бакетов - 1. Потому что в нулевом и первом поколении у нас нет такого, что при sweep образуется куча свободных больших участков, куда можно разместить новые контексты. Нам этот функционал бакетов там не нужен, поэтому бакет там один.
+
+А во втором поколении, которое может занимать гигабайты мелких объектов и в LOH, бакеты уже присутствуют, благодаря чему память там организована по рассказанной ранее структуре.
+
+Как происходит выделение памяти в этом случае? Сначала мы сканируем бакеты, в поисках первого, который подходит по размеру участков, находящихся внутри него. Когда мы выбрали бакет, идем по односвязному списку и в нем ищем лучший из тех, которые там присутствуют - тот, который лучше всех подойдет нам по размеру. Но есть одна особенность. Может так получится, что мы просили не так много места, а имеющиеся участки слишком большие. В этом случае мы выделим сколько необходимо, а остаток вернем в список свободных участков.
+
+Итак, мы нашли нужный участок. В этом месте лежит эмуляция объекта. Первое поле - это поле undo. Это - освободившийся участок, там лежал объект или группа объектов. Соответственно, первое поле это aSyncBlockIndex, потом - таблица виртуальных методов, потом данные. Когда этот участок стал участком свободной памяти, то первое поле стало операцией undo. Когда мы этот участок отлинкуем и сохраним ссылку на следующий участок в операции undo, чтобы можно было отменить, если что-то пойдет не так.
+
+Дальше таблица виртуальных методов свободного участка, чтобы GC понимал, с чем он имеет дело и size - размер свободного участка.
+
+После этого выделения мы проставляем aSyncBlockIndex. Таблица виртуальных методов становится та, что надо и вызывается конструктор. Все. Вот так работает выделение памяти. \
+
+## Выделение памяти в SOH
+
+Получается, что для того, чтобы выделить память сначала отрабатывает самый простой способ. Если объект влезает в allocation context, мы выделяем и отдаем. Это самый быстрый способ. Мы выходим моментально, у нас нет никаких дополнительных действий: вошли и вышли, и передвинуть указатель нам ничего не стоит.
+
+Если не влезаем в контекст, то идет первое усложнение. Контекст необходимо увеличить. Чтобы увеличить, мы используем более продвинутый способ, он находится в JIT_NEW helper.
+
+Мы пытаемся найти неиспользуемую часть в эфемерном сегменте. Когда у нас allocation context заканчивается, мы должны его куда-то перетащить. Это дорого. Поэтому мы сначала пытаемся его увеличить, нарастить. Если это не получилось, то надо перетащить.
+
+Куда мы должны его переместить и какие данные у нас для этого есть?
+
+Нужно поискать свободный участок в списке через бакеты, куда влезет наш контекст. Если участок есть, мы его передвигаем туда и пытаемся первым способом выделить там память.
+
+Если получилось - замечательно. Если не влез, мы пытаемся подкоммитить больше зарезервированной памяти: сдвинуть вертикальную черту вправо. Место зарезервировано, но памяти там еще не существует. Нам нужно расширить кучу и мы коммитим память. Сразу же мы не делаем этого, потому что commitment - операция достаточно долгая, как и резервирование. Она может занимать сотню миллисекунд. Это происходит потому, что для ее осуществления, необходимо обратиться к Windows. А операционная система максимально не доверяет тому, что в ней находится. Он отгораживает для нас песочницу, и когда мы вызываем winapi метод, то мы не просто его вызываем, а с повышением уровня привилегий. Чтобы это безопасно это сделать, необходимо, чтобы Windows скопировал фрейм метода, который вызывается из уровня приложения в уровень ядра, чтобы случайно не подхватить чужие инфицированные данные. Потом winapi функция отрабатывает и происходит возврат. В этот момент идет обратное копирование кусочка стека и идет переход из kernel части в user space. Вся эта операция занимает много времени, поэтому GC максимально старается использовать текущее пространство. И на каждом этапе он делает GC нулевого поколения: если что-то подсжимать, возможно, allocation context обратно вырастет. Когда контекст кончился, GC пытается его раздвинуть вправо, а там уже что-то есть, значит надо поискать свободные участки. Если не получилось, делается сборка мусора, может быть свободные участки все же появятся и получится разместить среди них новые данные. Когда ничего не получилось и все забито, то приходится коммитить дальше память. А если память кончилась, то вылетает OutOfMemoryException.
+
+## Выделение памяти в LOH
+
+В хипе больших объектов мы пытаемся найти неиспользуемую память, повторяя для каждого эфемерного сегмента LOH, потому что тут их может быть несколько, в зависимости от того, в каком режиме работает платоформа .NET. Поскольку в LOH по умолчанию у нас нет сжатия кучи, то управление памятью тут упрощено. Сжатие кучи - это очень жирная операция, которая требует больших вычислительных процессов. Но в LOH эта операция проводится только по запросу, а значит программист исходя из знаний о работе алгоритмов собственной программы, решил, что в данное время он готов потратить неизвестно сколько времени на сжатие огромного пространства. А значит, можно сильно упростить алгоритмы по работе с LOH.
+
+Для каждого эфемерного сегмента мы ищем участок памяти в списке свободных, пытаемся увеличить, если не получилось найти. Если не получилось увеличить - пытаемся подкоммитить зарезервированную часть. Следующим шагом запускаем GC, возможно несколько раз. И если совсем ничего не выходит - дергаем OutOfMemoryException. В данном подходе нет слова "сжатие", мы идем по простому пути.
+
+Многопоточность. Есть такое понятие как баланс хипов. Например, GC запущен в серверном режиме. Это значит, что у него по несколько SOH и LOH и эти пары назначены к конкретному процессорному ядру. У каждого ядра есть своя пара хипов и свои треды.
+
+Представим, что на каком-то ядре баланс нарушился. Оно начало аллоцировать слишком много памяти и перестало помещаться в текущие сегменты. Рассмотрим, что может сделать система управления памятью в данном случае.
+
+Ей может показаться, что если данное ядро занимается очень плотной аллокацией, то можно взять и перекинуть контекст на соседнее ядро и аллоцировать там. Одно ядро стоит, а другое выжато по максимуму. Это поможет избежать таких дорогих операций, как коммитмент памяти. Но при изменении контекста, вместе с этим участком, в другое ядро переносится и поток.
+
+Это делается не только потому, что закончилась память. Есть и другая причина. У каждого процессора есть кеш ядра. Когда приложение отрабатывает на каком-то участке памяти, весь он находится в этом кеше. Когда вы начинаете работать с другим участком и промахиваетесь мимо кеша, приходится выкачивать другой участок памяти под кеш. Если переключаться туда-сюда, будет самая худшая ситуация. Все будет работать очень-очень долго.
+
+Когда текущий allocation context ядра заканчивается, а на соседнем ядре ничего в это время не происходит и проще туда перекинуть контекст, чем аллоцировать новый кусок памяти, то он вместе с этим контекстом вынужден туда перекинуть текущий поток. Теперь он будет работать на другом диапазоне памяти. Ему нужно, чтобы другой диапазон памяти находился в его кеше. Поэтому вместе с контекстом перемещается и текущий поток.
+
+ Какие выводы можно тут сделать. Во-первых, надо помнить как выделяется память. Во-вторых, чтобы оптимизировать работу GC, надо понимать, что вам понадобиться сколько-то объектов определенного типа. И после вызова new, заполнили память, алгоритм у вас отработал. А потом у вас создается еще один объект такого типа, например уже в цикле. И это плохо. Алгоритм отрабатывает некоторое время, в параллельных потоках в это время может быть произведена и другая аллокация. И в том же алгоритме вы можете выделять другую память. Соответственно, вы фрагментируете память по типу. Во время GC вам нужны объекты первого типа как объекты нулевого поколения, которые быстро исчезнут. Но в алгоритме вы аллоцируете вечные объекты. GC вынужден проводить фазу сжатия кучи, т.к. эти самые объекты, которые быстро ушли на покой, образовали маленькие участки свободной памяти, куда больше ничего не разместить. В противном случае GC мог бы обойтись обычным sweep: если бы участок был большой, он бы попал в список свободных участков.
+
+Как лучше сделать. Если есть алгоритм на десять тысяч итераций, и там нужны объекты типа A, лучше выделить их сразу группой, а потом использовать. И когда GC отработает, у него получится огромный участок, где эти объекты были выделены. И он весь его отдаст в список свободных участков. Если же в вашем алгоритме вам каждый раз нужен всего лишь один экземпляр объекта типа A, то переиспользуйте его, сделайте метод init вместо конструктора. Получится, что вы вообще не фрагментируете память, а работаете на одном объекте.
+
+ Если вам нужно одновременно сто объектов делайте пул объектов, и доставайте их оттуда, чтобы не фрагментировать память короткоживущими объектами внутри алгоритма, чтобы не делать длительное сжатие кучи.
+
+ Первая книга вышла недавно. Ее написал Конрад Кокоса. Книга о менеджменте памяти объемом в тысячу страниц. Когда я ее в первый раз получил, я ожидал увидеть нечто другое. От размера тома у меня был шок. На самом деле автор молодец. Он написал ее для любого уровня подготовки. Это значит, можно вырезать вообще все, что лишнее. Например, уровень управления памятью процессором, Windows, список инструментов, которыми удобно пользоваться для диагностики - если вы хотите почитать просто про менеджмент памяти, то можно взять вторую половину книги. Первая половина книги - это просто подготовка ко второй. А вторая половина - как раз менеджмент памяти. Взять этот менеджмент памяти. Там очень легким языком все написано, постоянно делаются отсылки к предыдущим главам.
+
+ Если книгу ужать, получится страниц 300. А это уже подъемный объем, то, что возможно прочитать.
+
+ Вторая книга - Under the Hood of .NET Memory Management - для меня уже классика. Она не переведена на русский и распространяется только через сайт Red Gate, который делает разные инструменты для рефакторинга и работы с базами данных. В том числе у них есть тулза для анализа памяти, они были вынуждены исследовать как эта память работает. Там в очень коротком стиле, достаточном для того, чтобы приятно удивить людей на собеседовании в компанию, это все описано. В районе 200 страниц занимает. И моя книга.
+
+Какие у вас вопросы возникли?
+
+- .... (вопрос не слышен на записи)
+
+- Получается надо. По картинкам частично Наталья ответит на этот вопрос, как правильно выкачивать это, чтобы лишнее не расходовать и чтобы все было удобно. Использовать надо в любом случае одни и те же массивы, использовать пулы, которые появились. Можно написать свои или новые, которые появились. Чтобы с этим грамотно работать можно использовать то, о чем расскажет Наталья.
+
+- ... (вопрос не слышен на записи)
+
+- Массив - это массив ссылок. Вы не можете сделать массив картинок. Массив ссылок на картинки.
+
+- ... (вопрос не слышен на записи)
+
+- Используйте это в качестве ключа GUI.
+
+- ... (вопрос не слышен на записи)
+
+- Вопрос о том, насколько логично перекидывать поток с ядра на ядро без учета использования памяти конкретным ядром. Какая получается классификация в данном случае.
+
+ У нас есть первое ядро. Оно что-то делает, за ним забито два потока. Когда на него перекидывается со второго ядра, то в рамках первого ядра все в порядке: оно как работало со своей памятью, так и работает. Это сценарий один. Сценарий номер два. Перекидываемый поток, со второго на первое. Когда мы это делаем, то мы его переносим не просто так, а потому, что он в данный момент активно аллоцирует память. Все это делается исходя из статистики. Из статистики, видимо, делается следующий вывод: приложение чаще работает с памятью, которую оно только что выделило.
+
+- ... (вопрос не слышен на записи)
+
+- На целевом ядре?
+
+• ... (вопрос не слышен на записи)
+• Ничего не ляжет, там все в порядке будет. Они что-то считают, память не выделяют, контекст им не нужен. У первого ядра как были пара хипов, так они там и останутся. Они находятся в процессорном кеше. И когда туда переезжает тред с другого ядра и начинает аллоцировать, кеш не затронут. И с работой тоже самое.
+
+• ... (вопрос не слышен на записи)
+
+- Возможно, там несколько все сложнее. Но на уровне, который нам доступен на данный момент, объяснение такое.
+
+- ...(вопрос не слышен на записи)
+
+• Где посмотреть, как живет Linux? Если вы хотите посмотреть, как все тоже самое происходит в Linux, то можно обратиться к репорзиторию CoreCLR, там в разделе документации есть botr - Book of the Runtime. Это веб-книга, где разработчики ядра пишут документацию, как это работает. Это первый путь. Второй путь - это исходники. Но я не советую туда смотреть, если не хотите ужаснуться. Третий путь - это зайти в Book of the Runtime, посмотреть, кто туда пишет текст и связаться.
+
+• ...(вопрос не слышен на записи)
+
+- Есть .NET стандарт, есть разные платформы. Как писать, чтобы все работало хорошо везде. Ответ такой. И .NET Core, и .NET framework работают на основе RED JIT 57:05. Это некая система джиттинга и GC. Кодовая база у них одна. Отличий мало. Например, в методе карточного стола есть отличия. В Windows проставление флагов как Bundle Table идет автоматически на основе триггера и со страницы памяти. В Linux это будет ручная простановка. Но вас это волновать не должно, это низкоуровневая подробность, которая ни на что не влияет. Основной слой, который влияет - унифицирован, а других особенностей я пока не встречал.
+
+- ...(вопрос не слышен на записи)
+
+- Да, этой литературы вполне достаточно.
+
+-... (вопрос не слышен на записи)
+
+- Тут есть баланс. На счет того, что есть кеш.
+
+ Кеш - это централизованное хранение. В том плане, что все ссылки на младшее поколение там будут хранится рядом - это ключевое. В этом случае есть стопроцентная вероятность, что одно машинное слово в 32 бита перекроет весь кеш. Там не важно, сколько ссылок, будет выставлена просто одна единица.
+
+- ... (вопрос не слышен на записи)
+
+- Если они рядом хранятся, то это хорошо. Плохая ситуация, если у вас второе поколение большое и вы в разброс по всему поколению в каком-то алгоритме постоянно проставляете ссылки на младшее. Это редкая ситуация. Поэтому карточный стол и придуман. Приложение будет проставлять ссылки на младшее поколение из старшего в разброс по всей памяти. Если такое происходит - это очень плохо, потому что ему придется просматривать мегабайты объектов, в каких из них поля уходят на младшее поколение.
+
+Все места, которые ссылаются на более младшее поколение, по возможности должны быть сгруппированы.
+
+- ... (вопрос не слышен на записи)
+
+- Группируя места ссылок на младшее поколение, вы упрощаете работу GC. Если наоброрт, то он, встречая карту с единицей, вынужден просмотреть огромный диапазон памяти, которая вся не ссылается в младшее поколение кроме одного поля. Если разрежено ссылаться на младшее поколение - это худшая ситуация. Если все это разместить рядом, то он сходит по карточному столу один раз. Кеш - это хорошо.
+
+- ... (вопрос не слышен на записи)
+
+- Если много кешей, то будет, скорее всего, несколько карточных столов. Когда у вас много не кешировано, а просто разреженная память и есть единичные ссылки, которые постоянно выставляются - это плохо. - ... (вопрос не слышен на записи)
+
+- Async/await вообще не предполагает знаний о том, на каких потоках это выполняется. Это синтаксический сахар. На счет синтаксического сахара в дальнейших докладах сделан вывод, что в нагруженных участках приложения, его использовать нельзя. Потому что синтаксический сахар порождает часто аллоцирование объектов там, где вы не увидите этого по тексту программы, это будет сопровождаться трафиком памяти, трафиком GC. Поэтому если у вас есть участки памяти, которые работают нагружено, там от синтаксического сахара придется отказаться.
+
+- ... (вопрос не слышен на записи)
+
+- А это является результатом синтаксического сахара. Потому что async/await, это все таки помощь нам писать код удобно. Надо чтобы нагруженный код, когда что-то выделяет, делал это в одном конкретном понятном месте, без использования дополнительных конструкций. Тогда вы контролируете ситуацию. Когда вы делаете async/await, forEach, лямбды, вы начинаете использовать дополнительные аллокации. Так создается дополнительный трафик. Это удобно, но в правильных пропорциях.
diff --git a/book/ru/Memory/03-04-MemoryManagement-GC-Intro.md b/book/ru/Memory/03-04-MemoryManagement-GC-Intro.md
new file mode 100644
index 0000000..c8d6a6b
--- /dev/null
+++ b/book/ru/Memory/03-04-MemoryManagement-GC-Intro.md
@@ -0,0 +1,77 @@
+## Введение в сборку мусора
+
+> [In Progress] адаптация курса
+
+Следующий вопрос, который мы обсудим - это введение в сборку мусора.
+
+GC работает в двух основных режимах: Workstation и Server. Связано это с особенностями поведения приложения: либо оно серверное, либо десктопное.
+
+У десктопного приложения есть UI, со своими стандартами отклика. Стандарты отклика - около ста миллисекунд. Если после нажатия на кнопку и в течение ста миллисекунд ничего не происходит визуально, пользователь начинает думать, что программа тормозит. Исходя из этих оценок, приложение должно отрабатывать равномерно быстро без больших задержек в случайных местах. Поэтому GC работает чаще, чтобы иметь маленькую кучу нулевого поколения, на которой он может за известное время отработать.
+
+Если GC вдруг внезапно запустился во время отрисовки кнопки, он гарантированно уйдет раньше, чем эти сто миллисекунд, не создав у пользователя ощущения, что что-то тормозит.Если речь идет о серверном режиме работы, то там несколько другие условия работы. На сервере обычно много памяти, а менеджер памяти имеет возможность развернуться достаточно широко. Как мы знаем, на каждое ядро создает по паре хипов, в процессе работы GC отрабатывает реже, а по возможности - не отрабатывает в принципе. И, тем самым, GC увеличивает скорость работы сервера в промежутках между сборкой мусора.
+
+Прерывается он редко. Если говорить про ASP.NET (а там у нас запрос-ответ), то можно пока на одной ноде идет GC, на второй что-то делать. Каждый из этих режимов сейчас доступен в двух подрежимах: Concurrent и Non-Concurrent. Concurrent - это мифический режим. То есть когда у нас GC происходит, основной режим - когда все потоки встают и после завершения GC продолжают работу. Проще всего менять память, когда она не меняется кем-то другим. Мы может сжать кучу, поменять все указатели объектов на новые значения. Все это произойдет и при этом основное приложение не работает, а значит - нет рисков. Это Non-Concurrent. А Concurrent - это идеальный режим, когда все происходи в параллели. Но такого не бывает.
+
+Есть статья на Хабре. Еще я смотрю видеоролики от Джуга, где Шепелев (или кто-то другой, увлеченный GC) про них рассказывает очень часто. В Java очень много видов GC. Алибаба недавно свой тулз сделал. Это там, где бывший нищим китаец, стал самым богатым человеком в мире. И у них есть свой GC. Они тоже тоже писали, что у них есть один единственный GC, который по-честному полностью Concurrent. И в ходе расскза получается, что все же полностью Concurrent не бывает.
+
+Наш - не исключение. Я не копал и подробно рассказать не могу про Concurrent режим, но знаю, что паузы там есть. Просто они очень короткие. Такого, что он встал на паузу и до упора там нет.
+
+Как происходит сборка мусора? Если срабатывает триггер нулевого поколения, будет собрано только оно. Это правило. Нулевое поколение состоит из новых объектов, соответственно диапазон сборки мусора короткий, алгоритмы отработают крайне быстро.
+
+Если срабатывает триггер первого поколения, будет собрано нулевое и первое, потому что подразумевается, что первое поколение будет отрабатывать дольше. Если учесть, что нулевое отрабатывает чаще, то, скорее всего, после первого поколения, нулевое тоже будет в ближайшем будущем собрано, то можно его собрать сразу же. Поскольку первое поколение более тяжеловесное, если к нем присоединить сборку нулевого, это не сильно скажется на производительности.
+
+Если мы дошли до второго поколения, которое может достигать феерических размеров, то тем более никакой погоды не сделает, если собрать первое и нулевое поколение вместе со вторым.
+
+Каков порядок?
+
+У нас есть такой график 08:43. То, что темно - это есть какие-то объекты. Светлые участки без объектов. Есть некий allocation context. Пусть, у нас будет один поток.
+
+Что тут делает GC.
+
+Первым делом он маркирует объекты для целевых поколений. Но здесь нет ссылки и пора мусор почистить. Дальше идет следующий этап: GC выбирает между техниками сборки мусора. Техник может быть две: sweep collection без сжатием, и техника с сжатием.
+
+В sweep collection все недостижимые объекты нулевого поколения трактуются как свободное место. Все достижимые ообъекты становятся поколением один. То есть он у нас двигает границы. Мы промаркировали, освободили место, поколение передвинули. Compact collection все достижимые объекты поколения ноль уплотняются, занимая места недостижимых объектов. На слайде видно 10:38, что в том месте находится то, что было, а где есть свободные места. Уплотняем и сдвигаем границу поколений. Мы не копируем объекты из поколения в поколение - это дорого. Мы сдвигаем границу, указатель.
+
+Маркировке были подвергнуты только объекты нулевого поколения, другие объекты мы не маркировали, это дорого. Поколение ноль стало пустым. Это поведение по умолчанию. После того, как граница поколений сдвинулась, достижимые объекты переместились в поколение номер один. У объектов нет признаков, в каком поколении они находятся. При смене поколения они никуда не копируются, просто меняется адрес диапазона. Когда вы проверяете GC.GetGeneration(), получаете номер поколение. Этот метод смотрит, в какой диапазон адресов попадает адрес объекта.
+
+Поколение один выросло, как в случае sweep, так и в случае compact. Поколение два и LOH оказались не тронутыми.
+
+GC выбирает между техниками сбора мусора. Но на самом деле может так случится, что может быть произведено выделение памяти в более старшем поколении. Если там оказался какой-то свободный участок.У нас есть три поколения, есть LOH. Срабатывает первое поколение, GC работает на первом и нулевом. Дальше GC решает, что можно переместить объект один из gen_1 в gen_2, дальше уплотнить кучу, то gen_0 получится очень большим. Если вдруг какой-то объект почему-то при сборке мусора нулевого поколения переместился во второе, то удивляться не стоит, такое может случится очень легко.Еще один вариант: закончился сегмент. Если обычной сборки мусора не достаточно для размещения allocation context, то текущий сегмент начинает резервировать все заново. Этот сегмент памяти, где размещался хип, помечается как gen_2 only. Это значит, что в текущем сегменте будет жить только второе поколение. А дальше он выделяет новый сегмент виртуальной памяти, резервирует новый кусок.
+
+Когда приложение стартовало, он зарезервировал большой кусок памяти, но ее там физически нет. Начал коммитить. Пока он заполняет память, пытается сжать, коммитить дальше, память растет. И вот он докоммитил до конца этого большого зарезервированного куска. Резерв закончился. Сейчас ему нужен новый зарезервированный кусок. Он аллоцирует новый. И когда их больше одного становится, этот новый кусок помечается как эфемерный. Там будет размещаться только нулевое и первое поколение. Для всех остальных будет поколение два. Когда будет три сегмента, то первые два сегмента будут использоваться под поколение два, а третий - эфемерный под нулевое и первое. Когда их будет 50, то первые 49 сегментов будут принадлежать второму поколению, а последний под нулевое и первое. 16:54
+
+В кончившемся сегменте могли быть объекты нулевого поколения, а оно становится второго, то он должен скопировать объекты из нулевого поколения старого сегмента в первое поколение нового. Потому что все старые сегменты gen_2 only. А для нулевого поколения создаются allocation context для тредов.
+
+Посмотрим на слайд 17:42, там видно, как все делается. Потом выделяется новый сегмент, все уплотнили. Дальше некуда, выделяется новый сегмент. В нем располагается gen_1 и gen_0, а в старом только gen_2. Если их было много, но при этом один из gen_2 сегментов старых вдруг кардинально опустел, и там освободилось много места, то GC может принять следующее решение: поскольку у нас есть кусок свободной памяти, а аллоцировать новый сегмент намного дороже, чем просто скопировать, то он просто берет и использует один из старых сегментов как эфемерный, то есть переиспользует его.
+
+Чтобы это хорошо заработало, старые объекты gen_2 из этого сегмента должны быть убраны. Там не должно быть второго поколения.
+
+У нас было три поколения gen_2 only, дальше он решил, что третий gen_2 only опустел и можно там разместить эфемерный сегмент вместо того, чтобы аллоцировать там новый кусок памяти от Windows. Он берет и перегоняет gen_0 туда, при этом нулевое поколение становится gen_1. Gen_2 перемещается из третьего сегмента в четвертый, который становиться gen_2 only. И остаток третьего сегмента становится gen_0. Идет такая пересортировка, так будет работать лучше. В старом эфемерном сегменте заканчивается место, а в третьем место есть, и они меняются ролями.
+
+Как происходит сборка мусора?
+
+Во-первых, что-то запускает сборку мусора. Дальше все управляемые потоки встают на паузу, если у нас Non-Concurren GC. Поток, который вызывал GC запускает процедуру сборки мусора, то есть GC работает в потоке, который его инициировал. Дальше выбирается поколение, которое будет очищаться. Происходит фаза маркировки для выбранных поколений. То есть мы пробегаемся по всем объектам и маркируем их. Дополнительно идем в карточный стол, ищем там ненулевые значения, трактуем их как корни. Маркируем все, что исходит от них. Дальше фаза планирования. На основе данных из фазы маркировки пытаемся понять, какой из двух алгоритмов сборки мусора будем применять - sweep или compact. Sweep из-за скорости в приоритете, но если статистика показывает, что лучше compact, то будет выбран он. В дальнейшем мы поймем, что на самом деле фаза планирования является основной. Для того, чтобы понять, какой алгоритм применить, надо фактически выполнить оба алгоритма одновременно. Последняя операция - сбор мусора - фактически это коммитмент тех вычислений, которые были сделаны на предыдущей фазе. Далее идет последний шаг - восстановить работу всех потоков.Визуально это можно увидеть на слайде 23:17.
+
+Что вызывает GC?
+
+Причин может быть много. Попытка аллоцировать в SOH, а там место закончился. inducted - когда мы руками запросили.
+
+lowmemory - закончилась свободная память внутри процесса.
+
+empty - затрудняюсь сказать что.
+
+alloc_loh - соответственно, закончилась память в LOH.
+
+oos_loh - это по короткому пути.
+
+И так далее, на них не имеет смысла долго останавливаться. Что может быть. Исчерпали место в SOH. Это стадия, когда у нас allocation context закончился и GC надо его расширить. Когда контекст надо передвинуть в другое место. Прежде чем это делать, мы пытаемся скомпактить. Прежде чем мы расширяем сегмент. Потому что сегмент расширять дорого, мы попытаемся собрать мусор. Не получилось - пришлось расширять сегмент. И, наконец, прежде чем мы пытаемя новый сегмент использовать под эфемерный. Потому что новый сегмент - это еще дороже. И нужно попытаться в рамках текущего найти какие-то свободные участки и разместить новые объекты там.
+
+Тоже самое про LOH.
+
+Аллокатор исчерпал место после медленного алгоритма выделения памяти, после реорганизации сегментов и после GC. Ручной вызов GC тоже бывает триггером. Их бывает три режима. Первый GC.Collect() - вызов полного GC, блокирующего при этом при вынужденного compacting на LOH. Вызов GC.Collect(int gen) - на нужном поколении. Это тоже самое, но с выбором поколения. И последний режим - GC.Collect(int gen, GCCollectionMode mode) - это вызов на необходимом боколении, блокирующий, без вынужденного compacting, с необходимым режимом. Про ручной вызов GC хочется сказать, что на самом деле если вы доходите до этой стадии, это значит, что скорее всего вы не очень понимаете, что происходит в приложении. И как hotfix, чтобы все стало хорошо, вызывается GC.Collect().
+
+То, что я слышу чаще всего, про вызов GC.Collect(), это приложения на технологии Xamarin. Там есть свои особенности. На Android там есть два GC: от .NET и от Java. Проблема в том, что все приходит от Java, имеет реализацию интерфейса disposable. Вкупе с непониманием, кто должен дергать dispose, там очень сильно текут ресурсы. Если не привыкнуть писать правильно. Это время от времени приводит к мысли вызвать GC.Collect().Если хочется вызвать GC.Collect() в обычном приложении, то, скорее всего, это значит, что нам не очень понятно как это все работает внутри. Почему алгоритмы приводят в необходимости вызова этого метода.
+
+Чтобы не вызывать, необходимо посмотреть метрики от GC, построить графики. И посмотреть наложение графиков расхода памяти по разным поколениям, на график срабатывания GC, на другие графики и понять, что приводит к проседанию памяти. Возможно, вы увидите, что там что-то течет в этих графиках. Если такие места есть, то нужно смотреть внимательно в эти места и изучать там. В общем случае GC.Collect() вызывать не надо. Это, во-первых, означает, что GC потеряет свои статистики. После ручного вызова метода, GC может в том потоке, где вы только что выделяли объекты выдать вам allocation context не на 8Кб, а маленький, который замедлит дальнейшее выделение памяти. Это то, что лежит на поверхности.
+
+На данном этапе мы рассмотрели в общих чертах все шаги в каком порядке это работает. Далее будем рассматривать конкретные шаги, подробности. Но эти подробности хороши, когда видишь картину целиком. Текущий доклад был про общую картину, как GC отрабатывает, какие режимы есть. Последующие доклады - это будет изучение вглубь. Сейчас будет маркировка, планирование, сборка мусора, то, что касается GC. После чего последним докладом по GC будет общий обзорный доклад с выводами. На основе того, что мы услышали, какие правила можно вывести, как наши алгоритмы должны быть построены, чтобы не наталкиваться на плохую работу GC.
\ No newline at end of file
diff --git a/book/ru/Memory/03-06-MemoryManagement-GC-Mark-Phase.md b/book/ru/Memory/03-06-MemoryManagement-GC-Mark-Phase.md
new file mode 100644
index 0000000..6a17e7c
--- /dev/null
+++ b/book/ru/Memory/03-06-MemoryManagement-GC-Mark-Phase.md
@@ -0,0 +1,61 @@
+## Фаза маркировки достижимых объектов
+
+> [In Progress] адаптация курса
+
+Мы поговорили про выделение памяти, про общую картину, теперь будем обсуждать все подробно. Фаза маркировки GC.
+
+На этой стадии GC понимает какие поколения будут собраны. У нас есть знание, что GC является трассирующим. Как и весь алгоритм трассировки в 3D, чтобы простроить сцену, мы луч опускаем, с чем пересекся - то и объект, а куда отразился - отражение. Тоже самое и в GC. Мы к объекту идем по исходящим ссылкам и с помощью трассировки понимаем, какие объекты являются достижимыми, а какие - мусором.
+
+Он стартует из различных корней и относительно них обходит весь граф объекта. Все, до чего он дошел - хорошо. Остальное - мертвые зоны. Если рассматривать простой сценарий, когда GC у нас не Non-Concurrent, то у нас встают сначала managed потоки, когда они встали можно делать с памятью все, что угодно. Например, сделать фазу маркировки. На этой фазе для любого адреса из группы корней в заголовке объекта устанавливается флаг pin, если объект у нас pinned. Pinning может происходить либо из таблицы хендлов у application domain, либо из ключевого слова fixed. И когда GC посреди fixed срабатывает, он понимает, что эту переменную надо запинить.
+
+Обладая информацией о том, что у объекта есть исходящие ссылки на managed поля (это известно из таблицы виртуальных методов), он обходит все исходящие ссылки. Обход графов осуществляется путем обхода в глубину. Он сохраняет свое состояние во внутреннем стояке. При этом, при посещении каждого объекта, мы смотрим если он уже посещен, то пропускаем, если не стоит - устанавливаем флаг, как посещенному на указателе VMT. И, поскольку у нас все адреса в таблице виртуальных методов выровнены по процессорному слову (они делятся в 32х разрядной системе на 4 без остатка, а в 64х разрядной - на 8, это сделано для того, чтобы процессор быстрее работал на этих адресах), то получается, что младшие два бита адреса не используются и равны нулю. Значит, туда можно что-то записать, ничего при этом не испортив. Главное, потом не забыть маркировку снять.
+
+Все исходящие указатели из объекта с полей добавляются в стек адресов на обход.
+
+В стек сначала добавляем все корни, потом начинаем цикл обхода. Забираем с вершины стека адрес, идем в таблицу виртуальных методов, там обходим информацию о исходящих ссылках на управляемые объекты. Каждую исходящую ссылку заносим обратно в стек, а объект маркируем как пройденный. Следующая итерация. Когда стек пустеет - обход завершился. Все установленные флаги стираются во время фазы планирования.
+
+Корни.
+
+Во-первых, корнями являются локальные переменные метода. В данном примере 06:36 есть локальная переменная path и она является, по сути, корнем. Мы можем в локальную переменную сохранить адрес объекта, но никуда больше. Чтобы доказать, что объект достижим, нам необходимо обойти весь стек потока и собрать там исходящие локальные переменные.
+
+Это не должно быть собрано GC и может быть долго.
+
+Локальные переменные могут храниться в двух местах.
+
+Во-первых, в стеке потока - это структура, на основе которой происходит вызов методов. Есть поток, там крутятся методы, они друг друга вызывают. Их локальные переменные не пересекаются с локальными переменными таких же методов, которые в это же время вызываются, но в другом потоке. У каждого потока есть свой стек. Это массив, где при вызове метода выделяется некий кадр - кусок, в котором есть место под локальные переменные и некоторые параметры, с которыми метод вызывается. Когда вызывается следующий метод, он добавляется в конец. А когда метод завершает работу, они начинают с этого стека уходить. На этом стеке как раз хранятся локальные переменные метода и некоторые параметры, начиная с третьего, на сколько я помню, а первые два передаются через регистр.
+
+Поскольку некоторые методы могут долго работать, то GC должен понимать, что эти места до определенных моментов собирать не надо. Дальше нужно смотреть по scope переменных, код на слайде 09:45. Первый - это переменная class1. Она доступна в течение жизни всего метода. Она сначала аллоцировалась и с точки зрения языка C# она живет от первой фигурной скобки до последней. Если говорить о переменной class2 с точки зрения языка C# она живет внутри блока if, от первой фигурной скобки до последней. Но есть два варианта окончания scope. Упрощенный, когда scope заканчивается с неким лексическим блоком, а есть вариант, когда scope заканчивается с последним местом его использования.
+
+Раньше я считал магией, когда говорили, что GC может собрать объект прямо посреди метода, если вы перестали его использовать и других ссылок на него нет. Как GC понимает, что код перестал использовать переменную. Оказывается, все просто. Сбоку от описания метода лежит еще дополнительное описание scope переменных. Он примерно выглядит как на слайде 11:11. Если смотреть построчно, в первой и второе строках нет переменных. В третьей появилась переменная class1 в scope. Потом появился class2, и вот они постепенно начали исчезать. Это для частично прерываемого scope. Для полностью прерываемого scope у нас ситуация другая - слайд 11:37 . И на каком-то этапе они обе перестали использоваться. Седьмая строка - последняя, где используются обе переменные. И дальше идет операция return и после этого можно и не использовать. На самом деле здесь произойдет GC, то оба инстанса будут собраны. Но он сохраняет не просто значение переменной, но и место, где оно хранится, потому что архитектура Intel не подразумевает, что вы работаете напрямую с памятью. Надо сначала перекладывать адрес в регистр, а потом на основе регистра уже производить какие-то действия. В таблице scope хранятся именно регистры, хранящие данные. И когда доходит до варианта None, значит можно все собирать с конкретно этих регистров GC. Для Fully Interruptible на слайде 13:06 не используется class1. У нас тут картина меняется. Получается, что в третьей и четвертой строчке у нас используется регистр rax под хранение class1, потом в пятой строчке его использование пропадает. И если здесь сработает GC, то инстанс class1 может быть свободно собран.
+
+Дальше джиттер понимает, что rax больше не используется и можно его переиспользовать еще раз уже под другую переменную. Делает это ровно в трех строках, после чего точно также дальше не используется и собирается.
+
+Эта таблица использования 13:55 называется Eager root collection. Помимо того, что локальные переменные определяются стеком потока, они дополнительно определяются этой таблицей, которая, на самом деле добавляет нам много проблем.
+
+Какие именно проблемы? Вот такой код 14:21, очень простой. У нас есть таймер, который срабатывает каждые сто миллисекунд, выводя на экран текущие дату и время. Он запускается, работает и дальше у нас печатается "Hello", GC.Collect() и ReadKey(). Если смотреть с точки зрения C#, то таймер используется во всем Main. Поведение в данном случае должно быть такое: напечатали "Hello", дальше, пока пользователь не нажал кнопку, мы начинаем тикать на экран текущее время. Во втором варианте, когда у нас таймер используется только в одной строчке, вызывается GC.Collect(), GC должен понять, что после первой строчки таймер уже не нужен. И мы максимум одну строчку успеем увидеть, прежде, чем GC этот таймер соберет. А может быть и вообще не увидим. Суть в том, что поведение будет разным. Это было поведение debug режима и релиза.
+
+То есть, когда мы в релизе, думаем что все отладили и все прекрасно, запускаем на сервер, а там такое поведение и сложно понять почему оно. Получается, что поведение на релизе и на debug режиме отличается. Об этом надо помнить. И это легко проверить.
+
+Еще один вариант такого поведения на слайде 17:12. У нас Main. Он создает экземпляр класса SomeClass, вызывает DoSomething и ждет пользователя. Дальше SomeClass, у него есть DoSomething, посреди которого срабатывает GC, делает WriteLine. И у него есть финализатор, в котором вызывается строчка финалайзера. Данный метод может быть заинлайнен. Метод Main короткий, ничего не делает и его можно оптимизировать и передвинуть наверх. При этом исчезнет scope переменной message. Scope переменной sc продлится либо до второй строчки, где DoSomething, либо до конца метода Main. Разница в том, что если DoSomething будет заинлайнен, то у него пропадет ссылка на текущий объект this. Значит, пропадет и scope его использования. А значит GC, который вызвался посреди метода DoSomething может вызвать финализатор этого класса до того, как метод этого класса закончит работу. Такое тоже возможно.
+
+Первое - у нас есть сам класс. У него метод DoSomething, у него есть GC.Collect, есть Console.WriteLine и отсутсвует использование указателя this, потому что все это статическое, есть финализатор. И если посреди работы метода DoSomething, или даже с использованием this, но GC сработал после последней точки его применения, тоже может быть ситуация, что финализатор вызовется до того, как этот метод завершит работу. Указатель на объект уже никому не нужен, а значит GC может совершенно спокойно экземпляр этого класса собрать. Выглядит страшно, но призываю не удивляться, если такое случиться.
+
+Scope переменных.
+
+Как расширить scope переменных, чтобы таймер был в обоих случаях. Простейший способ расширить - это вызвать GC.KeepAlive(). На самом деле это пустой метод, смысл в том, чтобы продлить использование переменной, ничего не делая. Сслыка на объект куда-то уходит и джиттер автоматически расширяет scope переменной до конца метода. Переменная ждет.
+
+Еще одни корни - это pinned locals. Ключевое слово fixed. Есть два варианта пиннинга. Пиннинг - вещь очень плохая. Мы об этом поговорим на фазе планирования. Если есть возможность обходить пиннинг, лучше это делать. Если такой возможности нет, то нужно воспользоваться ключевым словом fixed, которое реального пиннинга делать не будет. Если дизассемблировать этот код в msi, то мы заметим, что эти переменные, которые обозначены 22:24 byte* array = list, вот этот list пометится как pinned. То есть мы пинуем list, его адрес помещаем в array. Но list пинуется, но только тогда, когда внутри этого fixed 22:49 срабатывает GC, если он не срабатывает между этими двумя фигурными скобками, то реального пиннинга не будет. Будет только флаг для GC, что этот массив нужно запинить, если он вдруг начнет эту область проходить. Это отличная оптимизация. Получается, что в Eager Roots collection дополнительно ставится тоже флаг, то, что переменная является pinned. Но не только у него, а у регистров тоже и у всего, где по реальному коду память будет содержать исходящие ссылки, дополнительно этот флаг так ж ставиться. Если GC срабатывает внутри фигурных скобок, то он, обходя Eager Roots collection, понимает, какие области памяти необходимо запинить и делает это, на время своей работы. Как только GC отработал и отпустил потоки, он распинивает эти объекты.
+
+Еще одна группа корней - это Finalization Roots. Если мы сделали финализатор, и финализируемый объект ушел в очередь на финализацию, то он является естественным образом рутом для обхода объектов. Иначе получится так, что если эти объекты еще на кого-то ссылаются, то они будут собраны GC. Чтобы этого не допустить, финализацию мы тоже обходим.
+
+GC Internal Roots. Для обхода графа объектов используется карточный стол. Таблица маркировки старшего поколения, что есть ссылка на младшее. То есть корнями так же является карточный стол. Это причина, по которой лучше группировать места ссылок на более младшее поколение вместе. Поскольку весь карточный стол является корнем, то фаза маркировки будет проходить дольше, если на карточном столе будет много ненулевых значений. Когда мы его просматриваем в поисках ненулевых значений. Если нашли такое, то объект текущего поколения ссылается на более младшее. Мы трактуем ссылку как корень и тоже обходим.
+
+GC Handle Roots
+
+Последняя группа корней - это внутренние корни, в том числе таблицы handle. Внутреннике корни - это статики. Массивы статических полей всех классов - это ссылка изнутри app domain. Тут же есть ссылка на таблицу handle. Они делятся на бакеты. Бакеты группируются по принципу типа GC handdle, их может быть много. Есть GC handle с weak reference, есть com-вские, есть запинованные - всех не перечислить. Соответственно, все они могут ссылаться уже куда угодно - SOH, LOH. И эта таблица также является и таблицей корней.
+
+Если совсем далеко не уходить, то это все. Какие можно сделать выводы?
+
+Во-первых, не стоит делать много разных GC handle-ов. При их появлении создается дополнительная нагрузка на GC: структура handle-ов является боковой для графа объектов. Чтобы правильно все сделать GC вынужден лезть в таблицу handle, пробегать ее, перемаркировывать объекты, чтобы потом, на фазе планирования, правильно с этими объектами поступить.
+
+Есть проблема и с карточным столом. Статики в целом - их не надо много делать. Это места, которые вечно держат все. А насчет пиннинга мы поговорим далее, так как при стадии планирования пиннинг играет важную роль. Нет таких сценариев, когда мы просто так используем пиннинг по принуждению, но то, что использовать надо с ключевым словом fixed мы точно запомним. Ну и начнем лучше проходить собеседования.
\ No newline at end of file
diff --git a/book/ru/Memory/03-08-MemoryManagement-GC-Planning-Phase.md b/book/ru/Memory/03-08-MemoryManagement-GC-Planning-Phase.md
new file mode 100644
index 0000000..d6ba85f
--- /dev/null
+++ b/book/ru/Memory/03-08-MemoryManagement-GC-Planning-Phase.md
@@ -0,0 +1,97 @@
+## Фаза планирования
+
+> [In Progress] адаптация курса
+
+Следующая фаза, о которой хочется поговорить - это фаза планирования. Самая интересная фаза из всех. В ее ходе происходит виртуальный GC, без физического. А физический это просто коммитмент результатов вычислений на фазе планирования.
+
+После фазы маркировки у нас нет никакой ясности, какой тип GC должен сработать: sweep или collection (?compacting). Чтобы понять, будет ли результирующая фрагментация слишком высокой, надо, во-первых, сделать и sweep, и сжатие кучи, но в виртуальном режиме. Иначе не понять, какой процесс будет лучше. Если мы можем примерно прикинуть, то механизмы пиннинг а могут помешать этим прикидкам и испортить конечный результат GC. Во время фазы планирования собирается информация, которая одновременно подходит для обоих алгоритмов, чтобы все сделать максимально быстро и выбрать лучший путь. После того, как GC понимает, что compacting, например, - это лучшее, что можно сделать, он его и делает. Но алгоритма, на самом деле, два. Я не слышал, чтобы были разговоры, чтобы в SOH работали оба алгоритма. До сих пор в моем представлении все выглядело так: в SOH идет сборка мусора путем сжатия куча, а в LOH - классический C++ алгоритм со свободными участками.
+
+В реальности в SOH работают оба алгоритма, как и в LOH.
+
+SOH.
+
+Используя информацию о размерах объектов мы идем друг за другом и собираем группы свободных объектов - это plugs, и группы пропусков - gaps. То есть, когда мы пробегаем по хипу, то можем легко итерироваться. В самом начале хипа лежит первый объект. Мы переходим в таблицу виртуальных методов, проходим по указателю и по нему, среди прочих полей, лежит размер reference type. Я не очень понимаю, почему sizeof() работает только для value type, потому что чтобы сделать sizeof() reference type достаточно просто пройти по указателю и считать этот размер. Есть только одна разница - для массивов и для строк. Потому что для строк массивов надо этот sizeof посчитать, просто так не угадаешь. А для всех остальных обычных типов проблем никаких нет.
+
+И вот так, друг за другом, мы по всем объектам пробегаемся. Понимаем, какие из них промаркированы, поэтому легко можем понять, что является plug, занятым куском, что является gap, пропуском. Это вычисляется элементарно, так как на фазе маркировки мы там галочку поставили. Все друг за другом идущие пропуски мы маркируем как едины пропуск - группу. Все друг за другом идущие занятые участки мы маркируем как один занятый участок.
+
+Размер и положение каждого пропуска могут быть сохранены. Дл каждого заполненного блока может быть сохранено его положение и финальное смещение. Это значит, что для каждого gap мы считаем его размер: напрмиер первый 32 байта. Для plug считаем offset, это то значение, которое было бы при запуске сжатия. Если бы мы сжимали, то offset - это то место, куда текущий plug переместился бы, насколько байт назад. Gap - это gap, тут понятно. И уже на этой стадии, если мы говорим о двух алгоритмах - sweep и collection (compacting?), можно увидеть всю информацию, которая нам нужна. В случае sweep нам нужны размеры пропусков, так как мы будем формировать списки свободных участков. Если мы делаем сжатие кучи, нам нужны только офсеты. На сладйде 07:10 это видно: вот он офсет -32, потом 32+64-96, следующий офсет -96+64-160. Так легко это все считается, поэтому все работает быстро. Чтобы это все произошло используется некий виртуальный внутренний аллокатор. Он итерирует по объектам и наращивает эту виртуальную дельту, сразу же делая виртуальный compaction этой кучи.
+
+Полученную информацию GC хранит в последних байтах предшествующего заполненной части gap.
+
+Был какой-то объект, на него исчезла последняя ссылка, прошел GC, информация в gap нам больше не нужна, значит мы его можем спокойно портить - писать туда то, что хотим, в том числе и результаты вычислений. Если у нас есть некий plug, то информацию мы запишем в предыдущий gap.
+
+На слайде 08:33 есть некие plugs и gaps и некий диапазон по центру. Мы его увеличили. Получилось, что там три процессорных слова. Дальше несколько слов - это plug. И последние три слова свободные. И перед plug, перед занятым участком, этот gap в три байта мы опять увеличили. Первый байт - это gapsize, второй - relocation offset. Третий разделен на две части, первую мы не рассматриваем, а вторая - это left и right offset, которые мы рассмотрим чуть позже.
+
+Размеры, которые мы считали, кладутся в gapsize. А relocation offset укладываются вторым байтом. Вся эта информация, которая размещена над куском памяти, вписана на места в нашем графике.
+
+Что же такое left и right? С помощью этих двух полей образуется двоичное дерево поиска. У нас есть plugs и gaps. Если рассматривать их как единую сущность, как на слайде 10:30, то left/right offset - это offset до левой и правой ноды в двоичном дереве поиска нужных plugs.
+
+Двоичное дерево поиска строится для того, чтобы после фазы планирования, в ходе фазы сжатия нам все адреса поменять: куча сжимается, адреса объектов меняются. У всех объектов, которые ссылались на наши, надо заменить адреса в исходящих полях, локальных переменных, во всех рутах - везде, где были ссылки на группу, которая сжимается. Чтобы это сделать быстро и строится бинарное дерево поиска сбалансированное.
+
+Куча может быть огромная, поэтому используется структура, называемая Brick Table. Чтобы не искать по всему дереву, используется структура, схожая с карточным столом. Весь участок памяти приложения делится на большие регионы. Ячейка Brick Table - это ссылка на корень дерева двоичного поиска, которая описывает plugs и gaps внутри этого диапазона памяти.
+
+Если у нас на слайде 13:00 диапазон памяти от 1000 до 2000, вторая ячейка Brick Table отвечает именно за него. Она указывает на некий plug в центре, который является корнем двоичного дерева поиска других plugs.
+
+Если значение ноль, то в этом диапазоне нет никаких данных. Если значение положительное - то это адрес некоего корня. Если значение отрицательное, то этот диапазон является продолжением предыдущего диапазона. В процессе поиска нужного адреса мы делим на 0x1000, чтобы получить номер ячейки. Встаем на ячейку, дальше смотрим, что там за значение. И если ноль - то нет данных, если больше единицы - корень, меньше нуля - то там офсет на ту ячейку Brick Table, в которой записан корень.
+
+Pinned Objects.
+
+Чем плохи запиненые объекты?
+
+Посомтрим на слайд 14:34. Тут есть кусок памяти, на нем - plugs, бледные участки, а яркий кусок - это запиненый объект. Что произойдет во время GC? Plug сожмется и все. А запиненый кусок останется на месте. Что произойдет, если слева от запиненого куска есть участок памяти, на который есть ссылка?
+
+Давайте вспомним, что же делает фаза планирования? Она вычисляет plugs и gaps. И в зоне, которая предшествует plug в gap планирование записывает размер gap и смещение. Вот здесь возникает неприятная ситуация. У нас два занятых участка. Первый - обычный plug, а второй - запиненый. Мы не можем рассматривать их как единый кусок. Потому что первый будет двигаться, а второй нет. Поэтому выделяют plugs и pinned plugs.
+
+В данном случае можно выкрутиться. Поскольку у нас все потоки стоят, мы можем испортить данные, а потом вернуть все назад. Память перед запиненым участком интерпретируется как кусок, в который можно что-то записать. Но перед тем, как этот участок портить, данные нужно куда-то сохранить, чтобы вернуть все обратно.
+
+Этот участок в 4 байта сохраняется в pinned plug queue. Это очередь на возврат. Мы сохраняем участок, чтобы потом его вернуть обратно. Участок называется saved_pre_plug. Туда сохраняется информация о типе информации и к чему она относится. После этого все сжимается и этот участок возвращается на свое место.То есть появляется дополнительное действие.
+
+А что, если наоборот? Сначала идет запиненый объект, а потом обычный объект, на который есть ссылка. С запиненым объектом все хорошо, он может свои данные хранить в gap. Где же хранить данные обычному объекту? Перед ним запиненный.
+
+Почему пинят? Потому что буфер уходит в unmanaged память. Если GC, перед тем как начать работу, саспендит потоки - managed. То про обычные потоки он ничего не знает. Он не может их засапендить. А если буфер ушел в unmanaged память, то любой может с ним в параллели работать из unmanaged кода. Туда нельзя ничего записывать, в том числе и информаций plugs&gaps. В этом случае весь этот кусок помечается как pinned plug: и запиненый объект, и тот, который за ним следует. Они оба пинуются. После GC они в такой же последовательности и остаются.
+
+Еще одна ситуация. Сначала у нас идет запиненый, потом два незапиненых объекта. Перед третьим объектом есть второй обычный объект. То есть, когда pinned plug объединяется для GC, в дальнейшем все равно воспринимается раздельно. Последний plug знает, что предшествующий объект не запинен и туда можно писать информацию о gaps и plugs. Но такой кусок тоже уходит в очередь pinned plug queue как saved_post_plug объект.
+
+И совсем нездоровая ситуация возникает, когда у нас идет несколько объектов подряд и один из них запиненый. Это одна из частых ситуаций. В этом случае происходит совмещение обоих объектов. Для того, чтобы записать gaps и offsets, нам необходимо испортить и предыдущий объект, и следующий. А чтобы восстановить, мы должны эти куски уложить в очередь на восстановление.
+
+Demotion.
+
+Есть такие понятия, как Promotion и Demotion.
+
+Promotion: GC дернули на нулевом поколении и после этого номер поколения вырос. Demotion работает наоборот. Когда у нас было первое поколение, мы сделали GC, и по какой-то причине оно стало нулевым.
+
+После GC запиненый объект остался в поколении ноль - это суть Demotion.
+
+Другая ситуация: есть поколение один и поколение ноль. На слайде 21:52 после GC слева осталось мало места, а под gen_0 - походит. И они оба перескочили на gen_0. Этому удивляться не стоит. Если такое произошло, надо просто искать, кто запинил объект и освобождать его.
+
+Еще одна ситуация на слайде 22:56. Объект может свалится во второе поколение, если слева места мало и там нет смысла размещать что-то еще. С запинеными объектами может происходить все, что угодно.
+
+LOH.
+
+Фаза планирования в LOH имеет смысл только для compacting, чтобы понять дальнейшие действия. В обычных сценариях сборки мусора у нас только sweep, а compacting нет. И фаза планирования в таких случаях отсутствует. Sweep не использует plug&gap, планирование ему не нужно. Он просто идет и составляет списки свободных участков. В этом плане sweep гораздо приятнее compacting.
+
+В обычном режиме LOH не осуществляет compacting, только по принуждению. Но в этом случае тоже без планирования. Потому что вы попросили конкретную и у него вся информация будет построена на месте, со всеми офсетами. Будут построены офсеты, plugs&gaps только для того, чтобы понять, может ли он обойтись обычным sweep. И если это возможно, то он его и сделает в хипе маленьких объектов. А в куче больших вычислять ничего не надо: что его попросят, то он и сделает. Поэтому фазы планирования там нет.
+
+Так как LOH сделан исключительно для хранения больших данных, это позволяет упростить некоторые вопросы. Нет необходимости группировать объекты в plugs. В SOH это происходит исключительно ради производительности. Если у нас куча объектов по 32 байта, то нет смысла их рассматривать отдельно. С точки зрения GC их надо рассматривать как единое целое. Поэтому каждый объект в LOH - это plug.
+
+Плотность объектов в нем намного ниже, значит эффективнее трансляция адресов - эффективнее работает дерево. Легче перенести большой объект, чем plug, группу больших объектов. Чтобы обеспечить хранение plugs&gaps информации между объектами в LOH резервируется дополнительное место. Мы не можем себе позволить резервировать дополнительное место под информацию по размерам gap и relocation offset в куче маленьких объектов. Это будет слишком жирно. Поскольку у нас адреса всех объектов должны быть выровнены по размеру процессорного слова, по 8 или по 4 байтам, а информация по plugs и relocation offset занимает всего 3 байта, то мы не можем этими тремя байтами манипулировать. Мы либо 4 будем использовать, либо 8. Поэтому в LOH используем оптимизацию, храним эту информацию в gap. А в LOH мы можем себе позволить дополнительную информацию слева положить, не перетирая при пининге предыдущие объекты.
+
+Когда строится compacting в LOH, мы просто дополнительно резервируем между всеми объектами немного места для того, чтобы положить туда офсет. Когда мы запрашиваем compacting вручную, мы просчитываем эти офсеты и все сжимается.
+
+Почему в SOH вместо sweep может быть выбран compacting.
+
+Например, это последний GC перед OutOfMEmoryException. Последняя надежда, что получится найти немного памяти.
+
+Compacting может быть попрошен программистом намерено. Или было выбрано все место в эфемерном сегменте. Если у нас в эфемерном сегменте нулевого и первого поколения выбрано вес место и все закоммичено, то необходимо аллоцировать новый сегмент. Это дорого по времени. Поэтому первое, что пытается GC сделать, это сжать.
+
+Высокая степень фрагментации поколения так же может спровоцировать compacing. Это один из основных случаев. Если фрагментация слишком высокая и нет никакой возможности разместить allocation context в каких-то свободных промежутках памяти, то запускается сжатие.
+
+Еще один вариант - процесс занял слишком много памяти. Это, в целом, понятно.
+
+Рассмотрим фрагментацию.
+
+Это понятие достаточно виртуальное. Что значит "слишком фрагментировано"? Ответ на вопрос простой. Если взять некий total fragmentation, который собирается во время фазы планирования (это суммарное количество памяти, которое занимают gaps) и поделить на размер поколения, то мы получим отношение. При fragmentation size в 40 тысяч байт на нулевом поколении fragmentation ratio это 50%. Это триггер. Если фрагментация вырастает, то мы делаем сжатие кучи.
+
+Мы получили новую информацию, что если у нас есть фрагментация более 50% при определенном размере хипа, то запускается не очень приятная процедура сжатия, которого мы хотим избегать.
+
+Избегать процесса сжатия относительно просто. У нас часто возникает ситуация, когда мы создаем объекты одного типа, при этом создаем мы их не сразу друг за другом, а на всем протяжении жизни программы, зная о том, что эти объекты будут удалены вместе. Это приведет к фрагментации. Чтобы так не случилось, можно, например, сделать пул этих объектов. В нем объекты создаются друг за другом, группой. И когда будет потеряна ссылка на пул, они вместе уничтожаться, тем самым создав единый gap. Я в целом приходил к выводам, что пулы использовать очень хорошо. Они могут помочь во многих ситуациях. Спасают от фрагментации, от попадания на карточный стол (вы достаете объект из пула уже второго поколения). Пул - метод инициализации. Туда же можно отнести боксинг, который можно решить через эмуляцию боксинга или даже пул эмуляции боксинга. Если есть какой-то метод, который принимает object, вы изменить этого не можете и вынуждены боксить, то можно этого избежать. Фаза планирования на этом завершена.
\ No newline at end of file
diff --git a/book/ru/Memory/03-10-MemoryManagement-GC-Sweep-Collect.md b/book/ru/Memory/03-10-MemoryManagement-GC-Sweep-Collect.md
new file mode 100644
index 0000000..88152c1
--- /dev/null
+++ b/book/ru/Memory/03-10-MemoryManagement-GC-Sweep-Collect.md
@@ -0,0 +1,33 @@
+## Sweep & Collect
+
+Мы рассмотрели практически все стадии, кроме последней. Эта фаза зачистки и сжатия.
+
+Если на фазе планирования было решено использовать sweep, мы должны осуществить GC путем отдачи всех неиспользуемых участков на переиспользование. Когда наши allocation context, которые существуют от разных потоков, начнут заканчиваться, они будут занимать эти свободные участки между другими объектами, таким образом исключая фазу сжатия. Фаза сжатия работает дольше. После этого этапа мы имеем такую таблицу 01:27. У нас есть поколения с бакетами, которые указывают на односвязаный список свободных участков. Бакеты сгруппированы по размерам. Внутри одного бакета идет список участков примерно одного размера. Мы можем попросить у бакетов участок нужного размера, и он по принципу first fit найдет нужный бакет и через best fit найдет подходящий участок внутри бакета.
+
+Gaps, которые меньше размера объекта игнорируются.
+
+После этого saved_pre_plug и saved_post_plugs расставляются по местам. Потом выполняется работа по обновлению очереди на финализацию. И затем - перестроение сегментов памяти.
+
+Например, может так случится, что после GC у нас часть страниц или сегментов больше не нужны. Зачем их держать, если можно отдать кому-то еще? Операционная система большая. Если не отдать, то все соседние приложения будут ругаться, что работают медленно. Так и есть: потому что наше приложение не освободило оперативную память под соседей.
+
+Sweep на LOH работает по-другому. Он не использует планирование, он обходит кучу. Свободные участки объединяет с состыкованными в один. Освободившиеся участки между занятыми добавляются в список свободных участков.
+
+Для меня было откровением, что в обоих хипах механизмы одни и те же. Просто в одном хипе у них разные приоритеты. В LOH compacting в настолько низком приоритете, что часть алгоритма просто отключена. Но в целом SOH и LOH использует одни и те же алгоритмы.
+
+Compacting.
+
+Если необходимо создается новый эфемерный сегмент. Если фаза планирования решила, что после сжатия места под gen_0 будет мало - создаем новый сегмент. Заменяем все ссылки на корректные. Для этого у нас есть информация по gaps&plugs, по которым легко посчитать все смещения. Перед тем, как сжимать, мы должны все ссылки поменять. Собирая gen_0 мы просматриваем его и карточный стол старшего поколения. Когда это сделано - начинаем менять ссылки в фазе сжатия до самого сжатия, чтобы не потерять информация по gaps и офсетам. Сканируем все места, производя замену. Сначала ссылки со стека, которые включают в себя все локальные переменные и параметры методов, переданные через стек, а так же eager roots collection. Потом ссылки с полей объектов, полученные через карточный стол. Затем ссылки с полей объектов SOH и LOH с этапа обхода графа. Также ссылки с pre и post plugs, потому что при сохранении туда могли попасть и ссылки. Pinned plugs queue. Ссылки с полей объектов, находящихся в finalization queue. Ссылки с handle tables.
+
+Мы должны выполнить все эти шесть шагов. Вывод - чем сильнее связанность графа, тем дольше работает этот шаг. Нужно обойти все это очень аккуратно, чтобы не промахиваться по кешу в рамках одного участка памяти.
+
+Далее выполняется копирование объектов на их новые места. После того, как мы поменяли все адреса, происходит сама фаза сжатия. Из последних байт gaps берется значение офсета, и на него смещаются все выжившие объекты, чтобы сжать кучу. Естественно, кроме запиненых объектов и тех, которые размещены после них.
+
+После этого восстанавливаются все pre и post plugs участки и исправляем положение поколений (после GC нулевое поколение становится первым, первое - вторым и нулевое сдвигается так, чтобы можно было выделять память дальше).
+
+После этого удаляется и разкоммичеивается память из-под полностью освободившихся сегментов.
+
+Перед каждым запиненым объектом образуется свободный участок. Его надо сохранить для дальнейшего выделения памяти. То есть объект запинен. Слева от него занятое место. И между занятым местом и запиненым объектом - свободные участки. Чтобы их потом переиспользовать, они сохраняются в список свободных участков. Чтобы это сделать нужно убедиться, что есть нужный бакет, либо создать новый, и уже оттуда спустить ссылку на односвязаный список занятых участков определенного размера.
+
+Получается, что задача sweep - создать односвязаный список на свободных участках. Сжатие - это дольше. Чтобы сработал sweep, надо группировать объекты, срок жизни которых совпадает. Это максимум, что мы можем сделать.
+
+Чтобы не мешать GC, мы не используем GCHandle bind. По возможности, мы используем только fixed. В этом случае мы можем избежать пининга, потому что fixed - это просто напоминание GC, что если он здесь очутился, то пинить надо.
diff --git a/book/ru/Memory/03-12-MemoryMenegement-GC-Results.md b/book/ru/Memory/03-12-MemoryMenegement-GC-Results.md
new file mode 100644
index 0000000..2caeb5f
--- /dev/null
+++ b/book/ru/Memory/03-12-MemoryMenegement-GC-Results.md
@@ -0,0 +1,51 @@
+## Выводы
+
+> [In Progress] адаптация курса
+
+Давайте поговорим о выводах.
+
+Первое: снижайте кросс-поколенческую связанность.
+
+Проблема: для оптимизации скорости сборки мусора GC, по возможности, младшее поколение. Он старается это делать часто, чтобы уложится в какие-то миллисекунды. Чтобы сделать это, ему необходима информация о ссылках старших, с карточного стола. Если карточный стол пустой, в особенности если bundle table пустой (потому что именно он покрывает мегабайты своими ячейками), если мы везде встретим нули - это просто отличная информация и GC пролетит максимально быстро. Если на bundle table встречает не ноль, то GC идет на карточный стол, начинает анализировать его, опускается еще ниже: анализируются килобайты памяти, 320 объектов в максимуме, для того, чтобы понять, какой из этих 320 объектов ссылается на более младшее поколение. Поэтому разреженные ссылки, в хаотичном порядке - это самый худший результат.
+
+Как одно из решений - это располагать объекты со связями в младшем поколении рядом, чтобы за них отвечала одна карта. Аллоцировать их группами, выдавая пользовательскому коду по запросу. Если мы так сделаем, то они вместе уйдут во второе поколение (например, пул) и карточный стол будет пустой. Избегать ссылок в младшее поколение, что уже сложнее.
+
+Нужно не допускать сильной связанности. Как следует из алгоритмов фазы сжатия, для сжатия кучи необходимо обойти дерево и проверить все ссылки, исправляя их на новые значения. Это та еще работа. При этом ссылки с карточного стола затрагивают целую группу объектов. Поэтому общая сильная связанность может привезти к проседанию при GC.
+
+Тут советы простые. Во-первых, располагать сильно-связанные объекты рядом во втором поколении. Это отсылка к карточному столу. Во-вторых, стоит избегать лишних связей в целом. Иногда бывает желание не обращаться через две ссылки к какому-то полю, разместить эту ссылку рядом. Таким образом добавляется еще третья ссылка на объект. А сильная связанность означает, что при сжатии надо будет намного больше этих ссылок обойти и исправить. Ссылок надо делать меньше. В том числе, избегать синтаксического сахара. Зачастую образуются аллокации, которых не видно. Как их увидеть? Можно установить расширение, которое показывает в коде скрытые аллокации. Это расширение в курсе, какие конструкции языка создают лишний трафик. Если хочется оптимизировать нагруженный код, это сильно помогает.
+
+Например, замыкания в некоторой степени зло. Они удобные, но с ними надо быть очень осторожными: замыкания начинают удерживать ссылки.
+
+Если говорить о disposable, то вызов метода CheckDisposed() нужно поставить во все публичные места. Помимо public методов, это еще и protected методы, internal методы - все, что не private. Что самое неприятное, публичным методом является лямбда, которую вы куда-то отдали по подписке. В этот момент она стала публичным методом, теперь ее можно откуда-то дернуть. И туда тоже надо ставить CheckDisposed(). Такие вещи могут породить лишнюю связанность. Когда вы что-то забыли и объект ушел на финализацию, он, через внутренние ссылки, тянет за собой все дерево, весь граф нашего приложения. Объекты, которые должны были быть собраны в нулевом поколении, незаметно уходят во второе.
+
+Следующий совет - мониторьте использование сегментов. Если у нас интенсивно работает приложение, может возникнуть ситуация, когда выделение новых объектов приводит к задержкам. Как это делать? С использованием утилит. Например PerfMon, Sysinternal Utilities, dotMemory. Именно сегменты лучше смотреть их более системных утилит, типа Sysinternal. Нужно смотреть точки выделения новых сегментов, совпадают ли они с вашими просадками. Что с этим делать? Если речь идет о LOH, то если в нем идет плотный трафик буферов, то, возможно, стоит переключится на использование ArrayPool. Он для этого и был сделан. Вместо того, чтобы постоянно аллоцировать кучу массивов, которые сами по себе тяжелые элементы, используйте ArrayPool.
+
+Если речь идет о SOH, стоит убедиться, что объекты одного времени жизни выделяются рядом, обеспечивая большую вероятность срабатывания sweep вместо collect (compact?). Если они рядом все уйдут, то пусть рядом и создадутся. Если у нас нагруженный код, внутри которого постоянно идут временные аллокации, то их лучше выделять из пула, чем через операцию new - она нагружает GC.
+
+Еще один совет - не выделяйте память в нагруженных участках кода. Если так делать, то GC выбирает окно аллокации, не 1Кб, а 8. И если окну не хватает места, это приводит к GC и расширению заккоммиченной зоны. Плотный трафик новых объектов заставит короткоживущие объекты с других потоков быстро уйти в старшее поколение с худшими условиями сборки мусора. Если у нас плотный трафик, мы не успеваем старое освобождать, поэтому объекты, рассчитанные на существование в нулевом поколении, уходят в первое, где GC работает медленнее. Когда мы говорим "объекты, рассчитанные на существование в нулевом поколении", сражу же понимаем, что создаем объект, который будет жить не дольше секунды. Мы его создали, забили данными и практически сразу отпустили. Он рассчитан на жизнь в нулевом поколении и чем раньше его отпустить, тем лучше. Как это сделать? Не хранить ссылку. Например, у вас есть длинный метод со своей логикой. И вы метод формируете не по принципу действий друг за другом, а по принципу "похожие операции рядом". Тогда получается, что в начале идет инициализация, а внизу эти объекты используются. Возможно, это использование можно поставить повыше. Метод большой, делает кучу всего. А чем он больше, тем выше вероятность срабатывания в процессе GC. Если вы выше использование этих объектов поднимите, выше вероятность того, что эти объекты уйдут с хипа прямо посреди работы этого метода. Если объекты передержать, а GC сработает, эти объекты уйдут в первое поколение.
+
+Полный запрет на использование замыканий в критичных участках кода. Полный запрет боксинга на критичных участках кода. Там, где необходимо создать временный объект под хранение данных, использовать структуры. Потому что структура ложиться на стеке, ничего не аллоцирует, моментально освобождается. Освобождение структуры требует простого сдвига указателя регистра SP. И не имеет значения, сколько у вас локальных переменных, их освобождение происходит с одинаковой скоростью вне зависимости от их количества. Еще лучше использовать ref struct - это stack only структуры. Для него джиттер может делать оптимизации.
+
+При количестве полей более двух - передавать по референсу. Если в этом случае прокидывать через параметры - будет очень жирно, будет идти копирование. А если перетащить по референсу, то будет передан только указатель, что бесплатно.
+
+Избегайте излишних выделений памяти в LOH. Размещение массивов в этой куче приводит либо к его фрагментации, либо к утяжелению процедуры GC.
+
+Решения. Ипсользовать разделение массивов на подмассивы и класс, который их инкапсулирует. На одном из докладов классная техника рассказывалась. Когда большие массивы в этот хип уходят - это плохо. Можно разделить большой массив на ряд маленьких, сделать массив массивов. То есть, массив, ячейки которого указывают на массивы. И все это ляжет в SOH. Работать с этим легко. Массиву нужно выставить правильную длину, например 2048. Когда мы делает доступ по индексу, вместо того, чтобы делить на 2048, мы сдвигаем на 11 бит. Тем самым делаем очень быстрый доступ к элементам внутренних массивов с эмуляцией непрерывного куска памяти. При этом такие массивы уйдут в SOH, и, если использовать ArrayPool, они лягут во второе поколение и перестанут влиять на сборку мусора.
+
+Также стоит контролировать использование массово double, чтобы они были меньше тысячи. Где оправдано и возможно использовать стек потока?Вместо того, чтобы делать оператор new, класс использовать стек потока. Например, есть ряд сверх-короткоживущих объектов - тех, которые живут в рамках вызова метода. Такие вещи создают трафик, особенно в нагруженных местах. Использование выделения на стеке, во-первых, полностью разгрузит кучу. Выделить память на стеке - это либо либо создать локальную переменную, либо использовать ключевое слово stackalloc. В моей книге есть целый раздел, посвященный этому оператору в главе "Стек потока". Это оператор C#, который выделяет память не в хипе, а в локальных переменных. Эта операция практически бесплатная и очень быстрая.
+
+Чтобы изучить, как правильно использовать этот оператор, я решил, что надо обратиться к авторам. Открыл исходники и искал по тексту. 90% использования - это тесты. В других местах - очень редко. В частности, я встретил stack list и value stringbuilder. Чем плох обычный stringbuilder? Нам говорят, что для соединения строк плюс - это плохо. Что делает обычный stringbuilder? Это однозсвязный список, внутри которого есть кусочки массивов, которые формируют строку. Когда мы создаем строку, мы туда аппендим. Он заполняет кусочки по очереди. Получается, что вы избавитесь от проблемы фрагментации строками, но не до конца. В этом случае все равно много всего создается. Как избежать?
+
+Большинство случаев использования simple stringbuilder и плюсов - это трассировка. Logger.trace() или logger.debug(). Вы формируете маленькие строчки из разнородных вещей. А value stringbuilder - это очень хорошая вещь, но есть одна проблема: его модификатор доступа internal. Но его можно обойти. Это stringbuilder на стеке. Вещь, которая строит строчку внутри локальный переменных, не аллоцируя вообще ничего. При создании экземпляр на вход ждет span - указатель на некий range памяти. Можно это написать следующим образом: Span x = stackalloc T[ ]. То есть вы среди локальных переменных выделяете память нужного размера и сохраняете в span. И дальше, на основе этого span создаете value stringbuilder, который внутри этого буфера будет строить строчку, не аллоцируя память вообще. Единсвенный случай, когда он аллоцирует память - если он не вместился. В остальных случаях все будет очень хорошо.Второй кейс - если ваш logger.info на вход принимает не строчку, а span, у вас даже строка не будет аллоцирована.
+
+Стоит использовать span memory, где это возможно, потому что эта вещь, которая нас спасает от лишний аллокаций.Освобождайте объекты как можно раньше. Задуманные как короткоживущие объекты могут попасть в gen_1, а иногда и в gen_2. Это приводит к более тяжелому GC, который работает дольше. Поэтому необходимо освобождать ссылку на объект как можно раньше. Если длительный алгоритм содержит код, который работает с какими-то объектами, разнесенными по коду, необходимо сгруппировать, перенося использование ближе. Это увеличит вероятность того, что они будут собраны GC.
+
+Вызывать GC.Collect() не нужно. Часто кажется, что если вызвать GC.Collect(), то это исправит ситуацию. Но намного полезнее выучить алгоритмы работы GC и посмотреть на приложение под тулзой трассировки: dotMemory и другим средством диагностики. Это покажет наиболее нагруженные участки, избавиться от лишних аллокаций, лишнего трафика. Главное, не увлекаться: преждевременная оптимизация тоже может привести к нечитаемости кода.
+
+Еще один совет - избегайте пиннинга. Пиннинг создает кучу проблем. GC ходит вокруг запинненных объектов как по минному полю.
+
+Избегайте финализации. Финализация вызывается не детерменированно. Она может не вызваться. Не вызванный Dispose() приводит к финализации со всеми исходящими ссылками из объекта, который держит другие объекты. Все это переносится в более старшее поколение, усложняя GC, приводя к полному GC во всех поколениях и заменой sweep на compacting.
+
+Нужно аккуратно вызвать Dispose(). Избегать большого количества потоков. Вообще, количество потоков советуют держать в районе количества ядер. Больше нет смысла, если все они работают без ожидания.
+
+Избегайте трафика объектов разного размера. При трафике объектов разного размера и времени жизни возникает фрагментация, как результат - повышение fragmentation ratio, срабатывание collection с изменением адресов во всех ссылающихся объектах. Поэтому решение - если предполагается трафик объектов, надо контролировать наличие лишних полей, приблизив размеры. Также проконтролировать отсутсвие манипуляций со строками. Там, где возможно, заменить readonly span на readonly memory. Освободить ссылку как можно раньше. Не обязательно обнулять, в методах достаточно поднять использование как можно выше.
\ No newline at end of file
diff --git a/book/ru/LifetimeManagement/3-Lifetime.md b/book/ru/Memory/QQ-Lifetime.md
similarity index 100%
rename from book/ru/LifetimeManagement/3-Lifetime.md
rename to book/ru/Memory/QQ-Lifetime.md
diff --git a/book/ru/ObjectsStructure.md b/book/ru/Memory/QQ-ObjectsStructure.md
similarity index 99%
rename from book/ru/ObjectsStructure.md
rename to book/ru/Memory/QQ-ObjectsStructure.md
index 7b527ea..418fffa 100644
--- a/book/ru/ObjectsStructure.md
+++ b/book/ru/Memory/QQ-ObjectsStructure.md
@@ -804,3 +804,5 @@ interface IC : IA
Почему это ужасно? Ведь на самом деле это порождает целый класс возможностей. Теперь нам не нужно будет каждый раз реализовывать какие-то методы интерфейсов, которые везде реализовывались одинаково. Звучит прекрасно. Но только звучит. Ведь интерфейс - это протокол взаимодействия. Протокол - это набор правил, рамки. В нем нельзя допускать существование реализаций. Здесь же идёт прямое нарушение этого принципа и введение ещё одного: множественного наследования. Я, честно, сильно против таких доработок, но... Я что-то ушёл в сторону.
[DispatchMap::CreateEncodedMapping](https://github.com/dotnet/coreclr/blob/master/src/vm/contractimpl.cpp#L295-L460)
+
+> Далее: [Memory<T> и Span<T>](./3-MemorySpan.md)
\ No newline at end of file
diff --git a/book/ru/LifetimeManagement/imgs/Disposable-Cover.png b/book/ru/Memory/imgs/Disposable-Cover.png
similarity index 100%
rename from book/ru/LifetimeManagement/imgs/Disposable-Cover.png
rename to book/ru/Memory/imgs/Disposable-Cover.png
diff --git a/book/imgs/Span/Performance.png b/book/ru/Memory/imgs/Span/Performance.png
similarity index 100%
rename from book/imgs/Span/Performance.png
rename to book/ru/Memory/imgs/Span/Performance.png
diff --git a/book/imgs/ThreadStack/AnyMethodCall.png b/book/ru/Memory/imgs/ThreadStack/AnyMethodCall.png
similarity index 100%
rename from book/imgs/ThreadStack/AnyMethodCall.png
rename to book/ru/Memory/imgs/ThreadStack/AnyMethodCall.png
diff --git a/book/imgs/ThreadStack/ForkAfterEnter.png b/book/ru/Memory/imgs/ThreadStack/ForkAfterEnter.png
similarity index 100%
rename from book/imgs/ThreadStack/ForkAfterEnter.png
rename to book/ru/Memory/imgs/ThreadStack/ForkAfterEnter.png
diff --git a/book/imgs/ThreadStack/ForkBeforeEnter.png b/book/ru/Memory/imgs/ThreadStack/ForkBeforeEnter.png
similarity index 100%
rename from book/imgs/ThreadStack/ForkBeforeEnter.png
rename to book/ru/Memory/imgs/ThreadStack/ForkBeforeEnter.png
diff --git a/book/imgs/ThreadStack/step1.png b/book/ru/Memory/imgs/ThreadStack/step1.png
similarity index 100%
rename from book/imgs/ThreadStack/step1.png
rename to book/ru/Memory/imgs/ThreadStack/step1.png
diff --git a/book/imgs/ThreadStack/step2.png b/book/ru/Memory/imgs/ThreadStack/step2.png
similarity index 100%
rename from book/imgs/ThreadStack/step2.png
rename to book/ru/Memory/imgs/ThreadStack/step2.png
diff --git a/book/imgs/ThreadStack/step3.png b/book/ru/Memory/imgs/ThreadStack/step3.png
similarity index 100%
rename from book/imgs/ThreadStack/step3.png
rename to book/ru/Memory/imgs/ThreadStack/step3.png
diff --git a/book/imgs/ThreadStack/step4.png b/book/ru/Memory/imgs/ThreadStack/step4.png
similarity index 100%
rename from book/imgs/ThreadStack/step4.png
rename to book/ru/Memory/imgs/ThreadStack/step4.png
diff --git a/book/imgs/ThreadStack/step5.png b/book/ru/Memory/imgs/ThreadStack/step5.png
similarity index 100%
rename from book/imgs/ThreadStack/step5.png
rename to book/ru/Memory/imgs/ThreadStack/step5.png
diff --git a/book/imgs/ThreadStack/step6.png b/book/ru/Memory/imgs/ThreadStack/step6.png
similarity index 100%
rename from book/imgs/ThreadStack/step6.png
rename to book/ru/Memory/imgs/ThreadStack/step6.png
diff --git a/book/imgs/tmp/internal_life.png b/book/ru/Memory/imgs/tmp/internal_life.png
similarity index 100%
rename from book/imgs/tmp/internal_life.png
rename to book/ru/Memory/imgs/tmp/internal_life.png
diff --git a/book/ru/readme.md b/book/ru/readme.md
index 9dc3363..2486ffa 100644
--- a/book/ru/readme.md
+++ b/book/ru/readme.md
@@ -1,41 +1,90 @@
-![](../../bin/BookCover-ru.png)
+## Введение
+
+Эта книга задумана мной как максимально полное описание работы .NET CLR, и частично - .NET Framework и призвана в первую очередь заставить посмотреть читателя на его внутреннюю структуру под несколько другим углом: не так, как это делается обычно. Связано это в первую очередь с утверждением, которое может показаться многим очень спорным: любой разработчик обязан пройти школу C/C++. Почему? Да потому что из высокоуровневых эти языки наиболее близки к процессору, и программируя на них начинаешь чувствовать работу программы сильнее. Однако, понимая, что мир устроен несколько иначе и у нас зачастую нет никакого времени изучать то, чем мы не будем напрямую пользоваться, я и решил написать эту книгу, в которой объяснение всех вопросов идет с более глубокой чем обычно - позиции и с более сложными или же попросту альтернативными примерами. Которые, помимо своей стандартной миссии - на самом простом коде показать как работает тот или иной функционал, сделать реверанс в альтернативную реальность, показав что все сильно сложнее чем может показаться изначально. Зачем? Чтобы и у вас возникло чувство понимания работы CLR до последнего винтика
# Содержание
- 1. Common Language Runtime
- 2. Основы менеджмента памяти: пользовательский слой
- 1. [Heap basics](./MemoryManagementBasics.md)
- 2. [Стек потока](./ThreadStack.md)
- 3. [RefTypes, ValueTypes, Boxing & Unboxing](./ReferenceTypesVsValueTypes.md)
- 4. [Memory, Span](./MemorySpan.md)
- 5. [Структура объектов в памяти](./ObjectsStructure.md)
- 6. Small Objects Heap
- 7. Large Objects Heap
- 8. Garbage Collection
- 9. Statics
- 3. Слой управления памятью: как работает CLR
- 1. Подробно про Small Objects Heap
- 1. Пример: дамп памяти, влияние pinned objects на аллокацию
- 2. Large Objects Heap
- 1. Пример: как легко испортить кучу, как этого избегать
- 3. Garbage Collection
- 1. Mark & Sweep
- 2. Оптимизация поколений
- 3. Финализация
- 4. Проблемы, связанные с GC и финализацией
- 5. [Шаблон Disposable (Disposable Design Principle)](./LifetimeManagement/2-Disposable.md)
- 5. [Шаблон Lifetime](./LifetimeManagement/3-Lifetime.md)
- 4. Поток исполнения команд
- 1. Домены приложений
- 1. [Введение в домены приложений](./AppDomains/1-AppDomains-Intro.md)
- 2. [Изоляция](./AppDomains/2-AppDomains-Isolation.md)
- 3. [Модель безопасности](./AppDomains/3-AppDomains-Security.md)
- 2. Исключительные ситуации
- 1. [Введение в исключительные ситуации](./ExceptionalFlow/1-Exceptions-Intro.md)
- 2. [Архитектура исключительной ситуации](./ExceptionalFlow/2-Exceptions-Architecture.md)
- 3. [События об исключительных ситуациях](./ExceptionalFlow/3-Exceptions-Events.md)
- 4. [Виды исключительных ситуаций](./ExceptionalFlow/4-Exceptions-Types.md)
+1. **Часть 1. Память**
+ 1. **Раздел 1.** Введение в управление памятью
+ 1. [Общие слова](./Memory/01-00-MemoryManagement-Intro.md)
+ 1. [Введение в управление памятью](./Memory/01-02-MemoryManagement-Basics.md)
+ 1. [Пара слов перед стартом](./Memory/01-02-MemoryManagement-Basics.md#пара-слов-перед-стартом)
+ 1. [Введение в управление памятью](./Memory/01-02-MemoryManagement-Basics.md#введение-в-управление-памятью)
+ 1. [Возможные классификации памяти исходя из логики](./Memory/01-02-MemoryManagement-Basics.md#возможные-классификации-памяти-исходя-из-логики)
+ 1. [Как это работает у нас. Обоснование выбора архитекторов](./Memory/01-02-MemoryManagement-Basics.md#как-это-работает-у-нас-обоснование-выбора-архитекторов)
+ 1. [Как это работает у нас](./Memory/01-02-MemoryManagement-Basics.md#как-это-работает-у-нас)
+ 1. [Стек потока](./Memory/01-04-MemoryManagement-ThreadStack.md)
+ 1. [Базовая структура, платформа x86](./Memory/01-04-MemoryManagement-ThreadStack.md#базовая-структура-платформа-x86)
+ 1. [Немного про исключения на платформе x86](./Memory/01-04-MemoryManagement-ThreadStack.md#немного-про-исключения-на-платформе-x86)
+ 1. [Совсем немного про несовершенство стека потока](./Memory/01-04-MemoryManagement-ThreadStack.md#совсем-немного-про-несовершенство-стека-потока)
+ 1. [Большой пример: клонирование потока на платформе х86](./Memory/01-04-MemoryManagement-ThreadStack.md#большой-пример-клонирование-потока-на-платформе-х86)
+ 1. [Время жизни сущности](./Memory/01-06-MemoryManagement-EntitiesLifetime.md)
+ 1. [Ссылочные типы](./Memory/01-06-MemoryManagement-EntitiesLifetime.md#ссылочные-типы)
+ 1. [Общий обзор](./Memory/01-06-MemoryManagement-EntitiesLifetime.md#общий-обзор)
+ 1. [В защиту текущего подхода](./Memory/01-06-MemoryManagement-EntitiesLifetime.md#в-защиту-текущего-подхода)
+ 1. [Предварительные выводы](./Memory/01-06-MemoryManagement-EntitiesLifetime.md#предварительные-выводы)
+ 1. [RefTypes, ValueTypes, Boxing & Unboxing](./Memory/01-08-MemoryManagement-RefVsValueTypes.md)
+ 1. [Ссылочные и значимые типы данных](./Memory/01-08-MemoryManagement-RefVsValueTypes.md#ссылочные-и-значимые-типы-данных)
+ 1. [Копирование](./Memory/01-08-MemoryManagement-RefVsValueTypes.md#копирование)
+ 1. [Переопределяемые методы и наследование](./Memory/01-08-MemoryManagement-RefVsValueTypes.md#переопределяемые-методы-и-наследование)
+ 1. [Поведение при вызове экземплярных методов](/Memory/01-08-MemoryManagement-RefVsValueTypes.md#поведение-при-вызове-экземплярных-методов)
+ 1. [Возможность указать положение элементов](/Memory/01-08-MemoryManagement-RefVsValueTypes.md#возможность-указать-положение-элементов)
+ 1. [Разница в аллокации](/Memory/01-08-MemoryManagement-RefVsValueTypes.md#разница-в-аллокации)
+ 1. [Особенности выбора между class/struct](/Memory/01-08-MemoryManagement-RefVsValueTypes.md#особенности-выбора-между-classstruct)
+ 1. [Базовый тип - Object и возможность реализации интерфейсов. Boxing](/Memory/01-08-MemoryManagement-RefVsValueTypes.md#базовый-тип---object-и-возможность-реализации-интерфейсов-boxing)
+ 1. [Nullable<T>](./Memory/01-08-MemoryManagement-RefVsValueTypes.md#nullablet)
+ 1. [Погружаемся в boxing ещё глубже](./Memory/01-08-MemoryManagement-RefVsValueTypes.md#погружаемся-в-boxing-ещё-глубже)
+ 1. [Что если хочется лично посмотреть как работает boxing?](./Memory/01-08-MemoryManagement-RefVsValueTypes.md#что-если-хочется-лично-посмотреть-как-работает-boxing)
+ 1. [Почему .NET CLR не делает пуллинга для боксинга самостоятельно?](./Memory/01-08-MemoryManagement-RefVsValueTypes.md#почему-net-clr-не-делает-пуллинга-для-боксинга-самостоятельно)
+ 1. [Почему при вызове метода, принимающего тип object, а по факту - значимый тип нет возможности сделать boxing на стеке, разгрузив кучу?](./Memory/01-08-MemoryManagement-RefVsValueTypes.md#почему-при-вызове-метода-принимающего-тип-object-а-по-факту---значимый-тип-нет-возможности-сделать-boxing-на-стеке-разгрузив-кучу)
+ 1. [Почему нельзя использовать в качестве поля Value Type его самого?](./Memory/01-08-MemoryManagement-RefVsValueTypes.md#почему-нельзя-использовать-в-качестве-поля-value-type-его-самого)
+ 1. [Шаблон Disposable](./Memory/01-10-MemoryManagement-IDisposable.md)
+ 1. [IDisposable](./Memory/01-10-MemoryManagement-IDisposable.md#idisposable)
+ 1. [Вариации реализации IDisposable](./Memory/01-10-MemoryManagement-IDisposable.md#вариации-реализации-idisposable)
+ 1. [SafeHandle / CriticalHandle / SafeBuffer / производные](./Memory/01-10-MemoryManagement-IDisposable.md#safehandle--criticalhandle--safebuffer--производные)
+ 1. [Срабатывание finalizer во время работы экземплярных методов](./Memory/01-10-MemoryManagement-IDisposable.md#срабатывание-finalizer-во-время-работы-экземплярных-методов)
+ 1. [Многопоточность](./Memory/01-10-MemoryManagement-IDisposable.md#многопоточность)
+ 1. [Два уровня Disposable Design Principle](./Memory/01-10-MemoryManagement-IDisposable.md#два-уровня-disposable-design-principle)
+ 1. [Как ещё используется Dispose](./Memory/01-10-MemoryManagement-IDisposable.md#как-ещё-используется-dispose)
+ 1. [Делегаты, events](./Memory/01-10-MemoryManagement-IDisposable.md#делегаты-events)
+ 1. [Лямбды, замыкания](./Memory/01-10-MemoryManagement-IDisposable.md#лямбды-замыкания)
+ 1. [Защита от ThreadAbort](./Memory/01-10-MemoryManagement-IDisposable.md#защита-от-threadabort)
+ 1. [Итоги](./Memory/01-10-MemoryManagement-IDisposable.md#итоги)
+ 1. [Финализация](./Memory/01-12-MemoryManagement-Finalizer.md)
+ 1. [Выводы](./Memory/01-14-MemoryManagement-Results.md)
+ 1. **Раздел 2.** Практическая
+ 1. [Memory, Span](./Memory/02-02-MemoryManagement-MemorySpan.md)
+ 1. [Span<T>, ReadOnlySpan<T>](./Memory/02-02-MemoryManagement-MemorySpan.md#spant-readonlyspant)
+ 1. [Span<T> на примерах](./Memory/02-02-MemoryManagement-MemorySpan.md#spant-на-примерах)
+ 1. [Правила и практика использования](/Memory/02-02-MemoryManagement-MemorySpan.md#правила-и-практика-использования)
+ 1. [Как работает Span](./Memory/02-02-MemoryManagement-MemorySpan.md#как-работает-span)
+ 1. [Span<T> как возвращаемое значение](/Memory/02-02-MemoryManagement-MemorySpan.md#spant-как-возвращаемое-значение)
+ 1. [Memory<T> и ReadOnlyMemory<T>](./Memory/02-02-MemoryManagement-MemorySpan.md#memoryt-и-readonlymemoryt)
+ 1. [Memory<T>.Span](/Memory/02-02-MemoryManagement-MemorySpan.md#memorytspan)
+ 1. [Memory<T>.Pin](/Memory/02-02-MemoryManagement-MemorySpan.md#memorytpin)
+ 1. [MemoryManager, IMemoryOwner, MemoryPool](./Memory/02-02-MemoryManagement-MemorySpan.md#memorymanager-imemoryowner-memorypool)
+ 1. [Производительность](./Memory/02-02-MemoryManagement-MemorySpan.md#производительность)
+ 1. **Раздел 3.** Подробности реализации GC
+ 1. [Выделение памяти под объект](./Memory/03-02-MemoryManagement-Allocation.md) *(Только наговорен текст)*
+ 1. [Введение в сборку мусора](./Memory/03-04-MemoryManagement-GC-Intro.md) *(Только наговорен текст)*
+ 1. [Фаза маркировки](./Memory/03-06-MemoryManagement-GC-Mark-Phase.md) *(Только наговорен текст)*
+ 1. [Фаза планирования](./Memory/03-08-MemoryManagement-GC-Planning-Phase.md) *(Только наговорен текст)*
+ 1. [Фазы Sweep/Collect](./Memory/03-10-MemoryManagement-GC-Sweep-Collect.md) *(Только наговорен текст)*
+ 1. [Выводы по менеджменту памяти и работе над производительностью](./Memory/03-12-MemoryMenegement-GC-Results.md) *(Только наговорен текст)*
+ 1. **Раздел 4.** Структура объектов в памяти
+ 1. [Структура объектов в памяти](./Memory/QQ-ObjectsStructure.md)
+ 1. **Раздел 5.** Вне порядка повествования
+ 1. [Шаблон Lifetime](./Memory/2-Basics/4-LifetimeManagement/3-Lifetime.md)
+1. **Часть 2. Поток исполнения команд**
+ 1. Раздел 1. Домены приложений
+ 1. [Введение в домены приложений](./Execution/A-AppDomains/1-AppDomains-Intro.md)
+ 1. [Изоляция](./Execution/A-AppDomains/2-AppDomains-Isolation.md)
+ 1. Раздел 2. Исключительные ситуации
+ 1. [Введение в исключительные ситуации](./Execution/2-ExceptionalFlow/1-Exceptions-Intro.md)
+ 1. [Архитектура исключительной ситуации](./Execution/2-ExceptionalFlow/2-Exceptions-Architecture.md)
+ 1. [События об исключительных ситуациях](./Execution/2-ExceptionalFlow/3-Exceptions-Events.md)
+ 1. [Виды исключительных ситуаций](./Execution/2-ExceptionalFlow/4-Exceptions-Types.md)
# Лицензия
-Находится в файле [LICENSE](../../LICENSE)
\ No newline at end of file
+Находится в файле [LICENSE](../../LICENSE)