ガベージコレクションとオブジェクトの有効期間

一見単純な質問: インスタンスメソッドが実行を完了していない場合、CLRはオブジェクトのファイナライザーを呼び出すことができますか?



言い換えると、次の場合に「インスタンスの最終化」を見ることができます。「何かを終えた」前に。



internal class GcIsWeird { ~GcIsWeird() { Console.WriteLine("Finalizing instance."); } public int data = 42; public void DoSomething() { Console.WriteLine("Doing something. The answer is ... " + data); // Some other code... Console.WriteLine("Finished doing something."); } }
      
      







回答: それは依存します。



デバッグビルドでは、これは決して起こりませんが、リリースでは可能です。 この説明を簡単にするために、次の静的メソッドを検討してください。



 static void SomeWeirdAndVeryLongRunningStaticMethod() { var heavyWeightInstance = new int[42_000_000]; // The very last reference to 'heavyWeightInstance' Console.WriteLine(heavyWeightInstance.Length); for (int i = 0; i < 10_000; i++) { // Doing some useful stuff. Thread.Sleep(42); } }
      
      





ローカル変数 'heavyWeightInstance'は最初の2行でのみ使用され、その後、GCによって理論的にコンパイルできます。 変数を明示的にnullに設定してリンクを解放できますが、これは必須ではありません。 CLRには、使用されなくなったオブジェクトを収集できる最適化があります。 JITコンパイラーは、「ポインターテーブル」またはGCInfo( coreclrリポジトリのgcinfo.cppを参照)と呼ばれる特別なテーブルを割り当てます。



インスタンスメソッドは、最初の引数に「this」ポインタが渡された静的メソッドです。 つまり、すべての最適化は、インスタンスメソッドと静的メソッドの両方に対して有効です。



これが実際にそうであることを証明するために、次のプログラムを実行して結果を見ることができます。



 class Program { internal class GcIsWeird { ~GcIsWeird() { Console.WriteLine("Finalizing instance."); } public int data = 42; public void DoSomething() { Console.WriteLine("Doing something. The answer is ... " + data); CheckReachability(this); Console.WriteLine("Finished doing something."); } } static void CheckReachability(object d) { var weakRef = new WeakReference(d); Console.WriteLine("Calling GC.Collect..."); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); string message = weakRef.IsAlive ? "alive" : "dead"; Console.WriteLine("Object is " + message); } static void Main(string[] args) { new GcIsWeird().DoSomething(); } }
      
      





ご想像のとおり、このプログラムを「リリース」モードで実行すると、次の結論が導き出されます。



Doing something. The answer is ... 42

Calling GC.Collect...

Finalizing instance.

Object is dead

Finished doing something








出力は、インスタンスメソッドの実行中にオブジェクトがアセンブルされたことを示しています。 次に、これがどのように起こるかを見てみましょう。





2番目のオプションを使用することにしました。 これを行うには、「 JITダンプ」セクションで説明されている手順を使用して、次の手順を実行します。



  1. CoreCLR Repoをビルドします(VC ++、CMake、Pythonなど、必要なすべてのVisual Studioコンポーネントをインストールすることを忘れないでください)。
  2. dotnet cliをインストールします。
  3. dotnetコア用のアプリケーションを作成します。
  4. ドットネットコアアプリケーションを作成して公開します。
  5. 新しく構築されたcoreclrバイナリを、公開アプリケーションのあるフォルダーにコピーします。
  6. COMPlus_JitDump = YourMethodNameなど、いくつかの環境変数を設定します。
  7. アプリケーションを起動します。


結果は次のとおりです。



 *************** After end code gen, before unwindEmit() IN0002: 000012 call CORINFO_HELP_NEWSFAST IN0003: 000017 mov rcx, 0x1FE90003070 // Console.WriteLine("Doing something. The answer is ... " + data); IN0004: 000021 mov rcx, gword ptr [rcx] IN0005: 000024 mov edx, dword ptr [rsi+8] IN0006: 000027 mov dword ptr [rax+8], edx IN0007: 00002A mov rdx, rax IN0008: 00002D call System.String:Concat(ref,ref):ref IN0009: 000032 mov rcx, rax IN000a: 000035 call System.Console:WriteLine(ref) // CheckReachability(this); <b>IN000b: 00003A mov rcx, rsi</b> //     «this»   GC IN000c: 00003D call Reachability.Program:CheckReachability(ref) // Console.WriteLine IN000d: 000042 mov rcx, 0x1FE90003078 IN000e: 00004C mov rcx, gword ptr [rcx] IN000f: 00004F mov rax, 0x7FFB6C6B0160 *************** Variable debug info 2 vars 0( UNKNOWN) : From 00000000h to 00000008h, in rcx <b>0( UNKNOWN) : From 00000008h to 0000003Ah, in rsi</b> *************** In gcInfoBlockHdrSave() <b>Register slot id for reg rsi = 0.</b> Set state of slot 0 at instr offset 0x12 to Live. Set state of slot 0 at instr offset 0x17 to Dead. Set state of slot 0 at instr offset 0x2d to Live. Set state of slot 0 at instr offset 0x32 to Dead. Set state of slot 0 at instr offset 0x35 to Live. <b>Set state of slot 0 at instr offset 0x3a to Dead.</b>
      
      





Jitコンパイラからのダンプは、WinDBGまたはVisual Studioの「逆アセンブリ」ウィンドウで表示されるものとはわずかに異なります。 主な違いは、ローカル変数(ASMバイアスの観点から使用される場合)とGCInfoの数など、より多くの情報を表示することです。 GCInfoテーブルの内容を理解するのに役立つコマンドのオフセットを示す別の便利な側面。



この場合、オフセット0x3Aのコマンドの後に「this」ポインターが不要になることは明らかです。 CheckReachabilityの呼び出しの直前。 これが、CheckReachabilityメソッド内でGCが呼び出された後にオブジェクトがアセンブル(破棄)された理由です。



おわりに



JITとGCは連携して、アプリケーションが使用しなくなった直後にGCがオブジェクトを収集するのに役立つサポート情報を追跡します。

C#言語仕様では、この最適化は可能であるが必要ではないと述べています。「現在のスコープからのローカル変数がオブジェクトへの唯一の参照であり、このローカル変数がプロシージャのどのパスでも使用されていない場合、義務 ))このオブジェクトを使用していないと見なし、アセンブリにアクセスできるようにする」。 したがって、本番コードではこの動作に依存しないでください。



All Articles