Garbage collection (GC) is a technology that frees programmers from the hassle of explicitly managing memory allocation for every object they create. Traditionally, the benefit of this automation has come at the cost of significant overhead. However, more efficient algorithms and techniques, coupled with the increased computational power of computers have made the overhead negligible for all but the most extreme situations.
Introduction
In fact, in many situations automated memory allocation can be significantly faster and more efficient than traditional approaches. Combined with increased programmer productivity, it would be negligent not to consider automated memory management a core component in nearly all future programming paradigms.
History
Though recent approaches have expanded the feasibility of an automated memory manager, the basic technology has been around for quite some time. GC was first invented by John McCarthy in 1958 as part of the implementation of Lisp. Though it has gained popularity through Java and .NET, it is included in languages such as Eiffel, Haskell, ML, Modula-3, Perl, Prolog, Scheme, and Smalltalk. Traditionally popular programming languages, such as C/C++, require the programmer to “micro-manage” memory consumption. Manual memory management is enormously tedious and error-prone; however, it can be more efficient in many ways if properly handled. This discrepancy in efficiency has slowed the widespread adoption of the automated approach.
Algorithms and Technologies
Though GC can be handled in a variety of ways, the core idea is the same across all techniques. The goal is differentiate between “garbage” and “non-garbage” objects and automatically free the memory that “garbage” objects are occupying. “Garbage” essentially refers to an object that is unreachable by any other currently reachable object. If it cannot be reached, it is not useable any longer and is therefore considered “garbage.”
Reference Counting
Reference counting is a form of garbage collection where each object stores a count of all the other objects that refer to it. Every time an object is assigned or an operation is performed that would alter a reference to an object, the reference count for all involved objects are appropriately updated. If an object’s reference count decrements to zero, then it is effectively unreachable and considered “garbage.” Though this technique provides a good conceptual example, it is rarely used in practice. Reference counting requires frequent updates and it cannot account for cyclical references, where an object indirectly makes a reference to itself. Additionally, though not nearly as troublesome as the first two problems, reference counting requires every object to reserve a special storage space for a reference count.
Mark-Sweep
The mark-sweep collection technique is one of the most basic tracing garbage collection technologies. Tracing garbage collectors start by considering every object to be garbage and then tracing through every reachable object. Anything that is not traced as reachable is remains “garbage.” A mark-sweep collector is able to recognize objects that have already been flagged as reachable and therefore can properly handle cyclical references. After it marks every reachable object, it then scans through all objects in the heap and frees any object not flagged as reachable. The downside of this approach is that the memory becomes fragmented since objects have been removed sporadically throughout the heap. Also, a mark-sweep collection can be quite resource-intensive, since it is forced to scan every object in memory, regardless of whether or not the object is flagged as “garbage.”
Copying
The copying collection technique can be dramatically faster than mark-sweep because it is able to dismiss “garbage” objects without ever scanning or analyzing them. Instead of simply removing “garbage” objects from their position in memory, it relocates all “non-garbage” objects to an entirely new location in memory, trashing everything left-over in the memory. It does this by splitting its allocated memory into two sections: the active section and the unused section. In the active section, objects are stacked on top of each other contiguously, making object allocation extremely efficient. New objects are allocated simply by placing them on top of the existing heap. When it runs out of free memory, the garbage collector traces through all reachable objects and copies them contiguously to the unused section of memory. The active section of memory is still full of reachable and unreachable objects, but that doesn’t matter since all the reachable objects have been copied to the unused section. Then, the active section and unused section switch places, dismissing the previous section of memory. When the new active section becomes full, the process is repeated again and the sections are swapped. This approach has the advantage of providing excellent allocation speed and it virtually eliminates fragmentation. The downside, however, is that long-living objects are continuously swapped between the two sections every time the collector is run. Additionally, it requires twice the amount of memory to run effectively.
Mark-Compact
To account for the benefits of the copying collector while considering memory constraints and long-lived objects, the mark-compact collector was formed. Though it is a bit more complex than copying collectors, the fundamental idea is the same. There is a definite separation between the active section of the memory and the free/unused section. However, unlike the copying collector, the mark-compact collector scans for all reachable objects and compacts them at the bottom of the heap, leaving the remaining memory free for consumption. The catch is that it doesn’t have to fully compact the memory each time, and since long-lived objects tend to get pushed to the bottom of the heap, they are not as frequently collected and compacted.
Generational
To further account for the differentiation between handling short-lived and long-lived objects, the generational approach was developed. In a generational collection, the heap is divided into multiple generations. Objects are created in the base (youngest) generation, and are promoted to higher (older) generations after passing some form of criteria, usually related to the age of the object. Garbage collection can be done at different time intervals and even using different techniques based on the generation the object is in. The only problem with this approach is when an older generation object references a younger generation object. Since the tracing collector does not trace into older generation references, this intergenerational reference gets lost in the mix if it is not referenced by some other object in its own generation. Luckily, there are only two ways for an older object to reference a younger object: either a reference in the older object is modified to refer to a younger object, or a young object that refers to other young objects gets promoted to an older generation. Languages that employ a generational collector account for this problem by maintaining a list of intergenerational references whenever such an occasion occurs.
Parallel Copying
With the advent of multi-processor systems, many garbage collection techniques have needed multi-thread aware implementations. The parallel copying collection is a version of copying collection that runs as many threads as there are CPUs, preventing the workload from being restricted to only one CPU. The standard copying collection is unable to spread the workload to different processors, which creates an unnecessary bottleneck in performance. Compared with traditional copying collection, parallel copying has a potential speed increase by a factor equal to the number of CPUs available.
Concurrent
The concurrent approach was developed to make the application execute as seamlessly as possible alongside (and at the same time as) the garbage collector. In order to accomplish this, it must first halt all other CPU activity to mark all reachable objects. Once it has completed that operation, it creates several garbage collection threads that execute concurrently with the program based on the marked and unmarked objects. Each thread does its part to remove “garbage” objects from memory.
Parallel Scavenge
The parallel scavenge collector is similar to the parallel copying collector; however, it is optimized for large memory heaps (10GB plus) on multiple-CPU systems. The approach is designed to reduce pauses and maximize throughput. It is rarely used on anything but servers.
Finalization
Though not exactly a complete garbage collection technique, it is important to recognize how object finalization is accounted for in managed memory languages. Traditionally, languages without automated memory management (such as C++) provided destructors for their objects to free any resources that the object might consume. The terminology and syntax for finalization is similar, but the implementation is quite different. Since object “destruction” is not always explicit in different garbage collection techniques (ex: copying collection), the compiler must take extra steps to explicitly call a finalization method when an object is freed from memory. Specifically, it must maintain a list of objects with finalizers, determine when those objects are removed from memory, prevent them from being removed from memory, execute their finalize method, and then place them back into memory to be marked as “garbage” and collected once more. As you can no doubt see, finalization defeats many of the benefits of various garbage collection techniques and should only be used when absolutely necessary.
Java Implementation
Java, specifically more recent versions of the JVM, implements a two-generation (young and old) approach with different collection techniques for each generation. The default collector for the young generation is the copying collector and the default collector for the old generation is the mark-compact collector. For multiple-CPU systems, Java provides the option of a parallel copying collector or a parallel scavenge collector for the young generation and a concurrent collector for the old generation. Since the vast majority of newly created objects “die young,” Java’s use of a copying collector on the young generation and a mark-compact on the older generation appears to provide a good mix of technologies for optimal speeds.
.NET Implementation
Like Java, the .NET platform uses a multi-generational approach to garbage collection. However, it has three generations (0, 1, and 2) instead of two, with the ability to expand to even more in the future if necessary. Furthermore, where Java uses different collection techniques for different generations, .NET currently uses the mark-compact collection technique for all generations. Generation 0 is where newly created objects are placed. Objects are promoted to Generation 1 whenever they survive garbage collection on Generation 0. Similarly, when objects in Generation 1 survive garbage collection they are placed in Generation 2 and objects that survive Generation 2 garbage collection are simply compacted within Generation 2. The optimizations determining which generations to compact when are numerous and frequently changing; however, it is safe to say that Generation 0 is the most frequently compacted generation due to the short lifespan of most objects. To further optimize performance, .NET sections off a part of the heap for large objects and never compacts that section in order to prevent the CPU from wasting cycles on shifting large amounts of memory. Though .NET does not vary the techniques for collection between generations, it is clear that considerable amounts of time have been spent fine-tuning the currently employed technique for peak performance.
Conclusion
Although garbage collection can never be as efficient in every way as a well-programmed “micro-managed memory” application, it has the potential to be as efficient or even more efficient in areas that are becoming increasingly important. For example, though garbage collection might require more memory to store and more CPU time to process, it can effectively compensate in increased memory allocation speed and speedier application development. As memory becomes cheaper and CPU’s become faster, the drawbacks become even less noticeable. Furthermore, garbage collectors are continuously being optimized to bring speeds even closer to those of manual memory management. The ratio of benefit over burden for this incredible technology has finally leveled out and will only go up in the future.
References
“Automatic Garbage Collection.” Wikipedia, the free encyclopedia. 6 Apr. 2004 http://en.wikipedia.org/wiki/Automatic_garbage_collection.
Richter, Jeffrey. “Garbage Collection: Automatic Memory Management in the Microsoft .NET Framework.” Microsoft Developer Network. 2000. Microsoft. Nov. 2000 http://msdn.microsoft.com/msdnmag/issues/1100/GCI/default.aspx.
Richter, Jeffrey. “Garbage Collection – Part 2: Automatic Memory Management in the Microsoft .NET Framework.” Microsoft Developer Network. 2000. Microsoft. Dec. 2000 http://msdn.microsoft.com/msdnmag/issues/1200/GCI2/default.aspx.
Goetz, Brian. “Java theory and practice: A brief history of garbage collection.” IBM developerWorks. 2003. IBM. 28 Oct. 2003 http://www-106.ibm.com/developerworks/java/library/j-jtp10283/.
Goetz, Brian. “Java theory and practice: Garbage collection in the 1.4.1 JVM.” IBM developerWorks. 2003. IBM. 25 Nov. 2003 http://www-106.ibm.com/developerworks/java/library/j-jtp11253/.
Goetz, Brian. “Java theory and practice: Garbage collection and performance.” IBM developerWorks. 2004. IBM. 27 Jan. 2004 http://www-106.ibm.com/developerworks/library/j-jtp01274/.
“Question of the month: 1.4.1 Garbage collection algorithms.” Java Performance Tuning. 2003. JavaPerformanceTuning.com. 29 Jan. 2003 http://www.javaperformancetuning.com/news/qotm026.shtml.
About the Author
Zachary Pinter is a student attending the University of West Florida in Pensacola, FL. He is an aspiring programmer, OS enthusiast, and subscribed member of OSNews.
If you would like to see your thoughts or experiences with technology published, please consider writing an article for OSNews.
Many people still think that malloc/free will be faster than GC in all situations. But this is no longer the case. The progress in garbage collection algorithms in the last 10 years has been amazing.
The only thing that is still much faster than a good generational garbage collector is stack allocation. But fortunately .NET has limited support for stack allocation using value types.
I’ve been reading OSNews for a few years now, and this is probably my favorite article that I’ve seen here so far. Good job Zachary.
Nice article. I liked it a lot. I heard the next version of C# (2.0 in Visual Studio 2005) has an improved GC too….
Though this technique provides a good conceptual example, it is rarely used in practice.
Well, while it’s not the best way to go, it’s easy to implement. It’s also the garbage collection model for Microsoft’s COM.
I’m a Java Programmer mainly now starting to get into some .NET stuff too. But in a past life I was a COM programmer. I’ve been bitten by cyclical references before.
the statement that garbage collection time is small. Any non-trivial Application will spend a large amount of time garbage collecting. The main problem with garbage collection is that it wastes memory and causes virtual memory to become a large damper on system performance.
Take the problem of programs that would like to use Virtual memory as a cache. Many programs load their GUI into RAM, the images of widgets, their config files, etc. Rather then read them off disk every time. When not needed and RAM is constrained, the memory this data is loaded into is swapped. Swapping this into RAM is usually faster then loading (and re-parsing the file) off the disk. However, mark and sweep gc routines require that all this data is constantly being swapped back into memory for the sole purpose of garbage collection. This is why java swing programs seem so slow.
When Longhorn comes out we will see how much better/worse programmers will treat their new enviroment.
I think it’s also used for VisualBasic.
And, it is used for OpenStep programming (ie, Cocoa and GNUstep). Strangely, GNUstep also permit boehm gc, but many people just prefers the classic reference counting scheme.
Yeah, VB 4-6 are pretty much built on top of COM.
>off the disk. However, mark and sweep gc routines require
>that all this data is constantly being swapped back into
>memory for the sole purpose of garbage collection.
What do you mean by “constantly”? If you use a generational
GC (which is the default on the Sun Java VMs), most GC runs
happen in the nursery or eden space (the new generation).
This generation does not use Mark/Sweep;
The Old generation does, but GC runs happen much less
often there.
And even that overhead can be reduced to nothing; there
is research on the topic “Pre-Tenuring”, which recognizes
extremely long lived data and optimizes GC for those
(see this paper:
http://cs.anu.edu.au/~Steve.Blackburn/pubs/papers/pretenure-oopsla-…
for details);
>This is why java swing programs seem so slow.
What the…? What kind of statement is that?
How does the occasional swapping of data concern the
perceived speed of GUI apps? And yes, it is
*occasional*, because most of the GC runs happen
on the young generation (again: no mark/sweep there)
and not the old generation.
If you feel your
Swing apps run too slowly, use the “Concurrent” setting
on the JVMs GC to keep GC pauses down to a minimum,
Reference counting is also used for GC in some scripting languages e.g. Perl and Python (I am not sure if python GC mechanism was changed since last time I checked, but Perl definitely counts references – I managed to create a few memory leaks by using circular references in my early days .
though I would have liked the conclusion to contain that in almost all general purpose programs having a garbage collector doesn’t hinder speed at all.
Simply because with manual memory management you do copy the objects much more often to avoid memory bugs – which is of course not anymore necessary with a garbage collector.
cu Martin
> Conclusion
>
> Although garbage collection can never be as efficient in
> every way as a well-programmed “micro-managed memory”
> application, …
Actually, this is not true. A “micro-managed menory” app that allocates 100,000 small items and then frees all but 5 of them takes a small constant C * 100000 to allocate and another (C * 100000) to free. However, a copying GC allocates by simple pointer advancing (b/c it’s got a completely empty heap) 100,000 items is a smaller constant c * 100000, and then determines that there are 5 live objects, takes some larger constant K * 5, and then frees the other 99,995 in ONE INSTRUCTION!
In short, “micro-managed” memory allocates and frees related to the number of items allocate and freed, and the copying GC allocates related to the number of items allocate, but frees related to the number of items left alive.
So in some cases, copying GC can be faster. But even if this is the case, usually GC’d languages are slower for other reasons, too, not just GC (like being interpreted, etc.)
If Java GUI apps seem slow because of this, than its a fault of the Java GC, not a problem inherent in garbage collectors. Functional Objects’ Dylan IDE, which uses the MPS, feels just like a native Windows app on my machine.
See:
http://www.ravenbrook.com/project/mps/
This is very true. Good memory managers that use different policies for different heaps can have extremely good performance for many common workloads. For example, generational GCs are very good for functional code that quickly creates then destroys lots of objects. The overhead is much less than it would be when using a fully general-purpose memory allocator. Depending on your workload, a good GC will be on average as fast as a good malloc(). When you have detailed knowledge about your program’s memory allocation patterns, you can outperform both using customized allocators (eg: pool allocators), but then again, a good memory manager will offer you a range of memory management options in addition to plain GC.
Reference counting is probably the most widely used automatic memory reclaiming technique, it just not very good as a general purpose garbage collectorr. It is used in most object oriented languages or toolkits as a way to keep track of implicitly shared objects, and lots of other places that just needs a fast and secure way to share object between peers (as long as you make sure objects aren’t peers they will not be self-referential).
I would love to see garbage collection as part of broad toolkit of memory managers, but so far as stand alone models they have seriously underimpressed me.
I’m not sure I understood which GC in the list are incremental and which are not incremental?
Incremental GC does not generate annoying pause, as the GC can be stopped any time and continue later without too many trouble (well except that the modified object must be updated).
Whether a GC is incremental is usually pretty independent of the algorithm used. Eg. there are incremental generational copying collectors, stop-the-world copying collectors, etc.
Python still uses reference counting as of 2.3, but it also has cycle detector that runs periodically to prevent memory leaks, since 2.0.
How GC is performed in Java is not guaranteed!
It is not specified in the Java Language Specification and varies by platform and JVM vendor. Even if the official Sun JVM has been observed to do it one way on a particular OS it does not mean that all JVMs (including others from Sun) will do exactly the same on other OSes.
Please be more careful before making such statements.
Very informative and well written. I would have liked to see a little more detail on the methods used by the Java and .NET VMs, perhaps a diagram =)
Thanks for the compliments and critiques everybody. I apologize for any specific details I might have overgeneralized. I certainly do not claim this to be a comprehensive analysis, but rather a “glance at” or introduction. The article was actually a final paper for my Science of Computing class I just completed. I’m eager to hear any further ellaborations or corrections on the material you might have. The different applications and approaches of this technology fascinates me.
Please read “Sun Certified Programmer & Developer for Java 2 Study Guide (Exam 310-035 & 310-027)” by Kathy Sierra and Bert Bates.
Their explanation of Java GC is by far the best I’ve read. In this they explicitly state that the JVM may do ABC or it may do DEF. And even if we reverse engineer the JVM and determine exactly how it does it we cannot be sure that the identical JVM will work that way on other OSes.
I personally have studied the the GC operations on various J2ME implementations (where memory is scarse and needs to be cleaned up often) and can say that the GC runs 90% of the time but that is due to how the vendor (KVM) implemented GC and not on Sun or the Java API.
Your explanations of GC in general are very well articulated and I don’t mean to criticize. I mean only to correct you.
I have a couple big problems with garbage collectors.
They tend to run when memory is scarce. This makes them extremely poor neighbors when running on systems with other applications. In fact a good number of the performance problems I see with Java GUI apps is because they end up eating huge chunks of memory and cause the rest of my machine to start slowing down.
Worse, when they do run their collectors, they run them all at once which pegs your CPU and creates a noticeable hiccup in your application (and your machine). With traditional micro-managing, you spread your CPU cycles over the lifetime of your application.
Now that’s not to say garbage collection isn’t extremely handy, however I’d rather use it to debug my manual memory allocations. After all, the time spent running my applications is significantly longer than the time spent writing them.
> Traditionally popular programming languages, such as
> C/C++, require the programmer to “micro-manage” memory
> consumption.
Traditionally, programmers coming from GC languages have a hard time to grasp the concept that, if you start to write destructors for every C++ class or have numerous “new()”s in your source, your design is broken. In over 90% of the cases, you have no need to use anything but automatic variables and default destructors.
And I like to know when my cleanup functions are called: when the object leaves scope, instead of some time when the GC fires, or never (as might be the case in Java). That allows me to do meaningful things in destructors.
That being said, note that it’s quite easy to “defeat” garbage collectors. One example for Java: Forget to unregister your event handlers before you release a widget, and you got a memory leak.
GC’s are nice, but I prefer them to be *optional*.
Any GC mechanism that depends on finalizers as the only object clean-up method trades an irritant with a real problem. The irritant is memory management. The problem is resource management — semaphores, files and so on. One of the clean, effective and easy solution is to use constructors and destructors to manage the allocation and deallocation of these resources. This garanties proper deallocation at the proper time, without ever risking accessing deallocated or never-allocated objects. This idea is often nicknamed RAII (Resource Allocation Is Initialization). This technique cannot be used with finilizers, however, which may never be run (at all!)
This does not mean that GC is bad. C++ (which seems to survive everything, beats-me-how) now has several GC availble, including some very interesting “conservative” collectors, which garanties that destruction occurs exactly when the object goes out-of-scope. This is, IMHO, the way to go. This way, wrapper objects can be made which handles nearly resource management — not just memory.
This is not about performance, though I admit that Java and .NET has problems in this regard. This is about correctness, which is far less easily solved.
Finalisers ( user defined “destructors”, a la C++ ) are bad for GC managed languages :
– Usually, the precise instant of destruction is random, as well as the order of destructions between several objects. Reference counting GCs are more “predictable”(?) than Mark/Sweeps ones.
– The “finalised” objects need to pass trough the collector twice as the finaliser may want to make the object uncollectable.
I recommand the reading of the classical paper “Uniprocessor Garbage Collection Techniques” by Paul R. Wilson, available as draft and full text, see http://citeseer.ist.psu.edu/wilson92uniprocessor.html, http://www.cs.utexas.edu/users/oops/papers.html
> C++ (which seems to survive everything, beats-me-how)…
Versatility. The very feature that makes it one of the ugliest bitches in the programming language arena also makes it applicable for just about everything…
Good article about garbage collectors.
Last year I switched from C++ to OCAML programming which uses a fast hybrid generational/incremental garbage collector. It’s much more fun just to code without the need for doing the memory mangement myself than to debug memory leaks and segfaults and desctructors. In most real applications ocaml-code is as fast as c-code, so the gc comes for free.
Traditionally, programmers coming from GC languages have a hard time to grasp the concept that, if you start to write destructors for every C++ class or have numerous “new()”s in your source, your design is broken. In over 90% of the cases, you have no need to use anything but automatic variables and default destructors.
This is true for the styles of programming common in C++. However, in lots of functional code, you need indefinite extent for objects, and GC is the only sane way to do it. Can you imagine trying to manually manage memory for closures?
Also, languages other than Java also have non-GC related cleanup methods available. It is very common for a Lisp or Dylan programmer to define a macro “with-open-file” (or whatever resource you want managed, and use it as such:
with-open-file (fs = “foo.text”, element-type: <byte>)
read-to-end(fs)
end;
(Example stolen from fun-o’s website
This technique can be generalized to any resource you want, and can even be used to manage locks automatically:
with-held-lock(lock)
do-stuff();
end;
So the lack of determinite finalization really isn’t a significant barrier to anyone except those coming from a C++ background
Conservative collectors do not guarantee determinite deallocation. Conservative collectors simply let you use GC with a non-cooperative language, by assuming that if a particular word looks like a pointer (regardless of whether it may just be an integer), it should be treated as a pointer into the heap. This is not a greatest way to do GC, but it performs pretty well in practice.
What you’re thinking of is something like boost::shared_ptr, or one of the other resource-managing templates. They are good within the confines of C++, but they are much more cumbersome to program (C++ really doesn’t make the wrapper transparent except for at invocation), and perform poorly compared to a real GC.
I have a couple big problems with garbage collectors.
They tend to run when memory is scarce.
Not if you use an incremental collector.
> > off the disk. However, mark and sweep gc routines require
> > that all this data is constantly being swapped back into
> > memory for the sole purpose of garbage collection.
>
> What do you mean by “constantly”? If you use a generational
> GC (which is the default on the Sun Java VMs), most GC runs
> happen in the nursery or eden space (the new generation).
Pages with eden are seldom swapped out since they are constantly in use.
> The Old generation does, but GC runs happen much less
> often there.
In GUI design the rule of thumb is that reaction latency should never exceed 0.1 seconds. (This is because of our perception of cause and effect, and keeping the cognitive load at a minimum.) It’s not OK if a GUI program has 2-3 second pauses every minute, and a JVM that pauses for a minute several times per hour is totally unacceptable for GUI programs. Now combine this with the poor VM performance of linux (especially 2.6) and you get a disaster. I’ve experienced several GC pauses of half an hour and one of about an hour, during which the linux POS is totally unusable.
wouldif be useful to have automatic gc, but exclude it from certain parts of your code, in an exclusion lock, for when you know that you don’t want to be interrupted by a gc thread? this complements the hints/invokes to a gc round …
> Traditionally, programmers coming from GC languages have a
> hard time to grasp the concept that, if you start to write
> destructors for every C++ class or have numerous “new()”s
> in your source, your design is broken. In over 90% of the
> cases, you have no need to use anything but automatic
> variables and default destructors
Very true! GC programmers tend to think that for every single bit of information, you have to do an allocation. Well, I don’t (like) to do OO and even less GC, just for the reason that it breeds BAD programming habits.
Any real good C programmer knows that you estimate the size
of any ‘object’ and then do the allocation in one chunk. If you can’t (because it is too large) you do another algorithm, but you’re not gonna fragment your memory. If it is memory conservation over speed you can do a realloc(). If not, who cares.
For my own compiler, I estimate the size of the objectcode during tokenizing, so don’t say it can’t be done. If you really can’t tell where you’re going you can always use the ‘increase allocation’ technique, where you start allocating a chunk (say 10K) and every other allocation is increased by a certain percentage (say 50%), so the next allocation is 15K.
However, this depends on the problem you have to solve. I doubt very much that an OO/GC programmer can beat the speed of my C programs. That doesn’t mean that there is no niche for GC languages, but don’t say they’re more efficient: they’re not and they never will be!
Still beats me that someone can ‘forget’ what he allocated..
Perhaps you should try some real world server programming. With virtual CPU emulators like valgrind finding a memory leak in C/C++ now a days is pretty trivial. People have been saying for the last 10 years that GC is just as good as new/delete. Although I have yet to see anything I can use that has the same performance as C and has GC.
The most often overlooked form of memory management is memory pooling. It’s not a universal technique, but it works in many situations, and is used in Samba and Apache at least.
Pooling is lifecycle-based memory management. When you know which parts of your application’s lifecycle a structure will be in, you can create a “pool” of memory which will all be freed at the end of that part of the lifecycle.
For example, Apache has memory pools for the lifetime of the server, the lifetime of the connection, the lifetime of the request, and others. If you allocate memory that you know will not be used past the end of the request, you allocate it in the request pool. Then, all objects in the request pool are freed simultaneously at the end of the request. This is by far the fastest method of memory management, because mallocs() and frees() are constant-time and very fast. Pool allocation is about the same speed as normal malloc().
There are two reusable implementations of this I’m aware of: Apache’s portable runtime and GNU’s obstack library. However, it’s a very powerful technique if your application fits that model.