Introduction
When
observing .Net Reference Type - Memory Layout, we'll notice that each reference
type contains additional overhead – the following 2 fields:
1.
Object
Header Word (a.k.a Sync Block Index - SBI)
2.
Type
Object Pointer -TOP (a.k.a Method Table Pointer - MT)
Remark:
In '.Net Type
Internals' article I elaborated on 'Reference Type' and 'Value
Type' memory layout and extensively explored additional .Net Type Internals
topics, such as: the Method Table internal CLR structure, with relevant
examples.
Sync Bloc Index
The
Sync Bloc Index (a.k.a Object Header Word) field is being used for several
different purposes:
1. Thread
Synchronization – using
the CLR Monitor Mechanism with the lock
keyword.
2. Storing the
object's Hash Code
– used in a hash-based collection, such as: Dictionary.
3. Garbage Collector
algorithm – used
in Mark phase.
4. Garbage Collector
Finalization mechanism.
In
addition to the data above, the CLR uses few bits in the Sync Block Index
field, in order to determine which information of these options is currently
stored in it.
In
this post I'll use simple code example to demonstrate the changes in the Sync
Block Index field, when implementing the first 2 options:
-
Thread Synchronization using the lock keyword.
-
Store the object's Hash Code when used in a hash-based
collection.
Remark:
Additional
detailed information could be found in the .Net Garbage Collection
article I'll post shortly.
In this
article I elaborated on the Garbage Collector's algorithm, different phases
(Mark, Sweep & Compact), GC Roots, GC Handles, GC Generations, LOH, GC
Modes, GC Finalization mechanism, the Dispose patterns, and more.
Example 1 – Thread Synchronization
1. Create a simple class Person:
In order
to present the SBI different values in some of the scenarios we'll examine, I
wrote the following 'Person' class:
public class Person
{
public int Id { get; set; }
}
|
Person instance memory layout
The
following image describes a person instance layout in the Managed Heap. (In
a 32bit system)
When we create an instance of an object, we are creating a reference to that object, which points to the beginning of the Type Object Pointer (a.k.a MT Pointer), whereas the Object Word Header (a.k.a Sync Block Index) is laid in a negative offset from that object.
2. Create a method with thread synchronization:
Next,
I'll create a method that uses the lock
keyword for thread synchronization:
private static void
SyncBlockIndexUsingLockExample()
{
Person person = new Person {Id = 4};
lock (person)
{
// Thread
synchronization code...
}
}
|
Remark: Normally, I wouldn't create a 'Person' object to lock
a code of block; I'll create a simple object. But for this example, I'd like to
present the Sync Block Index, and it would be much easier to present it using
the 'Person' object.
3. Debugging in Visual Studio:
I'll demonstrate this using the
Visual Studio different debug windows:
-
Disassembly window – to show the assembly calls and the relevant
registers.
-
Registers window – to retrieve the relevant memory addresses.
-
Memory window
– to show the actual values stored in the managed heap memory.
-
Immediate window – to use the SOS DLL, to perform debugging and
present the MT and other CLR data structures.
3.1 Disassembly window
When debugging the code prior to creating the person
instance, we could see from the 'Disassembly window' that the compiler is using
the ECX register to store the 'person' new instance location in the
Managed Heap.
3.2 Registers window & Memory window
So I copied the
ECX register value to the address textbox in the memory window:
After I copied the ECX register value to the
'Memory1 window', we could see our entire Person object.
We could see its:
-
Sync Block index Field: currently its value is zero.
-
Method Table Pointer address: 001a3424
-
Id Filed: we
set its value to 4.
Remark: MT Pointer & SBI addresses
As described above, we could see that the Person's
address - 0x01C69424 points to the beginning of the object, meaning to
the Method Table Pointer filed, and the Sync Block Index field reside
in a negative offset from it. (-4 Bytes in a 32bit system, thus the SBI address
is: 0x01C69420).
Next, we'll continue debugging and enter the locked
block of code.
Now we could see that the value of the Sync Block
Index filed was changed to: 00000001.
To conclude the example
up to now:
We managed to
demonstrate that when using the lock
keyword for thread synchronization, the CLR uses the Sync Block Index
field (of the locking object) and store relevant data for its own use; this is
part of the CLR Monitor Mechanism.
CLR Monitor Mechanism
The lock
keyword is actually a 'syntactic sugar' for the Monitor.Enter() and
Monitor.Exit() methods that are the implementation of the CLR Monitor
Mechanism, to control the thread synchronization.
Meaning, the compiler translate the lock keyword code to:
In
.Net Framework 3.5 - mscorlib.dll, v2.0.0.0
|
private static void
SyncBlockIndexUsingLockExample_3()
{
Person person = new Person { Id = 4 };
try
{
Monitor.Enter(person);
// Thread synchronization code...
}
finally
{
Monitor.Exit(person);
}
}
|
In
.Net Framework 4.6.1 - mscorlib.dll, v4.0.0.0
|
{
Person person = new Person { Id = 4 };
bool lockTaken = false;
try
{
Monitor.Enter(person, ref lockTaken);
// Thread synchronization code...
}
finally
{
if (lockTaken) Monitor.Exit(person);
}
}
|
If we'll observe the MSIL code of our method, we could
see the following:
3.3 Immediate window
In the
immediate window I'll use the SOS.DLL extension to present all of the above
(and more), by debugging our session and by examining the relevant Objects,
Method Tables, the Sync Block Index and other relevant CLR data.
SOS.DLL
– Son of Strike
The SOS
DLL is a debugger extension, provided as part of the .Net Framework (%windir%\Microsoft.NET\Framework\v4.0.30319), and could be used to perform
debugging on .Net processes, and to display relevant information on the CLR
internals.
Normally,
I would use it in the WinDbg tool (provided as part of the Debugging
Tools for Windows tool set), but for this example I'll use the
SOS.DLL inside Visual Studio in order to present the relevant information.
Remarks:
-
I'll add other posts to demonstrate how to use the
SOS extension in visual studio and in WinDbg, for different examples with
relevant commands.
-
There are additional debugger extensions that I'll
probably describe in other debugging posts, such as: sosex.dll,
psscor2.dll, psscor4.dll, ClrMD.
|
I've performed the following commands:
1. Load the SOS
DLL:
-
.load sos.dll
-
Output snippet:
.load sos.dll
extension
C:\Windows\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded
|
2. Review the
SyncBlock
-
!SyncBlk
-
Output snippet:
!SyncBlk
Index SyncBlock MonitorHeld Recursion Owning Thread
Info SyncBlock Owner
-----------------------------
Total 0
CCW 0
RCW 0
ComClassFactory 0
Free 0
|
Remark: Thin Lock
As we could observe in the output above the sync block
table is empty, this is since the CLR optimizes the Monitor Mechanism, and uses
a 'thin lock'
The Thin Lock mechanism was introduced in CLR
2.0, in order to improve performance.
Simply put, the CLR creates the Sync Block lazily,
only if there are other threads that need to execute the same code block, otherwise,
the CLR uses a thin lock to manage the synchronization.
In such cases the '!SyncBlk' command can’t present
thin locks, instead we could use the '!DumpHeap –thinlock' command.
3. Find Thin
Locks
-
!DumpHeap –thinlock
-
Output snippet:
!DumpHeap –thinlock
Address MT
Size
01c69424
001a3424 12 ThinLock owner 1 (004a03b8) Recursive 0
|
4. The object:
-
!DumpObj 01c69424
-
Output snippet:
!DumpObj 01c69424
Name: TestPerfExample.Person
MethodTable: 001a3424
EEClass: 001a191c
Size: 12(0xc) bytes
(C:...\TestPerfExample\bin\Debug\TestPerfExample.exe)
Fields:
MT Field
Offset Type
VT Attr Value Name
67042f74 4000001 4 System.Int32 1 instance 4
ThinLock
owner 1 (004a03b8), Recursive 0
|
---------------------------------------------------------------------------------------------------------------------------
Example 2 – Object's Hash Code
1. Create a method with a Dictionary:
In this
example, I'll create a method that uses a Dictionary (hash-based collection) to
present the changes in the Sync Block Index, while using the 'Person' object as
a key:
private static void
SyncBlockIndexUsingDictionaryExample()
{
Dictionary<Person, string> dictionary = new Dictionary<Person, string>();
Person person = new Person() { Id = 4 };
dictionary.Add(person,
"Demonstrating Sync Block
Index");
}
|
Debug
the method in 2 phases:
1.1 Create the 'Person' object
-
Disassembly window: We could see that the compiler uses the ECX Register
to store the 'person' object in the Managed (GC) Heap memory.
-
Registers window: I copied the ECX register value to the Memory 1
window.
-
Memory 1 window: We could observe the 'person' object as it appears in
the Managed Heap.
-
Currently the Sync Block Index field is all zeros,
since we hadn't added it to the Dictionary yet.
1.2 Add the 'Person' object to the dictionary
We could
clearly see that after adding the 'person' to the dictionary, its Sync Block
Field was modified accordingly!
Remark:
As already
mentioned, since the SBI has multiple purposes and could store different values,
the CLR adds the SBI additional bit(s) in order to distinguish which kind of
information is currently stored in it.
Thus, the
value appeared above is a combination of the GetHashCode() returned value with this bit(s).
Summary
In
this post we explored the different purposes of the Sync Block Index field declared
for every object we create.
In
addition I demonstrated how the CLR uses the Sync Block Index for:
1. Thread synchronization.
2. Storing the
object's hash code.
The End
Hope you enjoyed!
Appreciate your
comments…
Yonatan Fedaeli
1 comment:
Greatest!!! I am looking for this al the day and finally i found it!!! One millon oh thanks for you!!!
Post a Comment