diff options
| author | Michael Anthony Knyszek <mknyszek@google.com> | 2024-12-18 18:56:04 +0000 |
|---|---|---|
| committer | Gopher Robot <gobot@golang.org> | 2024-12-20 10:56:25 -0800 |
| commit | 767ccdf3532a0c51fc4f0165c13f4116b0994fe5 (patch) | |
| tree | c98098a878e6bf689c49008590984024e03be4f7 | |
| parent | 0c276906eee755d651c697f85abc03d6aeb9250d (diff) | |
| download | go-x-website-767ccdf3532a0c51fc4f0165c13f4116b0994fe5.tar.xz | |
_content/doc/gc-guide: add section on object death features
That is, finalizers, cleanups, and weak pointers.
For golang/go#67552.
For golang/go#67535.
Change-Id: I673a976e3bb94ab0ad282e4e053360292034eae6
Reviewed-on: https://go-review.googlesource.com/c/website/+/637555
Reviewed-by: Carlos Amedee <carlos@golang.org>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
| -rw-r--r-- | _content/doc/gc-guide.html | 180 |
1 files changed, 178 insertions, 2 deletions
diff --git a/_content/doc/gc-guide.html b/_content/doc/gc-guide.html index 6735a02a..2d70a1e7 100644 --- a/_content/doc/gc-guide.html +++ b/_content/doc/gc-guide.html @@ -391,7 +391,7 @@ cost of 50% more memory being used. </p> <p> -This change in overheads is the fundamental time/space trade-off mentioned +This change in overheads is the fundamental time/space trade-off mentioned earlier. And <b>GC frequency</b> is at the center of this trade-off: if we execute the GC more frequently, then we use less memory, and vice versa. @@ -998,6 +998,182 @@ These latency sources are visible in pointer writes requiring additional work. </p> +<h3 id="Finalizers_cleanups_and_weak_pointers">Finalizers, cleanups, and weak pointers</h2> + +<p> +Garbage collection provides the illusion of infinite memory using only finite +memory. +Memory is allocated but never explicitly freed, which enables simpler APIs and +concurrent algorithms compared to bare-bones manual memory management. +(Some languages with manually managed memory use alternative approaches such +as "smart pointers" and compile-time ownership tracking to ensure that objects +are freed, but these features are deeply embedded into the API design +conventions in these languages.) +</p> + +<p> +Only the live objects—those reachable from a global variable or a +computation in some goroutine—can affect the behavior of the program. +Any time after an object becomes unreachable ("dead"), it may be safely +recycled by the GC. +This allows for a wide variety of GC designs, such as the tracing design used +by Go today. +The death of an object is not an observable event at the language level. +</p> + +<p> +However, Go's runtime library provides three features that break that illusion: +<a href="/pkg/runtime#SetFinalizer">finalizers</a>, +<a href="/pkg/runtime#AddCleanup">cleanups</a>, +and <a href="/pkg/weak#Pointer">weak pointers</a>. +Each of these features provides some way to observe and react to object death, +and in the case of finalizers, even reverse it. +This of course complicates Go programs and adds an additional burden to the GC +implementation. +Nonetheless, these features exist because they are useful in a variety of +circumstances, and Go programs use them and benefit from them all the time. +</p> + +<h4 id="Advice">Advice</h4> + +<p> +For the details of each feature, refer to its package documentation +(<a href="/pkg/runtime#SetFinalizer">runtime.SetFinalizer</a>, +<a href="/pkg/runtime#AddCleanup">runtime.AddCleanup</a>, +<a href="/pkg/weak#Pointer">weak.Pointer</a>). +Below is some general advice for using these features. +</p> + +<ul> + <li> + <p> + <b> + Avoid using these features directly in typical Go code. + </b> + </p> + <p> + These are low-level features with subtle restrictions and behaviors. + For instance, there's no guarantee cleanups or finalizers will be run + at program exit, or at all for that matter. + The long comments in their API documentation should be seen as a warning. + The vast majority of Go code does not benefit from using these features + directly, only indirectly. + </p> + </li> + <li> + <p> + <b> + Encapsulate the use of these mechanisms within a package. + </b> + </p> + <p> + Where possible, do not allow the use of these mechanisms to leak into + the public API of your package; provide interfaces that make it hard or + impossible for users to misuse them. + For example, instead of asking the user to set up a cleanup on some + C-allocated memory to free it, write a wrapper package and hide that + detail inside. + </p> + </li> + <li> + <p> + <b> + Restrict access to objects that have finalizers, cleanups, and weak + pointers to the package that created and applied them. + </b> + </p> + <p> + This is related to the previous point, but is worth calling out + explicitly, since it's a very powerful pattern for using these + features in a less error-prone way. + For example, the <a href="/pkg/unique">unique package</a> uses + weak pointers under the hood, but completely encasulates the objects + that are weakly pointed-to. + Those values can never be mutated by the rest of the application, + it can only be copied through the + <a href="/pkg/unique#Handle.Value">Value method</a>, preserving + the illusion of infinite memory for package users. + </p> + </li> + <li> + <p> + <b> + Prefer cleaning up non-memory resources deterministically when possible, + with finalizers and cleanups as a fallback. + </b> + </p> + <p> + Cleanups and finalizers are a good fit for memory resources such as + memory allocated externally, like from C, or references to an + <code>mmap</code> mapping. + Memory allocated by C's malloc must eventually be freed by C's free. + A finalizer that calls <code>free</code>, attached to a wrapper object + for the C memory, is a reasonable way to ensure that C memory is + eventually reclaimed as a consequence of garbage collection. + </p> + <p> + However, non-memory resources, like file descriptors, tend to be subject to + system limits that the Go runtime is generally unaware of. + In addition, the timing of the garbage collector in a given Go program + is usually something a package author has little control over (for instance, + how often the GC runs is controlled by <a href="#GOGC">GOGC</a>, which can + be set by operators to a variety of different values in practice). + These two facts conspire to make cleanups and finalizers a bad fit to use + as the only mechanism for releasing non-memory resources. + </p> + <p> + If you're a package author exposing an API that wraps some non-memory + resource, consider providing an explicit API for releasing the resource + deterministically (through a <code>Close</code> method, or something similar), + rather than relying on the garbage collector through cleanups or finalizers. + Instead, prefer to use cleanups and finalizers as a best-effort handler for + programmer mistakes, either by cleaning up the resource anyway like + <a href="/pkg/os#File">os.File</a> does, or by reporting the failure to + deterministically clean up back to the user. + </p> + </li> + <li> + <p> + <b> + Prefer cleanups to finalizers. + </b> + </p> + <p> + Historically, finalizers were added to simplify the interface between Go code + and C code and to clean up non-memory resources. + The intended use was to apply them to wrapper objects that owned C memory or + some other non-memory resource, so that the resource could be released once + Go code was done using it. + These reasons at least partially explain why finalizers are narrowly scoped, + why any given object can only have one finalizer, and why that finalizer must + be attached to the first byte of the object only. + This limitation already stifles some use-cases. + For example, any package that wishes to internally cache some information about + an object passed to it cannot clean up that information once the object is gone. + </p> + <p> + But worse than that, finalizers are inefficient and error-prone due to the fact + that they <a href="https://en.wikipedia.org/wiki/Object_resurrection">resurrect + the object</a> they're attached to, so that it can be passed to the finalizer + function (and even continue to live beyond that, too). + This simple fact means that if the object is part of a reference cycle it can + never be freed, and the memory backing the object cannot be reused until at + least the following garbage collection cycle. + </p> + <p> + Because finalizers resurrect objects, though, they do have a better-defined + exection order than cleanups. + For this reason, finalizers are still potentially (but rarely) useful for + cleaning up structures that have complex destruction ordering requirements. + </p> + <p> + But for all other uses in Go 1.24 and beyond, we recommend you use cleanups + because they are more flexible, less error-prone, and more efficient than + finalizers. + </p> + </li> +</ul> + <!-- TODO: Add a short section about non-steady-state behavior. --> <h3 id="Additional_resources">Additional resources</h3> @@ -1238,7 +1414,7 @@ them in in order to understand GC impact and behavior. </p> <p> - See the <a href="https://pkg.go.dev/runtime/trace">documentation for the + See the <a href="https://pkg.go.dev/runtime/trace">documentation for the <code>runtime/trace</code></a> package for how to get started with execution traces. </p> |
