What is the object pool design pattern? The object pool behavioral pattern allows you to reuse objects instead of destroying them and creating them again. This can help when objects are big and expensive to create or when you just have a lot of them. Over time, this pattern also helps keep your computer memory from becoming fragmented which can cause you to run out of memory.
This design pattern helps solve two big problems that applications encounter with managing memory especially over a long period of time.
- Sometimes objects just take a long time or are otherwise expensive to create. Even if your objects are small and quick, you might sometimes need a lot of them.
- As you use and then free memory, a normal memory manager will build up sections of memory that while available are too small to use for large objects because there are other objects sitting in the middle or between free sections.
With this pattern, you allocate a block of memory when your application starts and use that memory to allocate objects that you don’t delete. Instead of deleting them, you mark them with a flag when they are not being used by some other code and are available. Then you manage this collection of objects yourself. This becomes your pool of objects.
Anytime some code needs one of these objects, instead of allocating a new block of memory for that one object, do this instead to use the object pool:
- Find an available object that already exists in the pool,
- Mark the object as no longer available,
- Initialize it as if it was just created,
- And give it to the calling code.
When the other code is done with the object, instead of deleting it, it gets returned back to the pool. This just means that the object becomes available again.
Listen to the full audio episode or you can also read the transcript below.
if you’d like to read the book that describes this pattern along with diagrams and sample code, then you can get it on Amazon here. For other books and resources that can help you create better software designs, make sure to visit the Resources page where you can find all my favorite books. These are books I recommend to my friends and people that I work with too.
The basic description says that this pattern improves both performance and memory use by reusing objects from a collection instead of creating new instances and then deleting them when done.
We definitely live in a more disposable world now than what life was like 50 or a hundred years ago. We have disposable food wrappers and cups, furniture that’s made from cheap particle board instead of solid wood, even disposable cameras.
While sometimes convenient, this can be expensive and cause problems. In programming terms, it all comes down to bits in the computer memory. So at least we’re not throwing those away too. It is still expensive because when you create a new object, some code needs to run to find room for the object, record the memory to be in use, and then run the constructor. If the constructor also takes a long time to do its job, then you can see where this can add up.
But what kinds of problems can this cause?
For that, let me describe a little about how the main memory in your computer works. Each running application is given a certain amount of memory and the application manages the memory by creating a heap structure to track available and used memory. The memory manager doesn’t know anything about the specific usage needs of your application. It just tries to find a spot in memory big enough to fit some requested memory and when you’re done, it’ll make that memory available again.
Let’s go to a kitchen example again. There’s no cakes this time. We’re going to talk about the cupboards and drawers. Imagine all your kitchen cabinets are empty and when you want to get a plate, or a cup, or a spoon, then one appears on demand.
This is kinda cool. But it comes at a cost. While you’re using this newly created utensil, it continues to claim space in the drawer or shelf where it was first created. At first this is no problem. You’ve got plenty of empty cabinet space and when you’re done for the day, everything empties out and you can start fresh the next day.
Then some important holiday arrives and your whole family comes over to use your magical kitchen. They request spoons, bools, and even large serving trays. Even you are surprised that your kitchen can produce serving trays. Soon though things start appearing all over in the cabinets. You need a plate and it appears at the back of the corner behind the sink.
It all comes crashing to a halt about now when somebody asks for a large dutch oven. You realize that while you have all kinds of room in the cabinets, there’s not a single spot big enough all by itself for that dutch oven. It almost would fit in the spot over the refrigerator except there’s a single used spot claimed by a spoon sitting right in the middle of the shelf. There’s room for both the spoon and the dutch oven but not while the spoon’s in the middle. You can’t even discard the spoon because your uncle is still using it to eat cake. Oh well, I guess I managed to fit some cake in this story after all.
The problem I just described with the magical kitchen is called fragmentation and it’s a real problem with computer memory. Especially after an application has been running for a while.
And if you think I made this whole thing up, well, you’re right. It was fun and hopefully got the point across better than my other example. Just give a small child a clean piece of paper and ask for a small circle or heart shape to be cut out from the paper. How likely do you think the child will be to cut the shape from the edge of the paper? Most kids I’ve seen will go straight to the middle of the paper and claim that spot first. Of course, the paper can’t be used for anything else after this. This is also fragmentation.
So enough of the introduction. I’ll explain how to actually implement this design pattern right after this message from our sponsor.
( Message from Sponsor )
I mentioned two problems with normal memory allocation. It can be expensive to create and destroy objects either because the work needed for each one is a lot or because you need a lot of small objects. And the other problem is memory fragmentation.
The nice thing about this design pattern is that it helps you with both of these problems. It’s actually simple considering the scope of the problems.
Here’s how it works.
When your application is first starting and you have all that memory available to you, that’s when you ask for a large section of memory right away. And if you have any special cases that you want to make sure will always have memory available, then go ahead and claim those blocks of memory too.
Now that you have some memory, what do you do with it? Well, fragmentation is most pronounced when you have different sized objects all trying to work with the same memory and they come and go and leave different sized holes behind in memory. One thing you can do is use your big block of memory for all your little objects. That way, they won’t get spread out through the main heap and cause problems.
Just divide the memory into smaller fixed sized pieces. Why fixed size? Well that way, when an object gets deleted, it leaves behind a hole that’s the same size as the next item that needs some memory.
If you take this approach, what you’ve done is create your own heap for similar sized objects.
This can help but it’s not what this design pattern is fully about.
Let’s say you have a need in your application for hundreds, or thousands, or even more of one of your objects. Not all at the same time though. This design pattern can’t help your computer to suddenly get more memory. It helps you manage your objects and their memory needs over time.
There’s another design pattern called flyweight that I explained in episode 69 that you may want to compare with this one. The flyweight pattern also helps you get more out of many objects. There’s a big difference though. The flyweight does help you fit thousands of objects in memory by showing you how to share as much as possible and calculate the rest of your data needs. This pattern explains how to repeatedly fit many objects into memory over time where they’re coming and going. Imagine a medieval battle with volleys of arrows flying through the air.
The flyweight will show you how to get more arrows into the air at the same time.
This pattern will show you how to repeat that process many more times.
The way you do this is to avoid destroying objects when they might be needed again. You leave the object in your large block of memory and just set a flag to show that it’s no longer in use. Because you didn’t actually destroy the object and because you maintain control over the memory and know not to overwrite the object, it’s waiting to be used again. When you have several of these waiting objects, they form a pool of available objects. When you want to use one, all you have to do is find one that’s waiting and setup its data members for the new usage. Don’t forget to clear the flag because the object is now in use.
This is sort of like how a temporary employment agency works. They have several employees waiting to serve you at a moment’s notice. It just takes a little training and they’re ready for work. And if you get somebody that you’ve already trained, then the person is ready even faster. When the work is done, the temporary worker goes back to the employment agency to wait for the next assignment.
That’s about all there is to this design pattern.
But here’s a few more things to consider.
- Because you allocate and maintain many objects in the pool ready to be used, they do occupy memory even when they’re waiting. You need to adjust your application to use the best pool size. Too little and you run out of available objects and either have to create and destroy them from main memory, or fail the request for a new object, or move to a larger pool. Too large and you have objects waiting around consuming memory that’s never needed.
- How will you initialize objects coming from the pool? Maybe you’ll have a method for this called initialize or sometimes just init for short. If some code forgets to initialize an object, then it might actually work unreliably with previous data values.
- Are your objects designed to be used only with a pool? Giving them an init method and an inUse flag is a big clue that there’s something special going on. This implies that only those objects that have been modified to work with your pool can be members. Maybe this is okay. Or maybe you want any object type to be usable with your pool. If so, then you’ll need to figure out how to initialize, track availability, and clean up when done.