I use a Cheney style GC which is triggered, and may start a collection, each time a term is rewritten. The collector starts of with one root. This gives a minimal overhead, since in other GCs collections are tried each allocation step and this scheme does without that overhead giving an allocation routine of just a few machine instructions.
A problem with this scheme is that there may not be enough room in the heap for allocating a new term during a rewrite. Normally, less than a hundred bytes will be allocated, so keeping a spill-over region will be quite safe, except for FFI which may introduce a term of unknown size for example while reading files.
The are several ways of handling this: a) adjusting to a normal GC where an allocation may be interrupted with a collection, which also would imply that I would need to travel the C stack to collect other roots, b) explicitly introduce an allocation routine in the language which will grow the heap such that enough room is available for building large terms, c) allocate large terms in another region, d) never allocate large terms, instead pass pointers from C and work on opaque structures which must be deallocated explicitly.
Option d) it is.