Destructors do not free the memory of the object being destroyed, but they can free memory that its fields point to.
But it's rarely a good idea to piggyback. Most resources are much more limited or time sensitive than memory. So you should free them immediately.
Even when your resource -is- memory it sometimes is a very very very bad idea to free via garbage collection.
Reference counting almost gives you as soon as, but it has trouble handling circular references. That is fixable, but not, AFAIK, in a way that’s both reasonably performance, frees resources as soon as they become unreachable, and isn’t too restricting.
The fix you refer to is one, but it falls under ‘not reasonably performant’.
One could also make it illegal to have cycles (easy way to do that: make it illegal to update any references). For many, many, that will fall under ‘too restricting’, though.
Alternatively, allow updates to references, but give every allocated object a ‘bit’ that indicates whether, from it, a reference can be reached that contains such an updated reference that points to an object created later (maintaining that property is doable, but may not be ‘reasonably performant’)
You can use that later: when the reference count of an object goes to zero, and the object has a reference to an object O with that bit set, check whether the graph reachable from O can be reached from elsewhere. Again, that’s doable, but likely not performant.
It somewhat depends on how common circular references are in real world code, though. I’m not aware of any papers researching that.
Historically, operating systems use buffers for IO and closing the file includes flushing the buffers of in-flight data (in addition to things like deallocating pointers to files at the OS level). Without explicit instructions the operating system doesn't know when a file is no longer in use.
A programming language/environment could open and close files on every read and every write automatically. But it would entail overhead for operating system calls to allocate/deallocate nodes in the file table. For some types of programs this might be ok. For others it might be problematic, particularly those with moderate or strong dependence on IO.
For a very small benefit of "automatic closing of resources" this looks like a huge trade-off.
And it's easy to determine when the use of the resource is complete: when there are no active pointers to the resource (the circular reference problem I don't think can happen with external resources like files).
with f = open("data.txt")
print f.readline()
The file is automatically closed at the end of the block. Those languages also have explicit open/close/seek calls to let the developer implement more complex cases.Btw, the os closes any open file when the program ends.
// try, catch, error handling, etc.
// omitted for clarity
friendly_write(file,data) {
open(file)
write(file, data)
close(file)
}
It would be an engineering tradeoff of code brevity for possibly lower IO performance. Sometimes it might be worth it. Other's it might not. Java's design reflects the engineering opinion in favor of finer grained control. This is consistent with it's heritage in the line of Algol inspired languages.https://en.wikipedia.org/wiki/Finalizer
Files specifically are a bit thorny because the OS generally places a limit on how many simultaneous open files a process can have, and you don't want to risk bumping in to that limit due to unpredictable garbage collection timing.