성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] VT sequences to "CONOUT$" vs. STD_O...
[정성태] NetCoreDbg is a managed code debugg...
[정성태] Evaluating tail call elimination in...
[정성태] What’s new in System.Text.Json in ....
[정성태] What's new in .NET 9: Cryptography ...
[정성태] 아... 제시해 주신 "https://akrzemi1.wordp...
[정성태] 다시 질문을 정리할 필요가 있을 것 같습니다. 제가 본문에...
[이승준] 완전히 잘못 짚었습니다. 댓글 지우고 싶네요. 검색을 해보...
[정성태] 우선 답글 감사합니다. ^^ 그런데, 사실 저 예제는 (g...
[이승준] 수정이 안되어서... byteArray는 BYTE* 타입입니다...
글쓰기
제목
이름
암호
전자우편
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'>(번역글) .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff</h1> <p> 이번에도 .NET Internals Cookbook 시리즈의 9번째 글을 번역한 것입니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff ; <a target='tab' href='https://blog.adamfurmanek.pl/2019/04/13/net-internals-cookbook-part-9/'>https://blog.adamfurmanek.pl/2019/04/13/net-internals-cookbook-part-9/</a> </pre> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>59. finalizer에서 예외가 발생하거나 무한 루프에 빠진다면?</div> <br /> 다음의 코드로 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 Program { public class Program { public static void Main(string[] args) { Console.WriteLine("Testing Foo"); var foo = new Foo(); foo = null; GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Testing Bar"); var bar = new Bar(); bar = null; GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Done!"); } } } class Foo { ~Foo() { Console.WriteLine("Good destructor"); } } class Bar { <span style='color: blue; font-weight: bold'>~Bar()</span> { Console.WriteLine("Destructor with exception"); <span style='color: blue; font-weight: bold'>throw new Exception();</span> } } /* 출력 결과 Testing Foo Good destructor Testing Bar Destructor with exception Unhandled Exception: System.Exception: Exception of type 'System.Exception' was thrown. at Bar.Finalize() in C:\temp\q59\ConsoleApp1\Program.cs:line 41 */ </pre> <br /> 프로그램이 종료하는 것을 볼 수 있습니다. 이는 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.object.finalize#System_Object_Finalize'>Object.Finalize Method</a> 문서에 다음과 같이 명시되어 있습니다.<br /> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> <span style='color: blue; font-weight: bold'>If Finalize or an override of Finalize throws an exception, and the runtime is not hosted by an application that overrides the default policy, the runtime terminates the process</span> and no active try/finally blocks or finalizers are executed. This behavior ensures process integrity if the finalizer cannot free or destroy resources. </div><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;' > windbg 분석 사례 - 종료자(Finalizer)에서 예외가 발생한 경우 비정상 종료(Crash) 발생 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11732'>http://www.sysnet.pe.kr/2/0/11732</a> </pre> <br /> 그렇다면, 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 Program { public class Program { public static void Main(string[] args) { Console.WriteLine("Testing Foo"); var foo = new Foo(); foo = null; GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Testing Bar"); var bar = new Bar(); bar = null; GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine("Done!"); } } } class Foo { ~Foo() { Console.WriteLine("Good destructor"); } } class Bar { <span style='color: blue; font-weight: bold'>~Bar()</span> { Console.WriteLine("Destructor with infinite loop"); <span style='color: blue; font-weight: bold'>while (true) { }</span> } } /* 출력 결과 Testing Foo Good destructor Testing Bar Destructor with infinite loop */ </pre> <br /> 표면 상으로는 문제가 없는 듯하지만, 실제로는 프로그램이 진행되면서 finalizer를 구현한 객체들이 누적되면서 OOM 예외가 발생할 가능성이 높아집니다. 이에 대해서는 다음의 글에서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11803'>http://www.sysnet.pe.kr/2/0/11803</a> windbg - 닷넷 응용 프로그램의 메모리 누수 분석 ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11808'>http://www.sysnet.pe.kr/2/0/11808</a> </pre> <br /> windbg의 finalizequeue 명령어로 확인할 수 있다고 설명했습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>60. What is finalization queue? What is f-reachable queue?</div> <br /> 종료 큐(finalization queue)와 F-reachable 큐에 대해서는 제 책의 "5.4.2.6 소멸자" 절을 참고하세요. ^^<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>61. 객체를 pinnging 한다는 의미</div> <br /> GC Heap에 있는 객체를 외부(Native) 코드에 전달했다고 가정해 봅니다. 그럼 외부에서 그 객체를 사용하는 동안 GC가 발동할 수도 있고 그렇게 되면 해당 객체의 위치가 바뀌게 됩니다. 하지만 외부 코드는 바뀐 객체에 대한 포인터를 모르므로 엉뚱한 위치의 데이터를 접근할 수 있습니다.<br /> <br /> 따라서 이 문제를 해결하기 위해 GC Heap에 있는 객체를 "움직이지 못하는 상태"로 고정시키는 것을 pinning이라고 합니다. 즉, 외부 코드에 GC Heap의 객체를 전달하려면 반드시 pinning시켜야만 합니다. pinning을 명시적으로 하려면 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/fixed-statement'>fixed 예약어</a>나 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.gchandle.alloc#System_Runtime_InteropServices_GCHandle_Alloc_System_Object_'>GC.Alloc 메서드</a>를 사용할 수 있습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>62. P/Inovke 호출은 인자를 pinning 할까?</div> <br /> P/Invoke 메서드가 호출되는 동안, 해당 메서드에 전달한 인자들은 자동으로 pinning 됩니다. 따라서 P/Invoke 메서드가 비동기 호출이라면 메서드 자체의 호출 이후에는 pinning이 풀리므로 비동기 작업이 완료되었을 때 문제가 발생할 수 있으므로 그런 경우에는 직접 pinning을 해야 합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>63. 쓰기 장벽(write barrier)이란?</div> <br /> 이 용어에 대한 자세한 설명은 다음 질문으로 넘기고 일단 쓰기 장벽이 실제로 사용되고 있는 사례를 검토해 보겠습니다. 예를 들어 다음의 코드는 Bar 객체를 Foo 객체가 포함하고 있는 상황입니다.<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 Program { public class Program { public static void Main(string[] args) { var foo = new Foo(); GC.Collect(); Console.WriteLine(GC.GetGeneration(foo)); <span style='color: blue; font-weight: bold'>foo.Bar = new Bar();</span> Console.ReadLine(); } } } <span style='color: blue; font-weight: bold'>class Foo</span> { <span style='color: blue; font-weight: bold'>public Bar Bar;</span> } class Bar { } </pre> <br /> 빌드하고 Main 메서드의 IL 코드를 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // ...[생략]... .method public hidebysig static void Main(string[] args) cil managed { // .maxstack 2 .locals init ([0] class Foo foo) .language '{3F5162F8-07C6-11D3-9053-00C04FA302A1}', '{994B45C4-E6E9-11D2-903F-00C04FA302A1}', '{5A869D0B-6611-11D3-BD2A-0000F80849BD}' IL_0000: nop IL_0001: newobj instance void Foo::.ctor() IL_0006: stloc.0 IL_0007: call void [mscorlib]System.GC::Collect() IL_000c: nop IL_000d: ldloc.0 IL_000e: call int32 [mscorlib]System.GC::GetGeneration(object) IL_0013: call void [mscorlib]System.Console::WriteLine(int32) IL_0018: nop <span style='color: blue; font-weight: bold'>IL_0019: ldloc.0 IL_001a: newobj instance void Bar::.ctor() IL_001f: stfld class Bar Foo::Bar</span> IL_0024: call string [mscorlib]System.Console::ReadLine() IL_0029: pop .line 15,15 : 9,10 '' IL_002a: ret } // end of method Program::Main // ...[생략]... </pre> <br /> "foo.Bar = new Bar();" 코드에 특이점이 없습니다. 하지만 이 코드를 실행 시 JIT 컴파일된 기계어로 보면 다음과 같이 clr!JIT_WriteBarrierEAX 함수가 호출되는 것을 볼 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\Users\adafurma\source\repos\ConsoleApp3\ConsoleApp3\Program.cs @ 13: 007a04a9 b9184e5d00 mov ecx,5D4E18h (MT: Bar) 007a04ae e8152ce2ff call 005c30c8 (JitHelp: CORINFO_HELP_NEWSFAST) 007a04b3 8945ec mov dword ptr [ebp-14h],eax 007a04b6 8b4dec mov ecx,dword ptr [ebp-14h] 007a04b9 ff15384e5d00 call dword ptr ds:[5D4E38h] (Bar..ctor(), mdToken: 06000002) 007a04bf 8b55f4 mov edx,dword ptr [ebp-0Ch] 007a04c2 8b45ec mov eax,dword ptr [ebp-14h] 007a04c5 8d5204 lea edx,[edx+4] <span style='color: blue; font-weight: bold'>007a04c8 e8b3e2d872 call clr!JIT_WriteBarrierEAX (7352e780)</span> C:\Users\adafurma\source\repos\ConsoleApp3\ConsoleApp3\Program.cs @ 14: 007a04cd e82a7e4372 call mscorlib_ni+0xb582fc (72bd82fc) (System.Console.ReadLine(), mdToken: 06000b65) 007a04d2 8945e8 mov dword ptr [ebp-18h],eax 007a04d5 90 nop </pre> <br /> CLR은 왜? JIT_WriteBarrierEAX 코드를 넣은 걸까요? 이어지는 다음의 질문에서 알아봅니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> <br /><a name="tag64" /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>64. Card 테이블? Brick 테이블?</div> <br /> 이전 글에서 "Bar 객체를 Foo 객체가 포함"하고 있는 상황이라고 했는데 예전에 제가 썼던 다음의 글에서 이와 관련해 card table을 설명했었습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > .NET GC - 하위 세대의 객체를 포함하는 상위 세대의 참조를 추적하기 위한 card-table ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/1670'>http://www.sysnet.pe.kr/2/0/1670</a> </pre> <br /> 즉, 하위 세대의 객체(Bar)를 포함하는 상위 세대의 참조(Foo)를 추적하기 위해 card table이 존재하고, 이때 "foo.Bar = new Bar();"와 같이 대입이 될 때 card table을 업데이트하기 위한 "Write Barrier" 코드를 수행해야 하는 것입니다.<br /> <br /> 그런데 개인적으로 의문이 드는 것이 있다면, 보통 Barrier에 대해 write/read가 사용되면 CLR에 적용된 메모리 모델에 따른 용어(Memory Barrier)이고 이것은 CPU가 명령어 실행을 최적화하는 과정에 임의로 순서를 바꾸지 못하도록 하는 역할을 합니다. 따라서 JIT_WriteBarrierEAX보다는 JIT_UpdateCardTable이라는 용어가 더 적합했을 텐데 왜? WriteBarrier라는 용어를 굳이 사용했느냐입니다.<br /> <br /> 어쩌면 GC와의 연동에서 Memory barrier의 역할도 해야 하기 때문에 그런 걸 수도 있지만... 현재 제 수준으로는 딱히 단정 지을 수가 없군요. ^^<br /> <br /> 참고로, Brick 테이블은 interior pointer의 참조를 기록하는 용도이며 Card 테이블이 128(x86), 256(x64)의 크기로 유지되는 반면 Brick 테이블은 2kB(x86), 4kB(x64)라는 차이가 있습니다.<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;' > Pro .NET Memory Management ; <a target='tab' href='https://prodotnetmemory.com/'>https://prodotnetmemory.com/</a> </pre> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1443&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </h1><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2607
(왼쪽의 숫자를 입력해야 합니다.)