성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
[정성태] 만드실 수 있습니다. 단지, Unity 엔진 내의 스크립트와 W...
글쓰기
제목
이름
암호
전자우편
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 2 - GC-related things</h1> <p> 이번에도 .NET Internals Cookbook 시리즈의 2번째 글을 번역한 것입니다.<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 2 - GC-related things ; <a target='tab' href='https://blog.adamfurmanek.pl/2019/02/23/net-internals-cookbook-part-2/'>https://blog.adamfurmanek.pl/2019/02/23/net-internals-cookbook-part-2/</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'>7. LOH(Large Object Heap)의 Compaction 방법?</div> <br /> GC의 동작 원리를 모르는 분들에게는 Compaction을 압축이라고 번역하면 오해가 생길 듯하고, 축소라고 번역해도 왠지 Heap의 크기 자체를 축소한다는 것으로 또 오해가 생길 것 같은데요. 그런 분들을 위해 다음의 글 3개를 먼저 읽어보실 것을 권합니다. ^^<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 닷넷 가비지 컬렉션 다시 보기 - Part I 기본 작동 방식 ; <a target='tab' href='http://www.simpleisbest.net/post/2011/04/01/Review-NET-Garbage-Collection.aspx'>http://www.simpleisbest.net/post/2011/04/01/Review-NET-Garbage-Collection.aspx</a> 닷넷 가비지 컬렉션 다시 보기 - Part II 세대별 가비지 콜렉션 ; <a target='tab' href='http://www.simpleisbest.net/post/2011/04/05/Generational-Garbage-Collection.aspx'>http://www.simpleisbest.net/post/2011/04/05/Generational-Garbage-Collection.aspx</a> 닷넷 가비지 컬렉션 다시 보기 - Part III LOH ; <a target='tab' href='http://www.simpleisbest.net/post/2011/04/11/Large-Object-Heap-Intro.aspx'>http://www.simpleisbest.net/post/2011/04/11/Large-Object-Heap-Intro.aspx</a> </pre> <br /> 위의 글을 읽었다는 가정으로, GC가 사용하지 않는 객체 공간을 살아 있는 객체들로 이동시켜 조밀하게 만드는 작업을 "축소"라고 설명할 수 있습니다. 축소 작업은 원래 GC 2세대 힙까지만 적용될 뿐 LOH에는 성능상의 문제로 하지 않았었습니다. 그러다 .NET 4.5.1부터 <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.runtime.gcsettings.largeobjectheapcompactionmode?view=netframework-4.7.2'>GCSettings.LargeObjectHeapCompactionMode</a> 속성을 CompactOnce로 설정하면 다음번 발생하는 Full GC 때 LOH를 축소하는 기능이 생긴 것입니다. 참고로 이 옵션은 기본적으로 비활성 상태이기 때문에 4.5.1에서도 명시적인 설정을 하지 않는 한 동작하지 않습니다.<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'>8. LOH에 할당되는 객체는?</div> <br /> 일반적으로 LOH에 할당되는 객체의 기준은 85,000 바이트 크기라고 알려져 있습니다. (그리고 그 크기는 향후 얼마든지 달라질 수 있다고 꼭 언급이 됩니다.) 재미있는 것은 32비트의 경우 double 타입의 배열이 요소가 1,000개 이상일 때 85,000 크기가 안 되는데도 LOH에 할당되는 것을 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 32비트 실행 (64비트에서는 모두 0) Console.WriteLine(GC.GetGeneration(new double[998])); // 0 Console.WriteLine(GC.GetGeneration(new double[999])); // 0 Console.WriteLine(GC.GetGeneration(new double[1000])); // 2 Console.WriteLine(GC.GetGeneration(new double[1001])); // 2 Console.WriteLine(GC.GetGeneration(new float[998 * 2])); // 0 Console.WriteLine(GC.GetGeneration(new float[999 * 2])); // 0 Console.WriteLine(GC.GetGeneration(new float[1000 * 2])); // 0 Console.WriteLine(GC.GetGeneration(new float[1001 * 2])); // 0 </pre> <br /> 그 이유는, LOH 힙이 항상 8바이트 정렬을 따르기 때문에 double 타입의 배열이 LOH에 할당되면 좀 더 성능이 좋아지기 때문이라고 합니다. 따라서 4바이트 정렬인 float 배열이라면 동일한 메모리 공간을 점유하는데도 LOH에 할당되지 않습니다. 반면 64비트에서는 double에 대한 이런 혜택이 없다고.<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'>9. 코드 실행 중에 외부 요인으로 발생하는 예외를 막을 수 있을까?</div> <br /> 여기서의 "외부 요인으로 발생하는 예외"란 코드 스스로 실행하는 중에 발생하는 null 참조 예외나 FileNotFound 예외 등이 아닌 OutOfMemoryException, StackOverflowException, ThreadAbortException으로 대표되는 Asynchronous exceptions을 의미합니다.<br /> <br /> 예를 들어, .NET 코드를 실행하는 A 스레드에 대해 다른 B 스레드에서 A 스레드로 ThreadAbortException을 발생시키는 경우 그 예외를 막을 수 있느냐는 것입니다. 마이크로소프트는 CLR 2부터 아래의 글에서 설명한,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > CER(Constrained Execution Region)이란? ; <a target='tab' href='http://www.sysnet.pe.kr/2/0/11868'>http://www.sysnet.pe.kr/2/0/11868</a> </pre> <br /> CER을 사용해 그것이 가능하도록 만들었습니다.<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'>10. GC되는 객체를 되살릴 수 있을까?</div> <br /> 일반적으로는 GC되는 객체를 살릴 수 없지만, 해당 객체의 Finalize 메서드를 정의해 그 안에서 참조를 다시 유지해주는 코드를 넣는다면 살릴 수 있습니다.<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.Runtime.CompilerServices; namespace ConsoleApp1 { class Program { public static Foo StrongReference = null; static void Main(string[] args) { Created(); while (true) { Console.WriteLine("Created, cleaning up."); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine(); Console.WriteLine("Press any key to continue..."); Console.ReadLine(); Program.StrongReference.Print(); } } [MethodImpl(MethodImplOptions.NoInlining)] private static void Created() { Foo foo = new Foo(); foo = null; } } public class Foo { int n = 1; public void Print() { Console.WriteLine(n); } <span style='color: blue; font-weight: bold'>~Foo()</span> { <span style='color: blue; font-weight: bold'>Program.StrongReference = this;</span> n++; Console.WriteLine("Finalizing..."); } } } /* 실행 결과: Created, cleaning up. Finalizing... Press any key to continue... <ENTER> 2 Press any key to continue... <ENTER> 2 Press any key to continue... */ </pre> <br /> 보는 바와 같이, Finalizer(~Foo 메서드)에서 static 변수를 root 참조로 설정함으로써 GC가 되는 과정에서 다시 객체를 되살리고 있습니다. 재미를 위해, GC를 비켜나간 후에 다음번 GC가 되기 전 Program.StrongReference = null을 설정하도록 바꾸면 어떻게 될까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > static void Main(string[] args) { Created(); while (true) { Console.WriteLine("Created, cleaning up."); GC.Collect(); GC.WaitForPendingFinalizers(); Console.WriteLine(); Console.WriteLine("Press any key to continue..."); Console.ReadLine(); Program.StrongReference.Print(); <span style='color: blue; font-weight: bold'>Program.StrongReference = null;</span> } } </pre> <br /> 얼핏 생각하면, 이런 경우 GC가 될 때마다 반복적으로 ~Foo 메서드가 실행되어 계속 되살아나야 할 듯싶은데 실제로 해 보면 다음과 같이 두 번째 GC 후 예외가 발생합니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Created, cleaning up. Finalizing... Press any key to continue... <ENTER> 2 Press any key to continue... <ENTER> Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object. at ConsoleApp1.Program.Main(String[] args) in C:\cookbook_part_2_gc\cookbook_series2_sample\q10\ConsoleApp1\Program.cs:line 24 </pre> <br /> 이유는, ~Foo 메서드가 실행되지 않기 때문입니다. GC는 해당 객체가 생성되는 시점에 Finalizer를 종료 큐에 등록할 뿐 참조를 살렸다고 해서 제거한 Finalizer를 종료 큐에 다시 넣지는 않습니다. 따라서 한 번 GC 과정을 밟은 객체의 경우 종료 큐에서 Finalizer가 없어진 상태이므로 위의 코드에서는 ~Foo 소멸자가 두 번째부터는 실행되지 않는 것입니다.<br /> <br /> 대신 종료 큐에 GC.ReRegisterForFinalize 메서드를 이용해 다시 넣는 것도 가능합니다. 이를 이용해 소멸자를 다음과 같이 정의하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > ~Foo() { Program.StrongReference = this; <span style='color: blue; font-weight: bold'>GC.ReRegisterForFinalize(this);</span> n++; Console.WriteLine("Finalizing..."); } </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;' > Created, cleaning up. Finalizing... Press any key to continue... <ENTER> 2 Finalizing... Press any key to continue... <ENTER> 3 Finalizing... Press any key to continue... </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'>11. VM Hoarding 이란?</div> <br /> GC는 세대를 거듭하면서 힙의 크기가 커집니다. 그런데 Heap 메모리를 연속적인 선형 공간으로 단일하게 유지하는 것이 아니라 "Segment"라는 블록 단위로 유지합니다. 그래서 일정 크기의 Segment를 생성한 후 그것이 다 차게 되면 다시 Segment를 할당해서 Heap의 크기를 늘리는 식입니다. 그런데 Segment를 생성 후 객체를 할당하다가 GC가 되어 해당 Segment가 통째로 비어 있게 될 수도 있습니다. 그렇게 되면 CLR은 Segment 자체를 해제해 버립니다. 대개의 경우 이 동작이 문제가 없겠으나 대량의 객체를 자주 할당하고 해제하는 작업을 반복하는 상황이라면 잦은 Segment 생성/해제가 반복되어 성능에 영향을 줄 수 있습니다.<br /> <br /> 바로 이런 문제를 해결하기 위해 CLR 2.0부터 <a target='tab' href='https://www.sysnet.pe.kr/2/0/1746'>CLR 호스팅 시</a>에 설정할 수 있는 <a target='tab' href='https://learn.microsoft.com/ko-kr/dotnet/framework/unmanaged-api/hosting/startup-flags-enumeration'>STARTUP_HOARD_GC_VM</a> 옵션을 제공합니다. 이 옵션이 설정되면, 비어 있는 Segment를 곧바로 해제하지 않고 대기 목록에 넣어 다음번 Segment 요청 시 곧바로 대기 목록의 Segment를 반환해 사용할 수 있도록 합니다.<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;' > Large Object Heap Uncovered (an old MSDN article) ; <a target='tab' href='https://blogs.msdn.microsoft.com/maoni/2016/05/31/large-object-heap-uncovered-from-an-old-msdn-article/'>https://blogs.msdn.microsoft.com/maoni/2016/05/31/large-object-heap-uncovered-from-an-old-msdn-article/</a> </pre> <br /> <hr style='width: 50%' /><br /> <a name='12'></a> <br /> <br /><div style='font-size: 12pt; font-family: Malgun Gothic, Consolas; color: #2211AA; text-align: left; font-weight: bold'>12. GC를 끌 수 있을까?</div> <br /> <a target='tab' href='https://learn.microsoft.com/en-us/dotnet/api/system.gc.trystartnogcregion#System_GC_TryStartNoGCRegion_System_Int64'>GC.TryStartNoGCRegion</a> 메서드를 호출하면 됩니다.<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;' > bool result = GC.TryStartNoGCRegion(50_000, 10_000); if (result == true) { // do your work... } if (GCSettings.LatencyMode == GCLatencyMode.NoGCRegion) { GC.EndNoGCRegion(); } </pre> <br /> 현재 LOH에 10,000 바이트의 여유 공간이 있고 일반 Segment에 40,000 바이트의 여유 공간이 있다면 GC.EndNoGCRegion 메서드가 호출되기까지 지정된 바이트 내의 메모리를 필요로 한다면 GC가 구동되지 않게 됩니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1434&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
2025
(왼쪽의 숫자를 입력해야 합니다.)