Is there not a way to figure out when a object should be destroyed?
The short answer is no. It is not possible for compilers to figure out at compile time when objects need to be freed, in the general case.
Consider the following pseudo-code:
A = allocate_some_object(some_input)
B = allocate_other_object(some_input)
if check_some_condition(A, B):
In order for the compiler to know whether
B should be freed after this function returns, it has to know what
check_some_condition(A, B) will return. That would require solving the halting problem, which is not possible in the general case.
check_some_condition could store a reference to
A somewhere else, in which case
A is not garbage at the end of the function even if
B is returned. The compiler would have to be able to know whether this happens with 100% certainty, and even if it did know you then have to solve the problem of flowing the information around until you find some place where
A does become garbage and can be deallocated. That could be in a completely different part of the program.
Plus, deallocating every object as soon as it becomes possible isn't actually always the best option. If objects are being created and dropped all the time, and you have enough memory available, running a garbage collector every so often and deallocating the garbage in bulk can actually be more efficient than hand-tuned C-style manual memory management (despite claims that GC is always a trade off between performance and convenience).
There are techniques that can do some of this, some of the time. Escape analysis can allow the compiler to deduce that a given object cannot "escape" some section of code, so what looks like a heap allocation can be turned into a stack allocation, which is effectively memory managed by the compiler. I've read about other techniques allowing a compiler to tell that a given object must be garbage at the point another object of the same size is created, so instead of compiling an allocation of a new object it re-uses the dead value. But it simply isn't possible to completely replace garbage collection cycles with the compiler figuring out statically when every object needs to be deallocated, because the compiler can only figure that out some of the time.