diff options
Diffstat (limited to 'Documentation')
-rw-r--r-- | Documentation/botr/garbage-collection.md | 10 | ||||
-rw-r--r-- | Documentation/botr/intro-to-clr.md | 2 | ||||
-rw-r--r-- | Documentation/botr/threading.md | 18 |
3 files changed, 15 insertions, 15 deletions
diff --git a/Documentation/botr/garbage-collection.md b/Documentation/botr/garbage-collection.md index c7f4741137..f10bf0c4a3 100644 --- a/Documentation/botr/garbage-collection.md +++ b/Documentation/botr/garbage-collection.md @@ -77,7 +77,7 @@ Logical representation of the managed heap The CLR GC is a generational collector which means objects are logically divided into generations. When a generation _N_ is collected, -the survived objects are now marked as belong to generation _N+1_. This +the survived objects are now marked as belonging to generation _N+1_. This process is called promotion. There are exceptions to this when we decide to demote or not promote. @@ -126,7 +126,7 @@ The allocation budget The allocation budget is a logical concept associated with each generation. It is a size limit that triggers a GC for that -generation when it exceeded. +generation when exceeded. The budget is a property set on the generation mostly based on the survival rate of that generation. If the survival rate is high, the budget is made larger with the expectation that there will be a better ratio of dead to live objects next time there is a GC for that generation. @@ -170,7 +170,7 @@ Relocate phase -------------- If the GC decides to compact, which will result in moving objects, then references to these objects must be updated. The relocate phase needs to find all references that point to objects that are in the -generations being collected. In contrast, the mark phase only consults live objects so doesn’t need to consider weak references. +generations being collected. In contrast, the mark phase only consults live objects so it doesn’t need to consider weak references. Compact phase ------------- @@ -213,7 +213,7 @@ This illustrates how a background GC is done. 1. User thread runs out of allocation budget and triggers a GC. 2. GC calls SuspendEE to suspend managed threads. 3. GC decides if background GC should be run. -4. If so background GC thread is woken up, to do a background +4. If so background GC thread is woken up to do a background GC. Background GC thread calls RestartEE to resume managed threads. 5. Managed threads continue allocating while the background GC does its work. 6. User thread may run out of allocation budget and trigger an @@ -227,7 +227,7 @@ This illustrates how a background GC is done. ### SVR GC with concurrent GC off 1. User thread runs out of allocation budget and triggers a GC. -2. Server GC threads are woken up and calls SuspendEE to suspend +2. Server GC threads are woken up and call SuspendEE to suspend managed threads. 3. Server GC threads do the GC work (same phases as in workstation GC without concurrent GC). diff --git a/Documentation/botr/intro-to-clr.md b/Documentation/botr/intro-to-clr.md index 2e5b8de0a2..097f2e30fd 100644 --- a/Documentation/botr/intro-to-clr.md +++ b/Documentation/botr/intro-to-clr.md @@ -206,7 +206,7 @@ As an aside, while exceptions avoid one common error (not checking for failure), Previous to version 2.0 of the CLR, the only parameterized types were arrays. All other containers (such as hash tables, lists, queues, etc.), all operated on a generic Object type. The inability to create List<ElemT>, or Dictionary<KeyT, ValueT> certainly had a negative performance effect because value types needed to be boxed on entry to a collection, and explicit casting was needed on element fetch. Nevertheless, that is not the overriding reason for adding parameterized types to the CLR. The main reason is that **parameterized types make programming easier**. -The reason for this is subtle. The easiest way to see the effect is to imagine what a class library would look like if all types were replaced with a generic Object type. This effect is not unlike what happens in dynamically typed languages like JavaScript. In such a world, there are simply far more ways for a programmer to make incorrect (but type-safe) programs. Is the parameter for that method supposed to be a list? a string? an integer? any of the above? It is no longer obvious from looking at the method's signature. Worse, when a method returns an Object, what other methods can accept it as a parameter? Typical frameworks have hundreds of methods; if they all take parameters of type Object, it becomes very difficult to determine which Object instances are valid for the operations the method will perform. In short, strong typing help a programmer express his intent more clearly, and allows tools (e.g., the compiler) to enforce his intent. This results in big productivity boost. +The reason for this is subtle. The easiest way to see the effect is to imagine what a class library would look like if all types were replaced with a generic Object type. This effect is not unlike what happens in dynamically typed languages like JavaScript. In such a world, there are simply far more ways for a programmer to make incorrect (but type-safe) programs. Is the parameter for that method supposed to be a list? a string? an integer? any of the above? It is no longer obvious from looking at the method's signature. Worse, when a method returns an Object, what other methods can accept it as a parameter? Typical frameworks have hundreds of methods; if they all take parameters of type Object, it becomes very difficult to determine which Object instances are valid for the operations the method will perform. In short, strong typing helps a programmer express his intent more clearly, and allows tools (e.g., the compiler) to enforce his intent. This results in big productivity boost. These benefits do not disappear just because the type gets put into a List or a Dictionary, so clearly parameterized types have value. The only real question is whether parameterized types are best thought of as a language specific feature which is "compiled out" by the time CIL is generated, or whether this feature should have first class support in the runtime. Either implementation is certainly possible. The CLR team chose first class support because without it, parameterized types would be implemented different ways by different languages. This would imply that interoperability would be cumbersome at best. In addition, expressing programmer intent for parameterized types is most valuable _at the interface_ of a class library. If the CLR did not officially support parameterized types, then class libraries could not use them, and an important usability feature would be lost. diff --git a/Documentation/botr/threading.md b/Documentation/botr/threading.md index 2e13d52df3..7c3d950921 100644 --- a/Documentation/botr/threading.md +++ b/Documentation/botr/threading.md @@ -16,7 +16,7 @@ The public Thread interface available to managed code intentionally hides the de - Different operating systems expose different abstractions for native threads. - In principle, managed threads are "virtualized". -The CLR provide equivalent abstractions for managed threads, implemented by the CLR itself. For example, it does not expose the operating system's thread-local storage (TLS) mechanism, but instead provide managed "thread-static" variables. Similarly, it does not expose the native thread's "thread ID," but instead provide a "managed thread ID" which is generated independently of the OS. However, for diagnostic purposes, some details of the underlying native thread may be obtained via types in the System.Diagnostics namespace. +The CLR provides equivalent abstractions for managed threads, implemented by the CLR itself. For example, it does not expose the operating system's thread-local storage (TLS) mechanism, but instead provides managed "thread-static" variables. Similarly, it does not expose the native thread's "thread ID," but instead provides a "managed thread ID" which is generated independently of the OS. However, for diagnostic purposes, some details of the underlying native thread may be obtained via types in the System.Diagnostics namespace. Managed threads require additional functionality typically not needed by native threads. First, managed threads hold GC references on their stacks, so the CLR must be able to enumerate (and possibly modify) these references every time a GC occurs. To do this, the CLR must "suspend" each managed thread (stop it at a point where all of its GC references can be found). Second, when an AppDomain is unloaded, the CLR must ensure that no thread is executing code in that AppDomain. This requires the ability to force a thread to unwind out of that AppDomain. The CLR does this by injecting a ThreadAbortException into such threads. @@ -27,7 +27,7 @@ Every managed thread has an associated Thread object, defined in [threads.h][thr All Thread objects are stored in the ThreadStore (also defined in [threads.h][threads.h]), which is a simple list of all known Thread objects. To enumerate all managed threads, one must first acquire the ThreadStoreLock, then use ThreadStore::GetAllThreadList to enumerate all Thread objects. This list may include managed threads which are not currently assigned to native threads (for example, they may not yet be started, or the native thread may already have exited). -[threads.h]: (https://github.com/dotnet/coreclr/blob/master/src/vm/threads.h) +[threads.h]: ../../src/vm/threads.h Each managed thread that is currently assigned to a native thread is reachable via a native thread-local storage (TLS) slot on that native thread. This allows code that is executing on that native thread to get the corresponding Thread object, via GetThread(). @@ -41,7 +41,7 @@ Thread Lifetimes A managed thread is created in the following situations: 1. Managed code explicitly asks the CLR to create a new thread via System.Threading.Thread. -2. The CLR creates the managed thread directly (see "special threads" below). +2. The CLR creates the managed thread directly (see ["special threads"](#special-threads) below). 3. Native code calls managed code on a native thread which is not yet associated with a managed thread (via "reverse p/invoke" or COM interop). 4. A managed process starts (invoking its Main method on the process' Main thread). @@ -54,9 +54,9 @@ When a native thread dies, the CLR is notified via its DllMain function. This ha Suspension ========== -The CLR must be able to find all references to managed objects in order to perform a GC. Managed code is constantly accessing the GC heap, and manipulating references stored on the stack and in registers. The CLR must ensure that all managed threads are stopped (so they aren't modifying the heap) to safely and reliably find all managed objects. It only stops at _safe point_, when registers and stack locations can be inspected for live references. +The CLR must be able to find all references to managed objects in order to perform a GC. Managed code is constantly accessing the GC heap, and manipulating references stored on the stack and in registers. The CLR must ensure that all managed threads are stopped (so they aren't modifying the heap) to safely and reliably find all managed objects. It only stops at _safe points_, when registers and stack locations can be inspected for live references. -Another way of putting this is that the GC heap, and every thread's stack and register state, is "shared state," accessed by multiple threads. As with most shared state, some sort of "lock" is required to protect it. Managed code must hold this lock while accessing the heap, and can only release the lock at safe points. +Another way of putting this is that the GC heap, and every thread's stack and register state, are "shared state," accessed by multiple threads. As with most shared state, some sort of "lock" is required to protect it. Managed code must hold this lock while accessing the heap, and can only release the lock at safe points. The CLR refers to this "lock" as the thread's "GC mode." A thread which is in "cooperative mode" holds its lock; it must "cooperate" with the GC (by releasing the lock) in order for a GC to proceed. A thread which is in "preemptive" mode does not hold its lock – the GC may proceed "preemptively" because the thread is known to not be accessing the GC heap. @@ -116,7 +116,7 @@ ThreadAbortException is a special type of exception. It can be caught by user co A ThreadAbortException is typically 'thrown' by simply setting a bit on the managed thread marking it as "aborting." This bit is checked by various parts of the CLR (most notably, every return from a p/invoke) and often times setting this bit is all that is needed to get the thread aborted in a timely manner. -However, if the thread is, for example, executing a long-running managed loop, it may never check this bit. To get such a thread to abort faster, the thread i "hijacked" and forced to raise a ThreadAbortException. This hijacking is done in the same way as GC suspension, except that the stubs that the thread is redirected to will cause a ThreadAbortException to be raised, rather than waiting for a GC to complete. +However, if the thread is, for example, executing a long-running managed loop, it may never check this bit. To get such a thread to abort faster, the thread is "hijacked" and forced to raise a ThreadAbortException. This hijacking is done in the same way as GC suspension, except that the stubs that the thread is redirected to will cause a ThreadAbortException to be raised, rather than waiting for a GC to complete. This hijacking means that a ThreadAbortException can be raised at essentially any arbitrary point in managed code. This makes it extremely difficult for managed code to deal successfully with a ThreadAbortException. It is therefore unwise to use this mechanism for any purpose other than AppDomain-Unload, which ensures that any state corrupted by the ThreadAbort will be cleaned up along with the AppDomain. @@ -135,8 +135,8 @@ Sync blocks are stored in the Sync Block Table, and are addressed by sync block The details of object headers and sync blocks are defined in [syncblk.h][syncblk.h]/[.cpp][syncblk.cpp]. -[syncblk.h]: https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.h -[syncblk.cpp]: https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.cpp +[syncblk.h]: ../../src/vm/syncblk.h +[syncblk.cpp]: ../../src/vm/syncblk.cpp If there is room on the object header, Monitor stores the managed thread ID of the thread that currently holds the lock on the object (or zero (0) if no thread holds the lock). Acquiring the lock in this case is a simple matter of spin-waiting until the object header's thread ID is zero, and then atomically setting it to the current thread's managed thread ID. @@ -164,7 +164,7 @@ Native code generally does not manipulate the GC mode directly, but rather uses It is important to understand that GCX\_COOP effectively acquires a lock on the GC heap. No GC may proceed while the thread is in cooperative mode. And native code cannot be "hijacked" as is done for managed code, so the thread will remain in cooperative mode until it explicitly switches back to preemptive mode. -Thus entering cooperative mode in native code is discouraged. In cases where cooperative mode must be entered, it should be kept to as short a time as possible. The thread should not be blocked in this mode, and in particular cannot generally acquire locks safely. +Thus entering cooperative mode in native code is discouraged. In cases where cooperative mode must be entered, it should be kept to as short a time as possible. The thread should not be blocked in this mode, and in particular cannot generally acquire locks safely. Similarly, GCX\_PREEMP potentially _releases_ a lock that had been held by the thread. Great care must be taken to ensure that all GC references are properly protected before entering preemptive mode. |