Quantcast
Channel: devahead BLOG » Android
Viewing all articles
Browse latest Browse all 5

Recycling objects in Android with an Object Pool to avoid garbage collection

$
0
0
Source code (LICENSE)

If you need to reduce the work of the garbage collector in Android, like in games development for example, all you can do is reduce the amount of objects you create and recycle those you’ve already created. One of the ways to do this is the implementation of the Object Pool design pattern.

An Object Pool could be implemented in many different ways depending on the needed features. What I’m going to show here is an implementation that respects the following requirements:

  • during its execution, the Object Pool doesn’t allocate new objects other than those contained in the pool itself (we prefer plain arrays instead of Java collections);
  • the Object Pool must be efficient in terms of execution speed;
  • the Object Pool is responsible for initializing and finalizing the objects it contains (the objects must be ready to use when retrieved from the pool and they must free any unneeded resource when they are put back in the pool);
  • if there’s no more room for new objects in the pool, then new objects must be created anyway if requested, but they’ll not be stored in the pool when freed;
  • the Object Pool must be able to handle every type of Object (we only ask the objects to implement an interface to make them ready for the pool);
  • sharing the pool between multiple threads doesn’t have to be a problem.

And now, straight to the source code. We start with the definition of the PoolObject interface:

/**
 * Interface that has to be implemented by an object that can be
 * stored in an object pool through the ObjectPool class.
 */
public interface PoolObject
{
	/**
	 * Initialization method. Called when an object is retrieved
	 * from the object pool or has just been created.
	 */
	public void initializePoolObject();
	
	/**
	 * Finalization method. Called when an object is stored in
	 * the object pool to mark it as free.
	 */
	public void finalizePoolObject();
}

This is the interface that an object must implement to let the pool perform two operations: the initialization and the finalization. We use an interface so we can easily extend every other class and make the pool able to handle different objects.

The Object Pool creates the needed objects through a factory class that implements the PoolObjectFactory interface:

/**
 * Interface that has to be implemented by every class that allows
 * the creation of objectes for an object pool through the
 * ObjectPool class.
 */
public interface PoolObjectFactory
{
	/**
	 * Creates a new object for the object pool.
	 * 
	 * @return new object instance for the object pool
	 */
	public PoolObject createPoolObject();
}

The factory class must decide how to create a new PoolObject instance whenever the pool needs it.

All we need to see now is the ObjectPool class, so let’s take a look at it:

/**
 * Object pool implementation.
 */
public class ObjectPool
{
	protected final int MAX_FREE_OBJECT_INDEX;

	protected PoolObjectFactory factory;
	protected PoolObject[] freeObjects;
	protected int freeObjectIndex = -1;

	/**
	 * Constructor.
	 * 
	 * @param factory the object pool factory instance
	 * @param maxSize the maximun number of instances stored in the pool
	 */
	public ObjectPool(PoolObjectFactory factory, int maxSize)
	{
		this.factory = factory;
		this.freeObjects = new PoolObject[maxSize];
		MAX_FREE_OBJECT_INDEX = maxSize - 1;
	}
	
	/**
	 * Creates a new object or returns a free object from the pool.
	 * 
	 * @return a PoolObject instance already initialized
	 */
	public synchronized PoolObject newObject()
	{
		PoolObject obj = null;
		
		if (freeObjectIndex == -1)
		{
			// There are no free objects so I just
			// create a new object that is not in the pool.
			obj = factory.createPoolObject();
		}
		else
		{
			// Get an object from the pool
			obj = freeObjects[freeObjectIndex];
			
			freeObjectIndex--;
		}

		// Initialize the object
		obj.initializePoolObject();
		
		return obj;
	}

	/**
	 * Stores an object instance in the pool to make it available for a subsequent
	 * call to newObject() (the object is considered free).
	 * 
	 * @param obj the object to store in the pool and that will be finalized
	 */
	public synchronized void freeObject(PoolObject obj)
	{
		if (obj != null)
		{
			// Finalize the object
			obj.finalizePoolObject();
			
			// I can put an object in the pool only if there is still room for it
			if (freeObjectIndex < MAX_FREE_OBJECT_INDEX)
			{
				freeObjectIndex++;
				
				// Put the object in the pool
				freeObjects[freeObjectIndex] = obj;
			}
		}
	}
}

The structure of this class is very simple. There are only three methods, the constructor and two other methods to create and free the object instances.

The constructor accepts two parameters, the factory instance and the maximum number of objects that can be stored in the pool.

The newObject method retrieves an object from the pool and returns it to the caller after initializing it. If there are free objects in the pool, then one of those is returned, otherwise a new one is created through the factory. In this way we put a limit on the maximum number of instances in the pool, but only the needed amount is actually created.

The freeObject method puts an object instance back in the pool making it available for subsequent calls to newObject. Before storing the object in the free objects array, its finalization method is called to let it free any unneeded resource if necessary. If there’s no room in the pool for the object, then only its finalization method is called.

Both the newObject and the freeObject methods are synchronized so the interaction between the pool and different threads is managed correctly.

We have all we need, so let’s take a look at an example to see how our new tool can be used and what is its benefit. Let’s say that we need to work with Point instances for a graphic application. We need to have many points, but we want to avoid to keep on creating them and try to reuse the instances if possible. We need to define a PoolObject first:

import android.graphics.Point;

import com.devahead.util.objectpool.PoolObject;

public class PointPoolObject extends Point implements PoolObject
{
	@Override
	public void initializePoolObject()
	{
		x = 0;
		y = 0;
	}

	@Override
	public void finalizePoolObject()
	{
		// No finalization needed
	}
}

Our PointPoolObject extends the Point class, so it’s a point in fact, but also implements the PoolObject interface. Whenever a point needs to be initialized, it must make sure that its x and y coordinates are both set to zero. There’s no need for a finalization since there are no resources to free.

A PointPoolObject instance is created through the PointPoolObjectFactory class:

import com.devahead.util.objectpool.PoolObject;
import com.devahead.util.objectpool.PoolObjectFactory;

public class PointPoolObjectFactory implements PoolObjectFactory
{
	@Override
	public PoolObject createPoolObject()
	{
		return new PointPoolObject();
	}
}

This simple factory doesn’t need to do anything particular, but just create a new instance of the PointPoolObject class. If additional steps are needed to create a new instance, you can perform them here in the createPoolObject method.

The following code is our example application to test the Object Pool:

import android.app.Activity;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Debug;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;

import com.devahead.androidobjectpool.poolobjects.PointPoolObject;
import com.devahead.androidobjectpool.poolobjects.PointPoolObjectFactory;
import com.devahead.util.objectpool.ObjectPool;

public class MainActivity extends Activity implements OnClickListener
{
	private Button testNoPoolBtn;
	private TextView testNoPoolText;
	private Button testHalfPoolBtn;
	private TextView testHalfPoolText;
	private Button testFullPoolBtn;
	private TextView testFullPoolText;
	private Button testExceedPoolBtn;
	private TextView testExceedPoolText;
	
	private ObjectPool pointObjectPool1;
	private ObjectPool pointObjectPool2;
	private ObjectPool pointObjectPool3;
	
	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		testNoPoolBtn = (Button)findViewById(R.id.testNoPoolBtn);
		testNoPoolText = (TextView)findViewById(R.id.testNoPoolText);
		testHalfPoolBtn = (Button)findViewById(R.id.testHalfPoolBtn);
		testHalfPoolText = (TextView)findViewById(R.id.testHalfPoolText);
		testFullPoolBtn = (Button)findViewById(R.id.testFullPoolBtn);
		testFullPoolText = (TextView)findViewById(R.id.testFullPoolText);
		testExceedPoolBtn = (Button)findViewById(R.id.testExceedPoolBtn);
		testExceedPoolText = (TextView)findViewById(R.id.testExceedPoolText);

		testNoPoolBtn.setOnClickListener(this);
		testHalfPoolBtn.setOnClickListener(this);
		testFullPoolBtn.setOnClickListener(this);
		testExceedPoolBtn.setOnClickListener(this);
		
		// Create object pools with a maximum of 100 PointPoolObject
		pointObjectPool1 = new ObjectPool(new PointPoolObjectFactory(), 100);
		pointObjectPool2 = new ObjectPool(new PointPoolObjectFactory(), 100);
		pointObjectPool3 = new ObjectPool(new PointPoolObjectFactory(), 100);
	}

	@Override
	public void onClick(View v)
	{
		if (v == testNoPoolBtn)
		{
			testNoPool(100, testNoPoolText);
		}
		else if (v == testHalfPoolBtn)
		{
			testPool(pointObjectPool1, 50, testHalfPoolText);
		}
		else if (v == testFullPoolBtn)
		{
			testPool(pointObjectPool2, 100, testFullPoolText);
		}
		else if (v == testExceedPoolBtn)
		{
			testPool(pointObjectPool3, 120, testExceedPoolText);
		}
	}

	private void testNoPool(final int POINTS_COUNT, TextView textView)
	{
		Point point;

		Point[] points = new Point[POINTS_COUNT];

		Debug.startAllocCounting();
		
		for (int i = 0; i < POINTS_COUNT; i++)
		{
			point = new Point();
			
			points[i] = point;
		}

		for (int i = 0; i < POINTS_COUNT; i++)
		{
			point = new Point();
			
			points[i] = point;
		}
		
		Debug.stopAllocCounting();
		
		int ac = Debug.getThreadAllocCount();
		
		textView.setText(ac + " ac");
	}
	
	private void testPool(ObjectPool objectPool, final int POINTS_COUNT, TextView textView)
	{
		PointPoolObject point;

		PointPoolObject[] points = new PointPoolObject[POINTS_COUNT];

		Debug.startAllocCounting();
		
		for (int i = 0; i < POINTS_COUNT; i++)
		{
			point = (PointPoolObject)objectPool.newObject();
			
			points[i] = point;
		}

		for (int i = 0; i < POINTS_COUNT; i++)
		{
			point = points[i];
			
			objectPool.freeObject(point);
		}
		
		for (int i = 0; i < POINTS_COUNT; i++)
		{
			point = (PointPoolObject)objectPool.newObject();
			
			points[i] = point;
		}
		
		Debug.stopAllocCounting();
		
		int ac = Debug.getThreadAllocCount();
		
		textView.setText(ac + " ac");
	}
}

As you can see, there are four buttons. We test how many instances are created in different situations, but the problem to solve is always the same: we need a certain amount of Point instances that have to be ready to use at the same time and we need to do this twice. We want to see what happens with or without the Object Pool and what happens with the pool if the total amount of needed instances at the same time is 50, 100 or 120 while having set the maximum size of the pool to be 100.

In the image above you can see the test results. What does this mean?

  • no Object Pool: 200 instances are created because we need 100 instances for two times and we cannot reuse them;
  • Object Pool and 50 instances needed: only 50 instances are created because the second time we need them we can just reuse those that have previously been created and are still in the pool (the pool maximum limit is never reached);
  • Object Pool and 100 instances needed: 100 instances are created filling the pool and the second time we need them we can use those inside the pool previously marked as free;
  • Object Pool and 120 instances needed: in this case 140 instances are created because the pool maximum limit is 100 and the other 20 instances are created anyway by ObjectPool, but there’s no room in the pool to store them when they’re not needed anymore; since we need the 120 instances twice, the second time 20 more instances are created so a total amount of 40 instances is created outside of the pool.

We tested that the Object Pool lets us recycle object instances and creates only the objects that are needed at the same time avoiding to create more useless instances. If you keep on calling the same methods you can understand that without an Object Pool the total amount of instances that the garbage collector needs to collect is potentially endless and this means a lot of CPU time wasted in collecting instances, while the Object Pool keeps the objects creation under control and the garbage collector may not need to be executed at all.

This is just a way to implement the Object Pool design pattern and it’s made to be simple and avoid the creation of any object other than those stored inside the pool while it is used. If you need different features you can modify the proposed architecture to suit your needs.

If you want to take a look at another performance test of this Object Pool implementation and many other tests on the Android platform, you can check my other post titled “Coding for performance and avoiding garbage collection in Android“.


Viewing all articles
Browse latest Browse all 5

Trending Articles