In Java, dynamic allocation of objects is achieved by using the new operator. Once an object is created, it uses some memory which remains allocated until it is garbage collected. When there are no references for the object, it is assumed to be no longer needed and it becomes eligible for the garbage collector. Collecting happens automatically during the lifetime of the program.
One way to influence garbage collection is to pay close attention to when an object is collected. Letting an object go out-of-scope is the natural way to garbage collect. Alternatively, you can explicitly set the one variable referencing an object to null, and thereby force the garbage collector to collect the object immediately rather than waiting for the next memory full event. The question is, does this actually increase or decrease total system performance?
int numTests = 1000000; System.gc(); try { Thread.currentThread().sleep(1000); } catch (Exception e) { } byte[] b = new byte[10000]; final long l = System.currentTimeMillis(); for (i = 0; i < numTests; i++) { b = new byte[10000]; b = null; }
The test takes 26.290s, but 26.158s without the explicit null. Thus there is a slight penalty for explicitly setting a variable to null, but the penalty is not significant. So set a variable to null if that makes your code more clear and readable, but not as a performance optimisation. In general, you should let the garbage collector do its job without interference. A modern generational garbage collector like Series 40 is difficult to improve on.
The results are slightly reversed when we are dealing particularly with large objects. If we drop the number of allocated objects to 10% and increase the size of each allocation by the same amount:
int numTests = 100000; .. for (i = 0; i < numTests; i++) { b = new byte[100000]; b = null; }
We get with null: 24.807s, without null: 25.112s. So by decreasing numTests by a factor of 10 and increasing the size of each object allocated by a factor of 10, we are allocating the same total memory, but in roughly half the time. And unlike our previous test with many small objects, when the objects are very large it is better to explicitly set them to null as this is cheaper than invoking the garbage collector more often.
This “explicitly null only large objects” effect is seen again when we add the following running in a different thread at the same time to the original test:
byte[] c = null; .. new Thread(new Runnable() { public void run() { while (i < numTests) { c = new byte[100000]; c = null; } } }).start();
In both test cases here we follow the recommendation
for small memory allocations of not explicitly calling b =
null
. We now find that the c = null
for
a very large memory allocation in fact speeds up garbage collection
very slightly by reducing the number of times the full garbage collector
runs: without null: 53.484s, with null: 53.020s.
Note that we can achieve this automatic garbage collection in a place where we might have set the variable to null using scoping with braces (“{“ and “}”). For example
{ byte[] d = new byte[100000]; // do something with d, but do not set it to null } // d is now out of scope and can be garbage collected naturally (and more quickly)
All inner classes contain a reference to the parent class. Even if your code does not take advantage of this, if you pass an inner class to an execution queue such as the event dispatch thread (EDT), the parent class cannot be garbage collected until the inner class instance has been executed and can be garbage collected. For example:
MyCanvas:
midlet.getDisplay().callSerially(new Runnable() { public void run() { System.out.println(“Canvas width: “ + MyCanvas.this.getWidth()); } });
If this behaviour is undesirable from a memory management standpoint, you can work around this by creating a static inner class, or a new class which is not an inner class.
Note that the system event thread (EDT) operates on a queue of Runnable objects that are processed in sequence. These objects could, for example, be referring for a short time to an old Canvas and all the large memory objects it contains, even though your code has already instructed the system to shift to a new Canvas.
You can ensure that all old queued references to an object are flushed from the queue using a pattern such as the following:
// MAKE SURE ALL OLD EVENTS ON EVENT DISPATCH THREAD HAVE COMPLETED this.getDisplay().callSerially(new Runnable() { public void run() { // Now all old events are done, because this runs after anything // already in the EDT queue, so you can for example safely allocate // more memory } });
One technique to reduce processor and garbage collection load is to pool and reuse variables which use a lot of RAM in a loop. This can be dangerous if you end up keeping large objects around even longer as a result, but it is appropriate for example if you are reusing images that are part of an animation.
For example, in Battle Tank application (see section Case Example: Battle Tank) reusing HUD image saved 16 ms per frame.
Here follows a simple example of image reuse:
public void paint(Graphics g, int w, int h) { if (bigImage == null) { bigImage = DirectUtils.createImage(w, h, 0x00000000); Graphics imgG = bigImage.getGraphics(); imgG.drawString(...); imgG.drawString(...); imgG.drawImage(...); ... } g.drawImage(bigImage, ...); }