The final two sections of this chapter discuss two seemingly opposing tuning techniques. Section 4.5.1 presents the technique of creating objects before they are needed. This technique is useful when a large number of objects need to be created at a time when CPU power is needed for other routines, and where those objects could feasibly be created earlier, at a time when there is ample spare CPU power.
Section 4.5.2, presents the technique of delaying object creation until the last possible moment. This technique is useful for avoiding unnecessary object creation when only a few objects are used although many possible objects can be created.
In fact, these techniques represent two sides of the same coin: moving object creation from one time to another. Preallocating moves object creation to a time earlier than you would normally create those objects; lazy initialization moves object creation to a later time (or never).
There may be situations in which you cannot avoid creating particular objects in significant amounts: perhaps they are necessary for the application and no reasonable amount of tuning has managed to reduce the object-creation overhead for them. If the creation time has been identified as a bottleneck, it is possible that you can still create the objects, but move the creation time to a part of the application when more spare cycles are available or there is more flexibility in response times.
The idea here is to choose another time to create some or all of the
objects (perhaps in a partially initialized stage), and store those
objects until they are needed. Again, if you have followed the
factory design pattern, it is relatively simple to replace the
return
new
Something()
statement with an access to the collection of spare
objects (presumably testing for a nonempty collection as well). If
you have not followed the factory design pattern, you may need to
track down all calls that create a new instance of the relevant class
and replace them with a call to the factory method. For the real
creation, you might want to spawn a background (low-priority) thread
to churn out objects and add them into the storage collection until
you run out of time, space, or necessity.
This is a variation of the “read-ahead” concept, and you can also apply this idea to:
Classloading (obviously not for classes needed as soon as the application starts up): see Section 3.8.
Distributed objects: see Chapter 12.
Reading in external data files.
Lazy initialization
means that you do not initialize
objects until the first time they are used. Typically, this comes
about when you are unsure of what initial value an instance variable
might have but want to provide a default. Rather than initialize
explicitly in the constructor (or class static initializer), it is
left until access time for the variable to be initialized, using a
test for null
to determine if it has been
initialized. For example:
public getSomething( ) { if (something == null) something = defaultSomething( ); return something; }
I find this kind of construct quite often in code (too often, in my
opinion). I can only rarely see a justifiable reason for using lazy
initialization. Not deciding where to initialize a variable correctly
is more often a result of lazy design or lazy coding. The result can
be many tests for null
executing when you access
your variables, and these null
tests never go
away: they are always performed, even after the variable has been
initialized. In the worst case, this can impact performance badly,
although generally the overhead is small and can be ignored. I always
recommend avoiding the use of lazy initialization for general coding.
On the other hand, there are particular design situations in which it is appropriate to use lazy initialization. A good example is classloading, where classes are loaded dynamically as required. This is a specific design situation in which it is clear there will be a performance impact on running applications, but the design of the Java runtime merited this for the features that it brought.
Lazy initialization can be a useful performance-tuning technique. As
usual, you should be tuning after functionality is present in your
application, so I am not recommending using lazy initialization
before the tuning stage. But there are places where you can change
objects to be lazily initialized and make a large gain. Specifically,
these are objects or variables of objects that may never be used. For
example, if you need to make available a large choice of objects, of
which only a few will actually be used in the application (e.g.,
based on a user’s choice), then you are better off not
instantiating or initializing these objects until they are actually
used.
An example is the char
-to-byte
encoding
provided by the JDK. Only a few (usually one) of these are used, so
you do not need to provide every type of encoding, fully initialized,
to the application. Only the required encoding needs to be used.
When you have thousands of objects that need complex initializations but only a few will actually be used, lazy initialization provides a significant speedup to an application by avoiding exercising code that may never be run. A related situation in which lazy initialization can be used for performance tuning is when there are many objects that need to be created and initialized, and most of these objects will be used, but not immediately. In this case, it can be useful to spread out the load of object initialization so you don’t get one large hit on the application. It may be better to let a background thread initialize all the objects slowly or to use lazy initialization to take many small or negligible hits, thus spreading the load over time. This is essentially the same technique as for preallocation of objects (see the previous section).
It is true that many of these kinds of situations should be anticipated at the design stage, in which case you could build lazy initialization into the application from the beginning. But this is quite an easy change to make (usually affecting just the accessors of a few classes), and so there is usually little reason to over-engineer the application prior to tuning.
Get Java Performance Tuning now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.