[インデックス 16823] ファイルの概要
このコミットは、Goランタイムにおけるガベージコレクション(GC)の挙動に関する修正です。具体的には、ファイナライザキュー(runfinq
)内で使用されるフレームがGCによって誤って参照されることを防ぎ、ファイナライズされたオブジェクトが適切に回収されるようにします。
コミット
commit eb04df75cd87722f396fb66583279afe5abfb1ca
Author: Dmitriy Vyukov <dvyukov@google.com>
Date: Fri Jul 19 18:01:33 2013 +0400
runtime: prevent GC from seeing the contents of a frame in runfinq
This holds the last finalized object and arguments to its finalizer.
Fixes #5348.
R=golang-dev, iant
CC=golang-dev
https://golang.org/cl/11454044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/eb04df75cd87722f396fb66583279afe5abfb1ca
元コミット内容
runtime: prevent GC from seeing the contents of a frame in runfinq
This holds the last finalized object and arguments to its finalizer.
Fixes #5348.
このコミットは、runfinq
関数内で使用されるフレームの内容がガベージコレクタ(GC)によって誤って参照されることを防ぐためのものです。このフレームは、最後にファイナライズされたオブジェクトとそのファイナライザへの引数を保持しています。これにより、Issue #5348で報告された問題が修正されます。
変更の背景
Go言語のガベージコレクタは、到達可能なオブジェクトを特定し、到達不可能なオブジェクトを解放することでメモリを管理します。ファイナライザは、オブジェクトがGCによって回収される直前に実行される特別な関数です。しかし、ファイナライザが実行される際に、そのファイナライザに渡される引数や、ファイナライズされるオブジェクト自体が一時的にメモリ上に存在します。
このコミットの背景にある問題は、runfinq
("run finalizer queue")関数がファイナライザを実行する際に、ファイナライザの引数を格納するために動的に確保されるframe
というメモリ領域が、GCによってポインタを含むものとして扱われていたことにあります。
本来、このframe
はファイナライザの実行が完了すれば不要になる一時的なデータであり、GCがその内容をスキャンする必要はありませんでした。しかし、GCがこのframe
をスキャンしてしまうと、その中に含まれる「最後にファイナライズされたオブジェクト」へのポインタがGCによって「到達可能」と誤認され、結果としてそのオブジェクトが回収されずにメモリリークが発生する可能性がありました。Issue #5348はまさにこの問題、つまりファイナライズされたはずのオブジェクトがGCによって回収されないという現象を報告していました。
この修正の目的は、runfinq
がファイナライザの引数を保持するために使用するframe
が、GCによってポインタを含まないものとして扱われるようにすることで、ファイナライズされたオブジェクトが適切に回収されるようにすることです。
前提知識の解説
このコミットを理解するためには、以下のGoランタイムとガベージコレクションに関する基本的な知識が必要です。
- ガベージコレクション (GC): GoのGCは、プログラムが動的に確保したメモリのうち、もはやプログラムから到達できない(参照されていない)領域を自動的に解放する仕組みです。GoのGCは並行マーク&スイープ方式を採用しており、プログラムの実行と並行してGCが動作します。
- ファイナライザ (Finalizer): Goでは、
runtime.SetFinalizer
関数を使って、オブジェクトがGCによって回収される直前に実行される関数(ファイナライザ)を登録できます。これは、ファイルハンドルやネットワーク接続などの外部リソースをクリーンアップするのに役立ちます。ファイナライザは、オブジェクトが「到達不可能」になった後に実行されますが、GCがそのオブジェクトを実際に回収するタイミングは保証されません。 runtime
パッケージ: Goのruntime
パッケージは、Goプログラムの実行環境を管理する低レベルな機能を提供します。これには、スケジューラ、ガベージコレクタ、メモリ管理などが含まれます。このコミットで変更されているmgc0.c
は、GoランタイムのGCに関するC言語で書かれた部分です。- メモリ割り当てとGCの連携: Goランタイムは、
runtime.mallocgc
のような関数を使ってメモリを割り当てます。この関数は、割り当てるメモリがポインタを含むかどうか(FlagNoPointers
など)をGCに伝えるためのフラグを受け取ります。GCはこれらのフラグを利用して、どのメモリ領域をスキャンすべきかを判断します。ポインタを含まないことが保証されているメモリ領域は、GCのスキャン対象から除外され、GCの効率が向上します。 - CgoとGoランタイムのCコード: Goランタイムの一部はC言語で書かれています(特に初期のGCやスケジューラなど)。これらのCコードは、Goのコードと密接に連携して動作します。
src/pkg/runtime/mgc0.c
は、GoのGCのコアロジックの一部を実装しているファイルです。 runfinq
関数: この関数は、ファイナライザキューに登録されたファイナライザを実行するGoランタイム内の内部関数です。ファイナライザは、GCによって回収される準備ができたオブジェクトに対して呼び出されます。
技術的詳細
このコミットの核心は、runfinq
関数内でファイナライザの引数を保持するために使用されるframe
というメモリ領域の割り当て方法を変更することにあります。
元のコードでは、frame
の再割り当てが必要になった場合、runtime·mal
関数を使用してメモリを割り当てていました。runtime·mal
は、GCに対してそのメモリ領域がポインタを含む可能性があることを示唆するデフォルトの動作をします。
// 変更前
runtime·free(frame);
frame = runtime·mal(framesz); // GCがスキャンする可能性がある
framecap = framesz;
しかし、このframe
はファイナライズされるオブジェクトとそのファイナライザへの引数を一時的に保持するものであり、GCがその内容をスキャンする必要はありません。なぜなら、ファイナライズされるべきオブジェクト自体は、まだ回収されていない間はfinc
(finalizer queue)という別のデータ構造に保持されており、GCはそこから到達可能性を判断するからです。frame
内のポインタは、GCの到達可能性判断には影響を与えない一時的な参照に過ぎません。
GCがframe
をスキャンしてしまうと、frame
内に含まれる「最後にファイナライズされたオブジェクト」へのポインタがGCによって到達可能と誤認され、そのオブジェクトが回収されないという問題が発生していました。
この問題を解決するため、コミットではruntime·mal
の代わりにruntime·mallocgc
関数を使用し、FlagNoPointers
フラグを渡すように変更しています。
// 変更後
runtime·free(frame);
// The frame does not contain pointers interesting for GC,
// all not yet finalized objects are stored in finc.
// If we do not mark it as FlagNoPointers,
// the last finalized object is not collected.
frame = runtime·mallocgc(framesz, FlagNoPointers, 0, 1); // GCにポインタを含まないことを明示
framecap = framesz;
runtime·mallocgc
は、Goランタイムの主要なメモリ割り当て関数であり、GCに関する様々なフラグを受け取ることができます。FlagNoPointers
フラグは、割り当てられたメモリ領域がポインタを含まないことをGCに明示的に伝えます。これにより、GCはこのframe
領域をスキャン対象から除外するようになります。
結果として、frame
内に一時的に保持されるファイナライズ対象のオブジェクトへのポインタがGCによって誤って到達可能と判断されることがなくなり、ファイナライズされたオブジェクトが適切に回収されるようになります。これは、GCの正確性を向上させ、メモリリークを防ぐ上で重要な修正です。
コアとなるコードの変更箇所
src/pkg/runtime/mgc0.c
ファイルのrunfinq
関数内のメモリ割り当て部分が変更されています。
--- a/src/pkg/runtime/mgc0.c
+++ b/src/pkg/runtime/mgc0.c
@@ -2274,7 +2274,11 @@ runfinq(void)
if(framecap < framesz) {
runtime·free(frame);
- frame = runtime·mal(framesz);
+ // The frame does not contain pointers interesting for GC,
+ // all not yet finalized objects are stored in finc.
+ // If we do not mark it as FlagNoPointers,
+ // the last finalized object is not collected.
+ frame = runtime·mallocgc(framesz, FlagNoPointers, 0, 1);
framecap = framesz;
}
*(void**)frame = f->arg;
コアとなるコードの解説
変更された行は以下の通りです。
- frame = runtime·mal(framesz);
+ frame = runtime·mallocgc(framesz, FlagNoPointers, 0, 1);
-
変更前:
frame = runtime·mal(framesz);
runtime·mal
は、Goランタイム内部でメモリを割り当てるための関数です。この関数は、割り当てられたメモリがポインタを含む可能性があるとGCに伝えるデフォルトの動作をします。そのため、GCはこのframe
領域をスキャン対象としていました。
-
変更後:
frame = runtime·mallocgc(framesz, FlagNoPointers, 0, 1);
runtime·mallocgc
は、Goランタイムの主要なメモリ割り当て関数であり、ガベージコレクタと連携します。framesz
: 割り当てるメモリのサイズです。FlagNoPointers
: このフラグが重要です。これは、割り当てられるメモリ領域がポインタを含まないことをGCに明示的に伝えます。これにより、GCはこのframe
領域をスキャンする必要がないと判断し、スキャン対象から除外します。0, 1
: これらの引数は、それぞれtyp
(型情報)とneedzero
(ゼロ初期化が必要かどうか)に対応します。このコンテキストでは、型情報は不要であり、ゼロ初期化は必要ないか、あるいはデフォルトでゼロ初期化されるため、これらの値が渡されています。
この変更により、runfinq
関数内でファイナライザの引数を保持するために使用されるframe
が、GCによってポインタを含まないデータとして扱われるようになります。これにより、frame
内に一時的に存在するファイナライズ対象のオブジェクトへのポインタがGCによって誤って到達可能と判断されることがなくなり、ファイナライズされたオブジェクトが適切に回収されるようになります。
コメントで説明されているように、The frame does not contain pointers interesting for GC, all not yet finalized objects are stored in finc.
(このフレームはGCにとって興味深いポインタを含んでおらず、まだファイナライズされていないすべてのオブジェクトはfinc
に格納されている) というのがこの変更の根拠です。If we do not mark it as FlagNoPointers, the last finalized object is not collected.
(もしFlagNoPointers
としてマークしないと、最後にファイナライズされたオブジェクトが回収されない) というのは、この問題が実際にメモリリークを引き起こしていたことを示しています。
関連リンク
- Go Issue #5348: runtime: finalizer object not collected
- Go CL 11454044: https://golang.org/cl/11454044
参考にした情報源リンク
- Go言語のソースコード (
src/pkg/runtime/mgc0.c
) - Go言語のIssueトラッカー (Issue #5348)
- Go言語のコードレビューシステム (CL 11454044)
- Go言語のガベージコレクションに関する一般的なドキュメントと解説
- A Guide to the Go Garbage Collector: https://go.dev/doc/gc-guide
- Go's Garbage Collector: https://go.dev/blog/go15gc (このコミットより後の情報も含まれるが、GCの基本的な概念理解に役立つ)
- Goランタイムの内部構造に関する情報源 (例: Goのソースコードを解説しているブログや書籍)I have generated the detailed explanation of the commit in Markdown format, following all the specified instructions and chapter structure. The output is in Japanese and includes comprehensive technical details, background, and prerequisite knowledge. I have also included relevant links and references.