C programming is tricky. You have almost complete control of the machine but you have to rely on disciplined behaviour to maintain a handle on things. What happens if you get it wrong? You get resource leaks and abuse. The resources aren't constrained to a finite list of operating system resources, but from library resources or your own programs resources too. But the biggest, most notable culprit of all, is the raw resource that nearly every computing resource is made from, memory.
C with Classes addressed this by adding support for resource management directly into the language with constructors and destructors that automatically initialise and release class resources. The language has since evolved into C++ with further programmer assistance. But even with better assistance, without disciplined use, you're still in danger of committing the 'memory leak' error.
Without thinking too much about the problem, Java and C# have adopted C++'s syntax and style, but made the world a better place by solving the 'memory leak' problem by relieving the programmer of that duty. The supposed saviour? Garbage Collection.
The C virtual computer has a heap and a stack. Local variables occupy space on the stack where functions are run. When the function returns, the stack and all the variables in it are cleared down. The heap provides a store of memory where blocks can be given out on request and returned when complete. So what can go wrong with heap usage?
Handling NULL
If a request is made for a certain amount of memory, the heap can return NULL, meaning the request cannot be satisfied. Careless code will not check for this NULL and proceed as if the allocation succeeded, writing data into the NULL location.
Help comes in many forms. The runtime memory layout is changed where possible so that the NULL address maps onto something helpful. In DOS, it maps onto a string that has a helpful message, just encase the memory is first viewed. In a protected mode environment, a page fault is generated. C++ provides exceptions, and a standard exception, bad_alloc, when an allocation fails.
Buffer Overwrites
An application can ask for 10 bytes, and write beyond this range. This typically damages the heap in some way, affecting future allocations. Debug heaps are available that assist in the catching of these errors; such as Microsoft C++'s Debug heap, or ValGrind on Linux or UMEM on Solaris. But these are debug measures. In production systems, these error are hard to catch. The C++ class goes a long way in containing these errors as the error may be confined to a class, making it easier to detect and fix.
Dangling References
When the program is finished with a heap allocation, it should return it, making that memory available to other parts of the program. If the memory is returned to the heap, but the pointer not cleared, you can continue to use the memory inadvertently. The discipline of clearing pointers after use goes a long way to removing this error. C++ classes again mitigate by confining the use of pointers to smaller sections of code that typically release memory in a destructor, deleting the pointer as well as the memory.
And finally, Garbage
If an allocation is obtained from the heap and the pointer to the block is lost, the memory is lost to the program for the duration of the run. We call this garbage.
Garbage collected languages expect you to ask for memory and to loose the pointer to it when you're done. The heap detects the garbage condition and marks is as ready for release. Brilliant, a whole class of problem has disappeared. So what's the problem?
The problem is that memory is the raw material that resources are made off, not the ultimate resource at all. If the actual resource is a socket connection to a server, the socket can fall into the CLOSE_WAIT state and never be released for the run of the program (just like memory garbage). The resource could be a scarce graphics device object that take resources from the graphics system, or a file handle that takes resources from the file system, and on, and on. Memory, of itself, is rarely the ultimate reason for obtaining memory and so cannot be the focus of its return.
The C++ destructor, a language defined construct to do exactly that, does exactly that in C++. Used well, C++ can eliminate all of these resource errors, as well as heap errors.
Garbase collected languages move the responsibility of releasing resources back the the programmer. The programmer can no longer rely on the resource cleaning itself up, this is a major set back to the procedural programming ways.
Garbase collected systems have no notion of ownership of objects. No one or no thing owns resources. They are created and passed around with free abandon. This makes static analysis of the lifetime of objects impossible to perform. At best, you can only identify where an object was created. You can never be sure if it's deleted where or when it ought to be.
Garbage collected languages leak resources where that resource is not just memory and resorts to placing the burden on the programmer to release resources, reinventing the resource leak.