Chapter 4. Object Creation

The biggest difference between time and space is that you can't reuse time.

Merrick Furst

"I thought that I didn't need to worry about memory allocation. Java is supposed to handle all that for me." This is a common perception, which is both true and false. Java handles low-level memory allocation and deallocation and comes with a garbage collector. Further, it prevents access to these low-level memory-handling routines, making the memory safe. So memory access should not cause corruption of data in other objects or in the running application, which is potentially the most serious problem that can occur with memory-access violations. In a C or C++ program, problems of illegal pointer manipulations can be a major headache (e.g., deleting memory more than once, runaway pointers, bad casts). They are very difficult to track down and are likely to occur when changes are made to existing code. Java deals with all these possible problems and, at worst, will throw an exception immediately if memory is incorrectly accessed.

However, Java does not prevent you from using excessive amounts of memory nor from cycling through too much memory (e.g., creating and dereferencing many objects). Contrary to popular opinion, you can get memory leaks (or, more accurately, object retention) by holding onto objects without releasing references. This stops the garbage collector from reclaiming those objects, resulting in increasing amounts of memory being used.[1] In addition, Java does not provide for large numbers of objects to be created simultaneously (as you could do in C by allocating a large buffer), which eliminates one powerful technique for optimizing object creation.

[1] For more information, see Ethan Henry and Ed Lycklama's article "How Do You Plug Memory Leaks?", Dr. Dobb's Journal, February 2000, http://www.ddj.com/documents/s=888/ddj0002l/0002l.htm.

Creating objects costs time and CPU effort for an application. Garbage collection and memory recycling cost more time and CPU effort. The difference in object usage between two algorithms can make a huge difference. In Chapter 5, I cover algorithms for appending basic data types to StringBuffer objects. These can be an order of magnitude faster than some of the conversions supplied with Java. A significant portion of the speedup is obtained by avoiding extra temporary objects used and discarded during the data conversions.[2]

[2] Up to SDK 1.4, data-conversion and object-lifecycle performance has been targeted by Sun. In 1.4, the core SDK int conversion is faster, but all other data type conversions are still significantly slower.

Here are a few general guidelines for using object memory efficiently:

  • Avoid creating objects in frequently used routines. Because these routines are called frequently, you will likely be creating objects frequently, and consequently adding heavily to the overall burden of object cycling. By rewriting such routines to avoid creating objects, possibly by passing in reusable objects as parameters, you can decrease object cycling.

  • Try to presize any collection object to be as big as it will need to be. It is better for the object to be slightly bigger than necessary than to be smaller than it needs to be. This recommendation really applies to collections that implement size increases in such a way that objects are discarded. For example, Vector grows by creating a new larger internal array object, copying all the elements from the old array, and discarding it. Most collection implementations have similar implementations for growing the collection beyond its current capacity, so presizing a collection to its largest potential size reduces the number of objects discarded.

  • When multiple instances of a class need access to a particular object in a variable local to those instances, it is better to make that variable static rather than have each instance hold a separate reference. This reduces the space taken by each object (one fewer instance variable) and can also reduce the number of objects created if each instance creates a separate object to populate that instance variable.

  • Reuse exception instances when you do not specifically require a stack trace (see Section 6.1).

This chapter presents many other standard techniques to avoid using too many objects and identifies some known inefficiencies when using some types of objects.