성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>windbg로 확인하는 Finalizer를 가진 객체의 GC 과정</h1> <p> 객체가 GC되었는지 확인하는 방법을 알아봤는데요.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg로 확인하는 객체의 GC 여부 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11509'>http://www.sysnet.pe.kr/2/0/11509</a> </pre> <br /> 그럼, Finalizer를 가진 객체로도 테스트를 해보겠습니다. 이를 위해 지난 예제의 Program 클래스에 Finalizer를 추가한 후,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; namespace ConsoleApp2 { class Program { static void Main(string[] args) { Instance(); Console.ReadLine(); // .NET 4 + x64 + Release 모드로 실행 후, // 이 시점에 Dump를 뜨고, GC.Collect(2, GCCollectionMode.Forced); // 엔터를 치면 GC가 수행되고, // 이 시점에 다시 Dump를 뜸 Console.ReadLine(); } private static void Instance() { Program pg = new Program(); } <span style='color: blue; font-weight: bold'>~Program() { Console.WriteLine("GCed"); }</span> } } </pre> <br /> GC.Collect 수행 전/후에 따라 덤프를 뜹니다. 우선 GC.Collect를 수행하기 전의 덤프를 분석하면 지난 글에서 설명한 데로 Program 객체는 0 세대 힙에 놓인 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>.loadby sos clr</span> 0:000> <span style='color: blue; font-weight: bold'>!dumpheap -type ConsoleApp2.Program</span> Address MT Size <span style='color: blue; font-weight: bold'>0000018414812df0</span> 00007ff8a7185a28 24 Statistics: MT Count TotalSize Class Name 00007ff8a7185a28 1 24 ConsoleApp2.Program Total 1 objects 0:000> <span style='color: blue; font-weight: bold'>!eeheap -gc</span> Number of GC Heaps: 1 <span style='color: blue; font-weight: bold'>generation 0 starts at 0x0000018414811030</span> generation 1 starts at 0x0000018414811018 generation 2 starts at 0x0000018414811000 ephemeral segment allocation context: none segment begin allocated size 0000018414810000 0000018414811000 0000018414817fe8 0x6fe8(28648) Large object heap starts at 0x0000018424811000 segment begin allocated size 0000018424810000 0000018424811000 00000184248199e8 0x89e8(35304) Total Size: Size: 0xf9d0 (63952) bytes. ------------------------------ GC Heap Size: Size: 0xf9d0 (63952) bytes. </pre> <br /> 이 상태에서 <a target='tab' href='http://blogs.microsoft.co.il/sasha/2012/02/25/finalization-queue-or-f-reachable-queue-find-out-with-sos/'>finalizequeue</a> 명령을 실행하면 다음과 같은 결과를 얻을 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!finalizequeue</span> SyncBlocks to be cleaned up: 0 Free-Threaded Interfaces to be released: 0 MTA Interfaces to be released: 0 STA Interfaces to be released: 0 ---------------------------------- generation 0 has <span style='color: blue; font-weight: bold'>9 finalizable objects</span> (0000018412b5a610->0000018412b5a658) generation 1 has 0 finalizable objects (0000018412b5a610->0000018412b5a610) generation 2 has 0 finalizable objects (0000018412b5a610->0000018412b5a610) Ready for finalization 0 objects (0000018412b5a658->0000018412b5a658) Statistics for all finalizable objects (including all objects ready for finalization): MT Count TotalSize Class Name 00007ff902a13f60 1 24 System.WeakReference <span style='color: blue; font-weight: bold'>00007ff8a7185a28 1 24 ConsoleApp2.Program</span> 00007ff902a31a38 1 32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle 00007ff902a319a8 1 32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle 00007ff9029fc3e0 1 32 Microsoft.Win32.SafeHandles.SafePEFileHandle 00007ff9029fd680 1 64 System.Threading.ReaderWriterLock 00007ff9029fa3f0 2 64 Microsoft.Win32.SafeHandles.SafeFileHandle 00007ff9029f7f28 1 96 System.Threading.Thread Total 9 objects </pre> <br /> 이 출력 결과에서 아래의 세 라인이 바로 소멸자 큐(Finalization queue)의 내용이고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > generation 0 has 9 finalizable objects (0000018412b5a610->0000018412b5a658) generation 1 has 0 finalizable objects (0000018412b5a610->0000018412b5a610) generation 2 has 0 finalizable objects (0000018412b5a610->0000018412b5a610) </pre> <br /> 그다음 라인의 내용이 FReachable Queue입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Ready for finalization 0 objects (0000018412b5a658->0000018412b5a658) </pre> <br /> 즉, 현재 소멸자를 가진 클래스의 인스턴스가 9개 생성된 상태이고 해당 객체들은 모두 0 세대 힙에 있습니다.<br /> <br /> 참고로, dumpheap으로 구한 객체의 주소로 gcroot 명령어를 이용하면 Finalization queue에도 root 참조가 있는지 곧바로 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!gcroot 0000018414812df0</span> <span style='color: blue; font-weight: bold'>Finalizer Queue:</span> 0000018414812df0 <span style='color: blue; font-weight: bold'>-> 0000018414812df0 ConsoleApp2.Program</span> </pre> <br /> <hr style='width: 50%' /><br /> <br /> 그다음 GC.Collect를 수행한 후의 덤프를 분석하면, 이번에는 Program 객체가 GC되지 않고 1 세대 힙에 놓인 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!dumpheap -type ConsoleApp2.Program</span> Address MT Size <span style='color: blue; font-weight: bold'>0000018414812df0</span> 00007ff8a7185a28 24 Statistics: MT Count TotalSize Class Name 00007ff8a7185a28 1 24 ConsoleApp2.Program Total 1 objects 0:000> <span style='color: blue; font-weight: bold'>!eeheap -gc</span> Number of GC Heaps: 1 <span style='color: blue; font-weight: bold'>generation 0 starts at 0x0000018414816c88 generation 1 starts at 0x0000018414811018</span> generation 2 starts at 0x0000018414811000 ephemeral segment allocation context: none segment begin allocated size 0000018414810000 0000018414811000 0000018414818ca0 0x7ca0(31904) Large object heap starts at 0x0000018424811000 segment begin allocated size 0000018424810000 0000018424811000 00000184248199e8 0x89e8(35304) Total Size: Size: 0x10688 (67208) bytes. ------------------------------ GC Heap Size: Size: 0x10688 (67208) bytes. </pre> <br /> 즉, Finalizer를 구현한 객체는 소멸자 큐에 등록된 것으로 인해 여전히 root 참조가 살아 있으므로 GC.Collect 이후 곧바로 GC되지 않고 1 세대 힙으로 승격하게 됩니다. (물론 일반적인 객체였다면 이 과정에서 root 참조가 없기 때문에 정리가 됩니다.)<br /> <br /> 또한 1 세대 힙으로 승격함과 동시에 소멸자 큐의 root 참조가 없어지고 FReachable Queue로 이동하게 됩니다. 그래서 첫 번째 GC.Collect 이후에는 다음과 같이 소멸자 큐의 등록이 없어진 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!gcroot 0000018414812df0</span> Found 0 unique roots (run '!GCRoot -all' to see all roots). </pre> <br /> <hr style='width: 50%' /><br /> <br /> 소멸자의 동작과 관련해 "소멸자 큐(finalization queue)"와 "FReachable Queue"가 있습니다. <a target='tab' href='http://www.yes24.com/24/goods/48156128?scode=032'>제 책에서도 설명</a>했지만, GC.Collect 시에 소멸자 큐에서 나온 객체는 FReachable Queue로 이동하고 이 시점에 대기하고 있던 소멸자 스레드가 FReachable Queue에 있는 항목을 곧바로 꺼내 소멸자 메서드를 호출합니다.<br /> <br /> 그래서 FReachable Queue에 객체가 있는 경우를 확인하기가 (일반적인 응용 프로그램의 경우) 쉽지 않습니다.<br /> <br /> 물론 억지로 ^^ 재현하면 쉽게 확인할 수 있습니다. 가령, 다음과 같이 소멸자를 갖는 객체를 하나 더 추가하는 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > using System; using System.Threading; namespace ConsoleApp2 { class Program { static void Main(string[] args) { Instance(); Console.ReadLine(); <span style='color: blue; font-weight: bold'>GC.Collect(2, GCCollectionMode.Forced);</span> Console.ReadLine(); } private static void Instance() { <span style='color: blue; font-weight: bold'>Program pg = new Program(); LongDtor ld = new LongDtor();</span> } <span style='color: blue; font-weight: bold'>~Program() { Thread.Sleep(1000 * 60); Console.WriteLine("Program.GCed"); }</span> } class LongDtor { <span style='color: blue; font-weight: bold'>~LongDtor() { Thread.Sleep(1000 * 60); Console.WriteLine("LongDtor.GCed"); }</span> } } </pre> <br /> 위와 같이 하면, GC.Collect 이후 2개의 객체(pg, ld)가 소멸자 큐에서 FReachable 큐로 이동할 것입니다. 이동하자마자 소멸자 스레드에 의해 꺼내져 dtor를 실행하게 될 텐데, 어느 쪽의 객체를 먼저 꺼내든 60초의 시간 동안 그다음 객체를 꺼낼 수 없으므로 적어도 1개의 객체가 FReachable 큐에 (60초 동안은) 머물게 됩니다.<br /> <br /> 따라서 첫 번째 GC.Collect 호출 후 덤프를 떠서 finalizequeue 명령으로 확인하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>!finalizequeue</span> SyncBlocks to be cleaned up: 0 Free-Threaded Interfaces to be released: 0 MTA Interfaces to be released: 0 STA Interfaces to be released: 0 ---------------------------------- generation 0 has 0 finalizable objects (000001f15f1bc4d0->000001f15f1bc4d0) generation 1 has 8 finalizable objects (000001f15f1bc490->000001f15f1bc4d0) generation 2 has 0 finalizable objects (000001f15f1bc490->000001f15f1bc490) Ready for finalization 1 objects (000001f15f1bc4d0->000001f15f1bc4d8) Statistics for all finalizable objects (including all objects ready for finalization): MT Count TotalSize Class Name 00007ff902a13f60 1 24 System.WeakReference 00007ff8a7175a30 1 24 ConsoleApp2.Program 00007ff902a31a38 1 32 Microsoft.Win32.SafeHandles.SafeFileMappingHandle 00007ff902a319a8 1 32 Microsoft.Win32.SafeHandles.SafeViewOfFileHandle 00007ff9029fc3e0 1 32 Microsoft.Win32.SafeHandles.SafePEFileHandle 00007ff9029fd680 1 64 System.Threading.ReaderWriterLock 00007ff9029fa3f0 2 64 Microsoft.Win32.SafeHandles.SafeFileHandle 00007ff9029f7f28 1 96 System.Threading.Thread Total 9 objects </pre> <br /> GC.Collect로 인해 0 세대 힙에 있던 소멸자를 가진 10개의 객체 중 8개가 소멸자 큐(Finalization queue)에 머물러 있고,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > generation 0 has 0 finalizable objects (000001f15f1bc4d0->000001f15f1bc4d0) generation 1 has <span style='color: blue; font-weight: bold'>8 finalizable objects</span> (000001f15f1bc490->000001f15f1bc4d0) generation 2 has 0 finalizable objects (000001f15f1bc490->000001f15f1bc490) </pre> <br /> 2개의 객체는 Finalization queue에서 꺼내져 Freachable Queue에 들어갔습니다. <br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Ready for <span style='color: blue; font-weight: bold'>finalization 1 objects</span> (000001f15f1bc4d0->000001f15f1bc4d8) </pre> <br /> 만약 소멸자 스레드에 의해 꺼내지지 않았다면 위의 출력 결과는 "2 objects"로 나오겠지만, 거의 곧바로 실행되기 때문에 Freachable Queue에 들어간 2개의 객체 중 한 개는 꺼내져서 소멸자 스레드에 의해 dtor가 호출 중이며 나머지 한 개는 아직 Freachable Queue에 머물러 있는 것입니다.<br /> <br /> 실제로 Freachable Queue에서 남아 있는 1개의 객체를 다음과 같이 덤프할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 0:000> <span style='color: blue; font-weight: bold'>dq 000001f15f1bc4d0 L1</span> 000001f1`5f1bc4d0 <span style='color: blue; font-weight: bold'>00000184`14812df0</span> 0:000> <span style='color: blue; font-weight: bold'>!do 00000184`14812df0</span> Name: ConsoleApp2.Program MethodTable: 00007ff8a7185a28 EEClass: 00007ff8a71824f8 Size: 24(0x18) bytes File: E:\ConsoleApp2\bin\Release\ConsoleApp2.exe Fields: None </pre> <br /> 따라서 현재 소멸자 스레드에 의해 꺼내져 실행되고 있는 객체는 ConsoleApp2.LongDtor 클래스의 인스턴스임을 예상할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이번 테스트를 통해 제가 그동안 잘못 알고 있던 것을 바로잡을 수 있게 되었습니다. 예전에는 다음과 같은 절차로 소멸자를 가진 객체가 없어진다고 알고 있었는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 1. dtor를 가진 객체 생성 1.1 GC Heap 0 세대에 객체가 등록됨 [객체가 scope을 벗어남] 2. 첫 번째 GC 수행 시 root 참조들이 없지만 dtor를 가지므로 GC heap에서 Finalization queue로 이동 3. 두 번째 GC 수행 시 Finalization queue에서 Freachable Queue로 이동하고, 그 순간 소멸자 스레드에 의해 실행 </pre> <br /> 실제로 테스트해본 결과 다음과 같이 진행되고 있었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 1. dtor를 가진 객체 생성 1.1 GC Heap 0 세대에 객체가 등록됨 1.2 소멸자 큐(Finalization queue)에도 객체가 등록됨 [객체가 scope을 벗어남] 2. 첫 번째 GC 수행 시 2.1 소멸자 큐에 root 참조가 있으므로 1 세대로 승격 2.2 소멸자 큐의 root 참조를 비우고 Freachable Queue로 이동 2.3 Freachable Queue로 이동하자마자 소멸자 스레드가 꺼내 실행 [따라서 이 시점에는 GC Heap 1 세대에만 참조가 있음] 3. 두 번째 GC 수행 시 GC Heap 1 세대의 객체가 정리됨 </pre> <br /> 음... 책 내용을 수정해야겠군요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그렇다면 WeakReference는 Finalizer를 가진 객체에 대해 언제 IsAlive 값이 False가 될까요? 얼핏 생각해 보면, 첫 번째 GC 수행 후에도 여전히 GC Heap 1 세대에 살아있으므로 그 시점까지는 IsAlive 값이 True로 유지될 듯싶었는데요. 테스트 결과 GC Heap 1 세대에 있음에도 불구하고 무조건 IsAlive = False로 되는 것을 확인할 수 있습니다.<br /> <br /> 이러한 시점 상의 문제로 WeakReference는 해당 객체가 실제로 GC되지 않아도 참조를 잃어버리게 됩니다. 예를 들어 (권장되지 않지만) 소멸자에서 해당 객체를 다시 부활(resurrection) 시키는 경우도 있을 텐데, WeakReference는 이런 상황을 고려하지 않습니다.<br /> <br /> (<a target='tab' href='http://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1249&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1527
(왼쪽의 숫자를 입력해야 합니다.)