Naive question here, as many in this discussion are way more experienced with the inner workings of languages:
It seems like doing away with GC is somewhat akin to the Halting Problem, in that it's impossible in the general case but may be practical in the common case. Obviously there are languages without GC, but they often end up reimplementing the patterns of GC for particular problems, so I really mean doing away with GC-like patterns and concerns (memory management).
With the Halting Problem, the specific feature that triggers it is unbounded loops. The solution is that most languages have two classes of loops. Ones that are bounded and ones that aren't. We still need unbounded loops for a language to be Turing-Complete, but frown upon their use unless it's absolutely necessary.
To the question: it seems to me that the equivalent feature that triggers the need for GC is passing/scoping items by reference rather than value, because it violates the simpler memory lifetime model of local variables in the stack frame. Could it be that the solution would be a language that makes it easier to explicitly reason about, detect, and limit objects that are passed around by reference?
I'm thinking the initial reaction will be no way, it's way too convenient to pass by reference, all languages pass larger objects by reference to avoid unnecessary copies, etc., but am wondering if it's the next "GOTO considered harmful".
Perhaps there's a way to rethink the way program flow and object persistence interact. Not in the general case, but as a way to make it more explicit which things might leak, in the way that most languages now allow you to mentally discount "foreach" loops as not subject to the halting problem and only worry about the rare "while" or recursive call.
This may be just me groping back to the idea of functional purity, as pure functions don't mutate external state, but perhaps there are other, less restrictive ideas that would also help, such as a notion of larger flow control regions that guarantee they won't return to some distant part of the program and thus will not reference an object used there again.
Overall, I'm just thinking there are no silver bullets, but that perhaps the cases where the problems occur can be corralled into a corner, as probably the majority of a lot of programs have no issues with memory management and would benefit from making that explicit, so it's easier to focus on the bits that do.
Edit: I'm guessing a lot of what I'm groping towards is exactly what Rust and its kin deal with, but should probably learn one of those kinds of languages before I armchair about this stuff.. :)