Imagine creating a Rust program entirely with `Rc`. It's basically a GC'd program at the point where the "roots" are managed by the reference counter. The "list of roots" is only ever messed with whenever a `Rc` is dropped/created, and one can optimize functions to take `&Rc` to reduce "GC" pressure. I do not believe it's possible to automate this process in general because if you could, I have a hunch the solution can be used to decide the Halting Problem.
So sure, in general a GC can perform some heuristics to predict the lifetime of an object, but usually the point of using a GC is that one does NOT know the lifetime or it is insanely complex.
Without these restrictions, this problem is isomorphic to the halting problem. (Proof: assignment of a given memory object to a field within another unrelated object creates another reference. The job of automatic memory management is to determine when no such references exist. Now replace that assignment with HALT. Any such automatic memory manager that operates statically would be able to find all HALT statements within the program and so solve the halting problem for an arbitrary program.)
That's why languages that manage memory statically like Rust & C++ must be able to reject some programs as "not passing the borrow-checker", and everything else requires run-time support via either GC or refcounting.
Depending on what one is trying to do, those constructs can still be allowed on safe code, or require explicit unsafe modules (or code blocks).
Examples, D, Nim, Swift, Mesa/Cedar, Modula-2+, Modula-3, Sing#, System C# (aka M#), .NET (version and language dependent).
If you can answer that, then you can optimize accordingly.
E.g you can choose to allocate objects that won't be retained on the stack instead of the heap, or in separate blocks.
You can even potentially benefit from this even if you can't 100% know. If you use a generational collector, an object that almost certainly escapes could bypass the nursery, for example.
And on the more extreme case you can even replace the objects with a bunch of local variables. For example, replace a Point object allocated on the stack with a pair of variables gor the x and y coordinates.
https://en.wikipedia.org/wiki/Region-based_memory_management...