Garbage Collection and Creating Objects

In many other programming languages, a programmer will specifically allocate memory in the loading stage of the game before the actual game bit begins. During the actual game (by this we mean the fast rendering, fast action part as opposed to the game menu area), the process of allocating new memory will be kept as minimal as possible; preferably, all memory will be allocated before the main game starts, as memory allocation is time consuming. An example of memory allocation would be when loading in an image; memory is allocated for the data of the image (e.g., its pixels), and then the image data is loaded into this memory. When memory is finished with (for example, that image is no longer required), the programmer will specifically deallocate the memory, usually after the level of the game is complete, and move onto the next level stage.

In Java, we are most concerned with creating objects. When an object is created, new memory for the object is allocated and the object is controlled under the watchful eye of the garbage collector. The garbage collector is a mechanism used for handling objects created in your program environment. Therefore, it is not specifically up to you, the programmer, to deallocate the memory of any objects you create. Let's take the following method as a very basic example:

public void myMethod() {     Object myObject = new Object(); }

When the object is created, its lifetime is controlled internally. Basically, the garbage collector watches for when the object loses its scope, and when it is no longer accessible from any references in your program, the object is lost. What needs to happen then is that the object's memory needs to be deallocated and eventually the object is destroyed once and for all since the object is of no further use once the method exits. However, the object would have further use if you assigned another variable with scope outside of this method to reference that object, which means it would not be garbage collected, as something else would still reference it in your program.

Objects can be created from anywhere in your program. Sometimes you won't even be aware of it (for example, when you specify a string literal, like "This is a string literal," this is really a String object like any other that you might have created using a string class constructor). Objects can also be created from methods of classes and objects in the standard Java packages, such as the getBounds method of a Component object (or a derived class of Component). Take the following code using the Component object myComponent as an example.

int x = myComponent.getBounds().x; int y = myComponent.getBounds().y;

Each call to the component method getBounds is actually creating a new Rectangle object. So in this code, we have created two new Rectangle objects, when all we needed was two integer variables. The objects created will be up for garbage collection right away, as we no longer hold a reference to the newly created objects after each line of code (provided the getBounds method doesn't hold on to the new objects created internally).

There are two immediate problems with creating objects consistently in the main loop. The first is that when many objects are garbage collected, it means that the garbage collector needs to do more work. As the garbage collector is running in a thread itself, the more it runs, the less processor share we have for the game logic and rendering, etc. The effect of this is that noticeable pauses can occur in the game's rendering, which basically means pauses in the main loop's execution as the garbage collector takes an increased gulp of the processor time. A new addition to J2SDK 1.4.1 is the command-line flag -XX:+UseConcMarkSweepGC that turns on concurrent garbage collection, whereby it uses more processor time in order to reduce garbage collection pause times. This command-line flag can be added when running your program as follows:

java -XX:+UseConcMarkSweepGC MyMainClass

The other problem is more severe and can come in the event that you create many objects in the main loop and don't allow the garbage collector to collect and remove these objects appropriately. The result is that you run out of memory and an OutOfMemoryError error is thrown. This is the reason why we always sleep in the main loop for at least 5 milliseconds—so that we do not starve other threads, such as the Garbage Collector.

Object Pooling

As creating and garbage collecting objects can be a serious bottleneck in the main loop, it is obviously best to do this as little as possible. However, a lot of the data structures that we need will be dynamic and require adapting through creating more objects or removing the ones you have, as opposed to just creating a set number of objects and using them. But that's the key right there; instead of creating, using, and removing (where the objects will be garbage collected), we can create and use without letting go of our objects. For this, we can create what is known as an object pool. The idea is that a set number of objects are created and added to the pool. These objects are now available to be used in your main game, grabbed from the object pool. Once the object has finished its use, it can then be returned to the object pool, ready for reuse later on, perhaps when a new object is needed, instead of it being garbage collected and newer objects being created when they are needed again. The emphasis is on object reuse.

The following code is a basic illustration of how an object pool might be implemented:

public class ObjectPool {     public ObjectPool(int size)     {         list = new Object[size];     }             public void dump(Object o)     {         if(currentObject<list.length)             list[currentObject++] = o;         else             System.out.println("List is full");     }             public Object grab()     {         if(currentObject > 0)         {             Object newObject = list[--currentObject];             list[currentObject] = null;             return newObject;         }         else         {             System.out.println("List is empty");             return null;         }     }           private Object list[];     private int currentObject; }

When creating the ObjectPool, we define the size of the list in which to store objects. The format for the list is simply an array. The list would then need to be filled with an initial set of objects, which may be added using the dump method. Then when an object is required, it can be returned through a call to the grab method and removed from the object pool. When the object is finished in the main game, the object can then be passed back to the object pool using the dump method once again. Of course, you will need to specifically handle when a new object is required and when it is no longer needed in your code.

When an object is obtained from the pool, its data may also need to be reinitialized. An initialization method can be defined to mimic what the constructor would usually do. The variable currentObject is used to handle the current index position in the pool of available objects, with the next available object stored at index currentObject - 1. Note that an object pool is only suitable for objects of the same type (or same base type, perhaps) but not for mixed types of objects. So if you wanted to store both Alien objects, and MagicStar objects for example, which are imagined to be completely different types of objects, you would need an object pool for each. Note that for new objects needed occasionally rather than every loop cycle, it is probably more beneficial to create the objects as usual than to go through the motions of using an object pool.

Notice that in this code we have print statements indicating if the list is full or empty. Both of these instances should not occur if you have added the maximum possible number of objects that you expect to use into the pool, but this may not be something that is easily estimated in your game. If this is the case, the list may be resized in the event of underestimating the amount of objects required. In the ObjectPool class to support this, a dynamic list object, such as java.util.LinkedList, would be more suitable for the object pool implementation.

There are other, more simplistic means of avoiding removing and recreating new objects in general. Suppose you have a list of "baddies." When one of the baddies is killed, it needs to be removed from the game. Instead of removing the baddie from the list (which would be expensive when removing an object from an ArrayList/Vector), and even dumping it back in an object pool, an isAlive or isActive state could simply be set in the object. This indicates to accessing elements of the game loop, such as rendering and collision detection, that they can ignore that element in the list, as the baddie is labeled dead. The downside to this is that unnecessary accessing and checking is performed continuously in the list. If you had a list of 100 baddies and only two were still alive, it would be a large waste to check through the entire list searching for live baddies.

Note 

The use of the finalize method should be avoided because it can prolong the object's lifetime when it is ready to be garbage collected. When it gets to the final stage, removal, if the finalize method has been overridden, the object's destruction is delayed as the object's finalize method needs to be queued for invocation. Although the finalize method is convenient, in general you should know at what stage in your code you are finished with an object and should provide the cleanup functionality for it yourself at that point.

The final note is on creating many small objects. The more objects that are created, the more work the JVM has to do to look after the larger group of objects. This involves checking to see if the objects are still visible (i.e., if any more references exist to that object, and if not, the object needs be garbage collected). This is something to be aware of, as in many circumstances a collection of objects could actually be defined in fewer objects. For example, a list of simple screen elements in your game might only be defined by individual x and y coordinates but share the same rendering code. Instead of creating an object for each of these screen elements, encapsulating the x and y coordinates, and rendering code into a class structure, the elements might be better handled as a group. Say you had 100 of these elements, which would mean using 100 objects; you could store each of the x and y values in arrays. An array is itself an object, but this would mean having two large objects in the place of 100 small ones. The rendering code could be adapted to pick these values out of the array for drawing. Of course, this system is not suitable for all types of grouped, similar objects and would become more complex the larger the number of attributes involved.



Java 1.4 Game Programming
Java 1.4 Game Programming (Wordware Game and Graphics Library)
ISBN: 1556229631
EAN: 2147483647
Year: 2003
Pages: 237

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net