In a nutshell
Finalization is not a simple matter to be handled by garbage
collectors. It is easy to use with reference counting GC, but this
family of GC is often incomplete, requiring memory leaks to be
compensated for by explicit triggering of destruction and
finalization of some objects and structures. Tracing garbage collectors are much more effective, but
they make it much harder to identify object to be finalized and
destroyed, as opposed to just identifying the unused memory, thus requiring
more complex management, with a cost in time and space, and in complexity
of the implementation.
Introduction
I assume that what you are asking is why garbage collected languages
do not automatically handle destruction/finalization within the
garbage collection process, as indicated by the remark:
I find it extremely lacking that these languages consider memory as the only resource worth managing. What about sockets, file handles, application states?
I disagree with the accepted answer given by kdbanman. While the facts stated there are
mostly correct, though strongly biased towards reference
counting, I do not believe they properly explain the situation
complained about in the question.
I do not believe that the terminology developed in that answer is much
of an issue, and it is more likely to confuse things. Indeed, as presented, the
terminology is mostly determined by the way the procedures are
activated rather than by what they do. The point is that in all cases,
there is the need to finalize an object no longer needed with some
cleanup process and to free whatever resources it has been using,
memory being just one of them. Ideally, all of it should be done
automatically when the object is no longer to be used, by means of a
garbage collector. In practice, GC may be missing or have
deficiencies, and this is compensated for by explicit triggering by
the program of finalization and reclamation.
Explicit trigerring by the program is a problem since it can allow for
hard to analyze programming errors, when an object still in use is
being explicitly terminated.
Hence it is much better to rely on automatic garbage collection to
reclaim resources. But there are two issues:
some garbage collection technique will allow memory leaks that
prevent full reclamation of resources. This is well known for
reference counting GC, but may appear for other GC techniques when
using some data organizations without care (point not discussed
here).
while GC technique may be good at identifying memory resources no
longer used, finalizing objects contained therein may not be simple, and
that complicates the problem of reclaiming other resources used by
these objects, which is often the purpose of finalization.
Finally, an important point often forgotten is that GC cycles can be
triggered by anything, not just memory shortage, if the proper hooks
are provided and if the cost of a GC cycle is considered worth
it. Hence it is perfectly OK to initiate a GC when any kind of
resource is missing, in the hope of freeing some.
Reference counting garbage collectors
Reference counting is a weak garbage collecting
technique, that will not handle cycles properly. It would indeed be
weak on destructing obsolete structures, and reclaiming other resources
simply because it is weak on reclaiming memory. But finalizers can be
used most easily with a reference counting garbage collector (GC),
since a ref-count GC does reclaims a structure when its ref count goes
down to 0, at which time its address is know together with its type,
either statically or dynamically. Hence it is possible to reclaim the
memory precisely after applying the proper finalizer, and calling
recursively the process on all pointed objects (possibly via the finalizing
procedure).
In a nutshell, finalization is easy to implement with Ref Counting GC,
but suffers from the "incompleteness" of that GC, indeed due to
circular structures, to precisely the same extent that memory
reclamation suffers. In other word, with reference count, memory is
precisely as poorly managed as other resources such as sockets, file
handles, etc.
Indeed, Ref Count GC inability to reclaim looping structures (in
general) may be seen as memory leak. You cannot expect all GC to avoid
memory leaks. It depends on the GC algorithm, and on the type
structure information dynamically available (for example in
conservative GC).
Tracing garbage collectors
The more powerful family of GC, without such leaks, is the
tracing family that explores the live parts of the memory, starting
from well identified root pointers. All parts of the memory that are
not visited in this tracing process (which can actually be decomposed
in various ways, but I have to simplify) are unused parts of the
memory that can be thus reclaimed1. These collectors will reclaim all
memory parts that can no longer be accessed by the program, no matter
what it does. It does reclaim circular structures, and the more
advanced GC are based on some variation of this paradigm, sometimes
highly sophisticated. It can be combined with reference counting in
some cases, and compensate for its weaknesses.
A problem is that your statement (at the end of the question):
Languages which offer automatic garbage collection seem to be prime candidates to support object destruction/finalization as they know with 100% certainty when an object is no longer in use.
is technically incorrect for tracing collectors.
What is known with 100% certainty is what parts of memory are no
longer in use. (More precisely, it should be said that they are no longer accessible, because some parts,
that can no longer be used according to the logic of the program, are
still considered in use if there is still a useless pointer to them in the program data.) But further processing and appropriate structures are needed to know
what unused objects may have been stored in these now unused parts of
the memory. This cannot be determined from what is known of the program, since the
program is no longer connected to these parts of the memory.
Thus after a pass of garbage collection, you are left with fragments
of memory that contain objects which are no longer in use, but there
is a priori no way to know what these objects are so as to apply the correct
finalization. Furthermore, if the tracing collector is the
mark-and-sweep type, it may be that some of the fragments may contain
objects that have already been finalized in a previous GC pass, but
were not used since for fragmentation reasons. However this can be
dealt with using extended explicit typing.
While a simple collector would just reclaim these fragments of memory,
without further ado, finalization require a specific pass to explore
that unused memory, identify the objects there contained, and apply
finalization procedures. But such an exploration requires
determination of the type of objects that were stored there, and type determination is also needed to apply the proper finalization, if any.
So that implies extra costs in GC time (the extra pass) and possibly
extra memory costs to make proper type information available during
that pass by diverse techniques. These costs may be significant as one
will often want to finalize only a few objects, while the time and space
overhead could concern all objects.
Another point is that the time and space overhead may concern program
code execution, and not just the GC execution.
I cannot give a more precise answer, pointing at specific issues,
because I do not know the specifics of many of the languages you
list. In the case of C, typing is a very difficult issue that lead to
the development of conservative collectors. My guess would be that
this affects also C++, but I am no expert on C++. This seems to be confirmed by Hans Boehm who did much of the
research on conservative GC. Conservative GC cannot reclaim
systematically all unused memory precisely because it may lack precise
type information on data. For the same reason, it would not be able to
systematically apply finalizing procedures.
So, it is possible to do what you are asking, as you know from some
languages. But it does not come for free. Depending on the language and its implementation, it may entail a cost even when you do not use the feature.
Various techniques and trade-offs can be considered to address these issues, but that is beyond the scope of a reasonably sized answer.
1 - this is an abstract presentation of tracing collection (encompassing both copy and mark-and-sweep GC), things vary according to the type of tracing collector, and exploring the unused part of memory is different, depending on whether copy or mark and sweep is used.
finalize
/destroy
is a lie? There's no guarantee it'll ever be executed. And, even if, you don't know when (given automatic garbage collection), and if necessary context is still there (it may already have been collected). So it's safer to ensure consistent state in other ways, and one might want to force the programmer to do so. $\endgroup$