Summary
Typed Go allocations larger than GC_LARGE_OBJECT_THRESHOLD currently bypass the semispace heap and go straight to malloc(). This breaks the runtime's GC assumptions for pointer-containing objects such as large slices of pointers.
Why
- Reachable children stored inside large typed objects can become invisible to the collector.
- Go code still treats these allocations as managed memory, but the runtime no longer scans or moves them.
- The manual
runtime.FreeExternal path is appropriate for explicit external allocations, not ordinary typed Go objects.
Evidence
runtime/gc_heap.c: gc_alloc() sends size > GC_LARGE_OBJECT_THRESHOLD to gc_external_alloc().
runtime/gc_runtime.c: runtime_makeslice() allocates backing arrays via gc_alloc(elem_size * cap, elem_type).
runtime/runtime_stubs.c: runtime_growslice() also uses gc_alloc(total_size, et).
Direction
- Add a GC-tracked large-object space with headers and type metadata.
- Or restrict the external-allocation bypass to pointer-free or explicitly pinned objects only.
- Do not let ordinary typed Go allocations silently cross from managed to unmanaged memory.
Summary
Typed Go allocations larger than
GC_LARGE_OBJECT_THRESHOLDcurrently bypass the semispace heap and go straight tomalloc(). This breaks the runtime's GC assumptions for pointer-containing objects such as large slices of pointers.Why
runtime.FreeExternalpath is appropriate for explicit external allocations, not ordinary typed Go objects.Evidence
runtime/gc_heap.c:gc_alloc()sendssize > GC_LARGE_OBJECT_THRESHOLDtogc_external_alloc().runtime/gc_runtime.c:runtime_makeslice()allocates backing arrays viagc_alloc(elem_size * cap, elem_type).runtime/runtime_stubs.c:runtime_growslice()also usesgc_alloc(total_size, et).Direction