본문 바로가기

.NET/Debugging

.NET 참조 개체 인스턴스의 SyncBlock 을 확인하는 방법

http://blog.naver.com/techshare/100143525064

위의 글을 퍼온글입니다.

 

사실, 원래는 StringBuilder 에서 OutOfMemoryException 의 원인만 밝히려고 했는데 쓰다 보니 궁금함이 꼬리를 물어서 이렇게 이야기가 길어졌군요. ^^

StringBuilder 에서의 OutOfMemoryException 오류 원인 분석
; http://www.sysnet.pe.kr/2/0/1171

windbg - 힙에서 .NET 타입에 대한 배열을 찾는 방법
; http://www.sysnet.pe.kr/2/0/1172

.NET Array는 왜 12 bytes 의 기본 메모리를 점유할까?
; http://www.sysnet.pe.kr/2/0/1173

일반 참조형의 메모리 소비는 얼마나 될까요?
; http://www.sysnet.pe.kr/2/0/1174

그런데, 여기서 옛날에 읽었던 글이 하나 더 생각나는 바람에... ^^;

Safe Thread Synchronization
; http://msdn.microsoft.com/en-us/magazine/cc188793.aspx

위의 글에 보면, Managed Heap 에서의 개체 메모리에 대한 정의가 다음과 같이 나옵니다.



이에 비춰서 지난 번에 개체들의 메모리를 살펴봤을 때의 덤프 내용을 다시 한번 살펴볼까요?

=== ConsoleApplication1.Program 인스턴스의 메모리 덤프 ===

0:004> dd 026cbf90 
026cbf90  00293810 00000000 00000000 00000000
026cbfa0  00000000 00000000 00000000 00000000

그림과 매치시켜 보면 아래와 같이 정의되는데요.

00293810 == MethodTable Pointer
00000000 == SyncBlock Index
00000000 == Object Fields

물론, 위와 같이 매핑되지 않습니다. 실제로 우리가 테스트해 본 바에 의하면 다음과 같은 의미였기 때문입니다. (아마도, 위의 그림은 단순한 개념도라고 봐야 하지 않을까 싶습니다.)

00293810 == MethodTable Pointer
00000000 == 여분(?)
00000000 == [unknown]

그렇다면 SyncBlock Index 값은 어디에 저장이 되는 것일까요? 이에 대해서는 다른 글에 답이 있었습니다.

Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects
; http://msdn.microsoft.com/en-us/magazine/cc163791.aspx



"MT" 값 이전의 -4 bytes 영역에 저장되어 있다고 표현되어 있습니다. 재미있는 것은, SyncBlock Index의 기본값에 대한 설명도 차이가 납니다.

"Safe Thread Synchronization" 글에서는 다음과 같이 lock 이 걸리지 않은 상태에서는 -1 값으로 채워져 있다는 설명을 포함하고 있는데,

"
On the other hand, ObjectB's SyncBlockIndex field is set to -1 indicating that ObjectB doesn't have a SyncBlock associated with it for its use
"


"Drill Into .NET Framework Internals to See How the CLR Creates Runtime Objects" 글에서는 0 값으로 채워져 있다고 되어 있습니다.

"
For most object instances, there will be no storage allocated for the actual SyncBlock and the syncblk number will be zero
"


어느 쪽이 맞는지, 한번 확인을 해볼까요? ^^

비교를 위해 lock 이 걸린 object 와 그렇지 않았을 때의 메모리 구조를 확인해야 할 텐데요. 분석을 쉽게 하기 위해 다음과 같이 간단하게 코딩을 하고,

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Program[] pgList = new Program[0];
            Thread.Sleep(-1);
        }
    }
}

windbg 로 메모리를 덤프해 보면,

// 이전 글들의 예제에서 많이 포함했으므로, 이전 단계는 생략합니다.

0:000> !do 0228bf90 
Name:        System.Object[]
MethodTable: 72f86ba8
EEClass:     72d09688
Size:        16(0x10) bytes
Array:       Rank 1, Number of elements 0, Type CLASS
Element Type:ConsoleApplication1.Program
Fields:
None

0:000> dd 0228bf8c (0x0228bf90 - 4 == 0228bf8c)
0228bf8c  00000000 72f86ba8 00000000 00163810
0228bf9c  00000000 00000000 00000000 00000000
0228bfac  00000000 00000000 00000000 00000000

덤프된 결과를 각각 다음과 같이 분석할 수 있습니다.

00000000 == SyncBlock Index
72f86ba8 == MethodTable (System.Object [])
00000000 == 배열의 수
00163810 == System.Object[] 에 담긴 요소의 Type에 대한 MethodTable
00000000 == ?

아하... 사용되지 않는 SyncBlock Index의 값은 0 이 맞군요. ^^ (각각의 글이 씌여진 시점에 따라 .NET 의 버전차이라고 의심되었으나, .NET 1.1/2.0/4.0 응용 프로그램에 대해서 동일한 결과값을 얻었습니다.)

본론으로 돌아가서, 이제 해당 object 에 대해 lock 을 걸어서 컴파일 한 다음,

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Program[] pgList = new Program[0];
            lock (pgList)
            {
                Thread.Sleep(-1);
            }
        }
    }
}

windbg 로 메모리를 덤프해 보면,

0:000> !do 026dbf90 
Name:        System.Object[]
MethodTable: 72f86ba8
EEClass:     72d09688
Size:        16(0x10) bytes
Array:       Rank 1, Number of elements 0, Type CLASS
Element Type:ConsoleApplication1.Program
Fields:
None
ThinLock owner 1 (0077e0a8), Recursive 0


0:000> dd 026dbf8c (026dbf90 - 4 == 026dbf8c)
026dbf8c  00000001 72f86ba8 00000000 00283810
026dbf9c  00000000 00000000 00000000 00000000
026dbfac  00000000 00000000 00000000 00000000

이제는 SyncBlock Index 값이 1 로 설정되어 있고 !DumpObject 의 결과에는 "ThinLock owner 1 (0077e0a8), Recursive 0" 이라는 출력결과도 확인할 수 있습니다.

00000001 == SyncBlock Index
72f86ba8 == MethodTable (System.Object [])
00000000 == 배열의 수
00283810 == System.Object[] 에 담긴 요소의 Type에 대한 MethodTable
00000000 == ?




'참조형'의 값은 모두 이렇게 데이터 구조에 "SyncBlock"을 가지고 있는 반면, 스택에 생성되는 '값형식' 에서는 "SyncBlock" 이 존재하지 않습니다.

바로 이 때문에 참조값에 대해서만 닷넷에서 lock 구문(Monitor.Enter 메서드)으로 사용할 수 있는 것입니다.

object lockInstance = new object();

lock (lockInstance) // 이 시점에 SyncBlock Index 값이 설정되고,
{
} // 이 시점에 SyncBlock == 0x00000000 으로 해제됨.

try 
{
    Monitor.Enter(lockInstance); // 이 시점에 SyncBlock Index 값이 설정되고,
} 
finally
{
    Monitor.Exit(lockInstance); // 이 시점에 SyncBlock == 0x00000000 으로 해제됨.
}

int errorLock = 5;

lock (errorLock)  // 컴파일 오류: 'int' is not a reference type as required by the lock statement
{
}

때로는 문서에서만 읽었던 이런 내용들을, 직접 확인해 보는 것도 그런데로 재미가 있군요. ^^


여기서부터 저의 주석 쩝 syncblock에 대한 내용을 내 블로그에서 다뤘었는데 data structure까지는 미처 다루지 못했었습니다. 사실 잘 몰랐구요 참 위의 블로그에서 많은 걸 배웁니다. 껄껄껄 정성태씨 하이팅