Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

(시리즈 글이 12개 있습니다.)
.NET Framework: 811. (번역글) .NET Internals Cookbook Part 1 - Exceptions, filters and corrupted processes
; https://www.sysnet.pe.kr/2/0/11838

.NET Framework: 816. (번역글) .NET Internals Cookbook Part 2 - GC-related things
; https://www.sysnet.pe.kr/2/0/11869

.NET Framework: 818. (번역글) .NET Internals Cookbook Part 3 - Initialization tricks
; https://www.sysnet.pe.kr/2/0/11871

.NET Framework: 819. (번역글) .NET Internals Cookbook Part 4 - Type members
; https://www.sysnet.pe.kr/2/0/11872

.NET Framework: 820. (번역글) .NET Internals Cookbook Part 5 - Methods, parameters, modifiers
; https://www.sysnet.pe.kr/2/0/11873

.NET Framework: 821. (번역글) .NET Internals Cookbook Part 6 - Object internals
; https://www.sysnet.pe.kr/2/0/11874

.NET Framework: 822. (번역글) .NET Internals Cookbook Part 7 - Word tearing, locking and others
; https://www.sysnet.pe.kr/2/0/11876

.NET Framework: 823. (번역글) .NET Internals Cookbook Part 8 - C# gotchas
; https://www.sysnet.pe.kr/2/0/11877

.NET Framework: 824. (번역글) .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff
; https://www.sysnet.pe.kr/2/0/11878

.NET Framework: 825. (번역글) .NET Internals Cookbook Part 10 - Threads, Tasks, asynchronous code and others
; https://www.sysnet.pe.kr/2/0/11879

.NET Framework: 826. (번역글) .NET Internals Cookbook Part 11 - Various C# riddles
; https://www.sysnet.pe.kr/2/0/11882

.NET Framework: 831. (번역글) .NET Internals Cookbook Part 12 - Memory structure, attributes, handles
; https://www.sysnet.pe.kr/2/0/11891




(번역글) .NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff

이번에도 .NET Internals Cookbook 시리즈의 9번째 글을 번역한 것입니다.

.NET Internals Cookbook Part 9 - Finalizers, queues, card tables and other GC stuff
; https://blog.adamfurmanek.pl/2019/04/13/net-internals-cookbook-part-9/





59. finalizer에서 예외가 발생하거나 무한 루프에 빠진다면?

다음의 코드로 finalizer에서 예외를 발생시키면,

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
{
    ~Bar()
    {
        Console.WriteLine("Destructor with exception");
        throw new Exception();
    }
}

/* 출력 결과
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
*/

프로그램이 종료하는 것을 볼 수 있습니다. 이는 Object.Finalize Method 문서에 다음과 같이 명시되어 있습니다.

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 and no active try/finally blocks or finalizers are executed. This behavior ensures process integrity if the finalizer cannot free or destroy resources.


저도 관련된 글을 쓴 적이 있습니다. ^^

windbg 분석 사례 - 종료자(Finalizer)에서 예외가 발생한 경우 비정상 종료(Crash) 발생
; https://www.sysnet.pe.kr/2/0/11732

그렇다면, Finalizer에서 무한 루프가 발생하면 어떻게 될까요?

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
{
    ~Bar()
    {
        Console.WriteLine("Destructor with infinite loop");
        while (true) { }
    }
}

/* 출력 결과
Testing Foo
Good destructor
Testing Bar
Destructor with infinite loop
*/

표면 상으로는 문제가 없는 듯하지만, 실제로는 프로그램이 진행되면서 finalizer를 구현한 객체들이 누적되면서 OOM 예외가 발생할 가능성이 높아집니다. 이에 대해서는 다음의 글에서,

windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상
; https://www.sysnet.pe.kr/2/0/11803

windbg - 닷넷 응용 프로그램의 메모리 누수 분석
; https://www.sysnet.pe.kr/2/0/11808

windbg의 finalizequeue 명령어로 확인할 수 있다고 설명했습니다.





60. What is finalization queue? What is f-reachable queue?

종료 큐(finalization queue)와 F-reachable 큐에 대해서는 제 책의 "5.4.2.6 소멸자" 절을 참고하세요. ^^





61. 객체를 pinnging 한다는 의미

GC Heap에 있는 객체를 외부(Native) 코드에 전달했다고 가정해 봅니다. 그럼 외부에서 그 객체를 사용하는 동안 GC가 발동할 수도 있고 그렇게 되면 해당 객체의 위치가 바뀌게 됩니다. 하지만 외부 코드는 바뀐 객체에 대한 포인터를 모르므로 엉뚱한 위치의 데이터를 접근할 수 있습니다.

따라서 이 문제를 해결하기 위해 GC Heap에 있는 객체를 "움직이지 못하는 상태"로 고정시키는 것을 pinning이라고 합니다. 즉, 외부 코드에 GC Heap의 객체를 전달하려면 반드시 pinning시켜야만 합니다. pinning을 명시적으로 하려면 fixed 예약어GC.Alloc 메서드를 사용할 수 있습니다.





62. P/Inovke 호출은 인자를 pinning 할까?

P/Invoke 메서드가 호출되는 동안, 해당 메서드에 전달한 인자들은 자동으로 pinning 됩니다. 따라서 P/Invoke 메서드가 비동기 호출이라면 메서드 자체의 호출 이후에는 pinning이 풀리므로 비동기 작업이 완료되었을 때 문제가 발생할 수 있으므로 그런 경우에는 직접 pinning을 해야 합니다.





63. 쓰기 장벽(write barrier)이란?

이 용어에 대한 자세한 설명은 다음 질문으로 넘기고 일단 쓰기 장벽이 실제로 사용되고 있는 사례를 검토해 보겠습니다. 예를 들어 다음의 코드는 Bar 객체를 Foo 객체가 포함하고 있는 상황입니다.

using System;

namespace Program
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var foo = new Foo();
            GC.Collect();

            Console.WriteLine(GC.GetGeneration(foo));
            foo.Bar = new Bar();
            Console.ReadLine();
        }
    }
}

class Foo
{
    public Bar Bar;
}

class Bar
{
}

빌드하고 Main 메서드의 IL 코드를 보면,

// ...[생략]...
  .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
    IL_0019:  ldloc.0
    IL_001a:  newobj     instance void Bar::.ctor()
    IL_001f:  stfld      class Bar Foo::Bar
    IL_0024:  call       string [mscorlib]System.Console::ReadLine()
    IL_0029:  pop
    .line 15,15 : 9,10 ''
    IL_002a:  ret
  } // end of method Program::Main

// ...[생략]...

"foo.Bar = new Bar();" 코드에 특이점이 없습니다. 하지만 이 코드를 실행 시 JIT 컴파일된 기계어로 보면 다음과 같이 clr!JIT_WriteBarrierEAX 함수가 호출되는 것을 볼 수 있습니다.

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]
007a04c8 e8b3e2d872      call    clr!JIT_WriteBarrierEAX (7352e780)

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

CLR은 왜? JIT_WriteBarrierEAX 코드를 넣은 걸까요? 이어지는 다음의 질문에서 알아봅니다.





64. Card 테이블? Brick 테이블?

이전 글에서 "Bar 객체를 Foo 객체가 포함"하고 있는 상황이라고 했는데 예전에 제가 썼던 다음의 글에서 이와 관련해 card table을 설명했었습니다.

.NET GC - 하위 세대의 객체를 포함하는 상위 세대의 참조를 추적하기 위한 card-table
; https://www.sysnet.pe.kr/2/0/1670

즉, 하위 세대의 객체(Bar)를 포함하는 상위 세대의 참조(Foo)를 추적하기 위해 card table이 존재하고, 이때 "foo.Bar = new Bar();"와 같이 대입이 될 때 card table을 업데이트하기 위한 "Write Barrier" 코드를 수행해야 하는 것입니다.

그런데 개인적으로 의문이 드는 것이 있다면, 보통 Barrier에 대해 write/read가 사용되면 CLR에 적용된 메모리 모델에 따른 용어(Memory Barrier)이고 이것은 CPU가 명령어 실행을 최적화하는 과정에 임의로 순서를 바꾸지 못하도록 하는 역할을 합니다. 따라서 JIT_WriteBarrierEAX보다는 JIT_UpdateCardTable이라는 용어가 더 적합했을 텐데 왜? WriteBarrier라는 용어를 굳이 사용했느냐입니다.

어쩌면 GC와의 연동에서 Memory barrier의 역할도 해야 하기 때문에 그런 걸 수도 있지만... 현재 제 수준으로는 딱히 단정 지을 수가 없군요. ^^

참고로, Brick 테이블은 interior pointer의 참조를 기록하는 용도이며 Card 테이블이 128(x86), 256(x64)의 크기로 유지되는 반면 Brick 테이블은 2kB(x86), 4kB(x64)라는 차이가 있습니다.

이와 관련해 좀 더 자세하게 알고 싶다면 다음의 책을 추천한다고.

Pro .NET Memory Management 
; https://prodotnetmemory.com/

(첨부 파일은 이 글의 예제 코드를 포함합니다.)



[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 4/19/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 76  77  78  79  [80]  81  82  83  84  85  86  87  88  89  90  ...
NoWriterDateCnt.TitleFile(s)
11810정성태2/11/201914620.NET Framework: 808. .NET Profiler - GAC 모듈에서 GAC 비-등록 모듈을 참조하는 경우의 문제
11809정성태2/11/201916031.NET Framework: 807. ClrMD를 이용해 메모리 덤프 파일로부터 특정 인스턴스를 참조하고 있는 소유자 확인
11808정성태2/8/201917170디버깅 기술: 123. windbg - 닷넷 응용 프로그램의 메모리 누수 분석
11807정성태1/29/201915115Windows: 156. 가상 디스크의 용량을 복구 파티션으로 인해 늘리지 못하는 경우 [4]
11806정성태1/29/201915439디버깅 기술: 122. windbg - 덤프 파일로부터 PID와 환경 변수 등의 정보를 구하는 방법
11805정성태1/28/201916893.NET Framework: 806. C# - int []와 object []의 차이로 이해하는 제네릭의 필요성 [4]파일 다운로드1
11804정성태1/24/201915105Windows: 155. diskpart - remove letter 이후 재부팅 시 다시 드라이브 문자가 할당되는 경우
11803정성태1/10/201914159디버깅 기술: 121. windbg - 닷넷 Finalizer 스레드가 멈춰있는 현상
11802정성태1/7/201915866.NET Framework: 805. 두 개의 윈도우를 각각 실행하는 방법(Windows Forms, WPF)파일 다운로드1
11801정성태1/1/201917250개발 환경 구성: 427. Netsh의 네트워크 모니터링 기능 [3]
11800정성태12/28/201816217오류 유형: 509. WCF 호출 오류 메시지 - System.ServiceModel.CommunicationException: Internal Server Error
11799정성태12/19/201817323.NET Framework: 804. WPF(또는 WinForm)에서 UWP UI 구성 요소 사용하는 방법 [3]파일 다운로드1
11798정성태12/19/201816483개발 환경 구성: 426. vcpkg - "Building vcpkg.exe failed. Please ensure you have installed Visual Studio with the Desktop C++ workload and the Windows SDK for Desktop C++"
11797정성태12/19/201813603개발 환경 구성: 425. vcpkg - CMake Error: Problem with archive_write_header(): Can't create '' 빌드 오류
11796정성태12/19/201813192개발 환경 구성: 424. vcpkg - "File does not have expected hash" 오류를 무시하는 방법
11795정성태12/19/201816015Windows: 154. PowerShell - Zone 별로 DNS 레코드 유형 정보 조회 [1]
11794정성태12/16/201812543오류 유형: 508. Get-AzureWebsite : Request to a downlevel service failed.
11793정성태12/16/201814675개발 환경 구성: 423. NuGet 패키지 제작 - Native와 Managed DLL을 분리하는 방법 [1]
11792정성태12/11/201815455Graphics: 34. .NET으로 구현하는 OpenGL (11) - Per-Pixel Lighting파일 다운로드1
11791정성태12/11/201815343VS.NET IDE: 130. C/C++ 프로젝트의 시작 프로그램으로 .NET Core EXE를 지정하는 경우 닷넷 디버깅이 안 되는 문제 [1]
11790정성태12/11/201813380오류 유형: 507. Could not save daemon configuration to C:\ProgramData\Docker\config\daemon.json: Access to the path 'C:\ProgramData\Docker\config' is denied.
11789정성태12/10/201824731Windows: 153. C# - USB 장치의 연결 및 해제 알림을 위한 WM_DEVICECHANGE 메시지 처리 [2]파일 다운로드2
11788정성태12/4/201813322오류 유형: 506. SqlClient - Value was either too large or too small for an Int32.Couldn't store <2151292191> in ... Column
11787정성태11/29/201817506Graphics: 33. .NET으로 구현하는 OpenGL (9), (10) - OBJ File Format, Loading 3D Models파일 다운로드1
11786정성태11/29/201814242오류 유형: 505. OpenGL.NET 예제 실행 시 "Managed Debugging Assistant 'CallbackOnCollectedDelegate'" 예외 발생
11785정성태11/21/201816561디버깅 기술: 120. windbg 분석 사례 - ODP.NET 사용 시 Finalizer에서 System.AccessViolationException 예외 발생으로 인한 비정상 종료
... 76  77  78  79  [80]  81  82  83  84  85  86  87  88  89  90  ...