Classes

This section discusses performance issues related to constructor calls and class initialization using examples.

  •   Class and Instance Initialization

 

When an instance of a class is created using new, initialization of the class’s instance variables (variables unique to each instance) must be done. By contrast, class variables (those declared static, and shared across instances) need only be initialized once, conceptually at program invocation time. The difference between these types of initialization is quite important

With this there is a potential inefficiency. The month number/name data found in months [] is an instance variable of the class, that is, a copy of the data is found in every instance of the class.

Structuring the data in this way does not make sense, in that the number/name data never changes, and is the same across all class instances.

The program requires lesser time, saving of 10-1 over the first approach. Moreover, it saves a lot of space per class instance as well. As a general rule, it’s worth “factoring out” methods that do not operate on unique data in a class instance, and variables that are not unique to an instance, and making both methods and variables static, that is, shared across all instances.

  •   Recycling Class Instances

 

While implementing some type of a linked list structure, and that nodes of the structure have the form:

class Node {

  Object data; // data

  Node link; // link to next node in the list

}

The costs of allocating and garbage collecting large numbers of these nodes need to be considered.

One alternative to new and garbage collection is to keep a free list of nodes, and check the list before new is called to allocate a node. For example, a class variable freelist can be used for this purpose. A node referenced by a variable noderef can be freed, by saying

noderef.link = freelist;

freelist = noderef;

That is, add the freed node to the head of the free list. When a node is allocated, freelist is checked, and if it’s not null, the node can be obtained by saying:

noderef = freelist;

freelist = noderef.link;

For this scheme to work, freelist must be updated in a thread-safe manner, by using synchronized methods or statements.

This technique tends to work against the whole idea of constructors, and against a cleanly defined approach to initializing objects. This approach makes most sense when some type of an internal utility class is needed, and large numbers of instances of this class needs to be maintained.


Methods

            There is an intrinsic cost associated with calling Java methods. These costs involve actual transfer of control to the method, parameter passing, return value passing, and establishment of the called method’s stack frame where local variables are stored. Such costs show up in other languages as well. In this section we will look at a few of the performance issues with methods.

  •   In lining

            Perhaps the most effective way to deal with method call overhead is method in lining, either by a compiler doing it automatically, or doing it yourself manually. In lining is done by expanding the inlined method’s code in the code that calls the method. There are several ways that compilers can perform automatic inlining. One way is to expand the called method inline in the caller, which improves speed at the expense of code space. Another approach is more dynamic, where methods are inlined in a running program. Note that it is possible to make methods more attractive for inlining, by keeping them short and simple. For example, a method that simply returns the value of a private field is a prime candidate for inlining.

  •   Final Methods

            One way to help a compiler with inlining is to declare methods as final, that is, declaring that no subclass method overrides the method. The same technique is used with whole classes, indicating that the class cannot be subclassed, and that all its methods are implicitly final.

            In other words, use of final provides hints about what are typically called virtual functions, that is, methods or functions that are dispatched at run time based on the type of the object that they are called for. It’s harder to inline a virtual method, since the actual method to be called cannot be determined at compile time.

  •   Synchronized Methods

            Synchronized methods are used in thread programming, where an object is given exclusive access to a method, so that the method can perform data structure updates without interference from other threads. Such methods are slower than non-synchronized ones, because of the overhead associated with obtaining a lock on the method’s object.

            The synchronized call takes about twice as long as the non-synchronized one. This difference is important in some contexts, for example in the collection classes. Collection classes like ArrayList are not themselves thread-safe, but they have mechanisms whereby a thread-safe wrapper can be added on top of a collection class instance. This approach is useful in designing custom classes.


Strings

            Strings are a widely used data type in the Java language. Java strings are represented as objects of type String, and store sequences of 16-bit Unicode characters, along with the current string length.

Strings are Immutable

Perhaps the most important point about Java strings relative to performance is that strings are immutable, that is, they never change after creation.

            In other words, the two strings to be concatenated are copied to a temporary string buffer, and then copied back. Such copying is quite expensive. So a fundamental performance rule to remember with strings is to use StringBuffer objects explicitly if you’re building up a string. String concatenation operators like + and += are fine for casual use, but quite expensive otherwise. This program illustrates the respective costs of + and StringBuffer.append():

            The program is around 40 times faster  using StringBuffer  instead of the + operator and String class.

Accumulating Strings Using char [] Arrays

            String buffers are an effective way of accumulating strings, that is, building up a string a piece at a time. Another approach is to create an output char[] array, add characters to it, and then convert the result to a string.

An application that uses this technique is one that converts from the UTF-8 3 encoding used by Java libraries (when storing Java strings to a disk file) to a string object. Java characters are encoded as one, two, or three bytes in the UTF-8 scheme, so when a stream of bytes is converted back to characters, it is guaranteed that the number of characters will never be more than the number of bytes.

Using == and String.equals() to Compare Strings

            In the Java language, the == operator, cannot be used to compare strings. This operator when applied to references simply compares the references themselves for equality, and not the referenced objects

So an effective way for comparing would be

if (s1 == s2 || s1.equals(s2))

If the references are identical, this will short-circuit the equals() method call.

This technique illustrates a more general principle of performance – always perform a cheap test before an expensive one, if possible. In this example, == is much less expensive to perform than

equals().


Libraries

This section touches on some of the performance issues with using classes and methods from the standard Java libraries.

  •   System.arraycopy()

 

            System.arraycopy() is a method that supports efficient copying from one array to another. For example, if an array vec1 has to be copied to another array vec2,

System.arraycopy(vec1, 0, vec2, 0, N);

can be used by specifying the starting offset in each array.

For very short arrays, use of arraycopy method may be counterproductive, because of overhead in actually calling the method, checking the method’s parameters, and so on. Object.clone() represents another approach to copying an array.clone() allocates a new instance of the array and copies all the array elements. Note that Object.clone() does a shallow copy, as does

System.arraycopy(), so if the array elements are object references, the references are copied, and no copy is made of the referenced objects.

  •   Vector vs. ArrayList

            

            The class java.util.Vector is used to represent lists of object references, with support for dynamic expansion of the vector, random access to vector elements, and so on.

A newer scheme is the Java collection framework, which includes a class ArrayList that can be used in place of Vector. Some of the performance differences between Vector and ArrayList include:

?     Vector’s methods are synchronized, ArrayList’s are not. This means that Vector is thread-safe, at some extra cost

?     The collection framework provides an alternative to ArrayList called LinkedList, which offers different performance tradeoffs

?     When Vector needs to grow its internal data structure to hold more elements, the size of the structure is doubled, whereas for ArrayList, the size is increased by 50%. So ArrayList is more conservative in its use of space.

It’s worth using the collection framework in your applications if you possibly can, because it’s now the “standard” way to handle collections. If you are concerned about thread safety, one way to handle this issue is to use wrappers around objects like ArrayList,

Choosing the collection classes

            HashSet should be used for maintaining unique objects if it is not intended to be thread safe for the collection for all basic (add/remove/access) operations otherwise synchronized HashSet should be used for thread safe.

TreeSet should be used for ordered and sorted set of unique objects for non-thread safe collection otherwise use synchronized TreeSet for thread safe.

Use HashMap for non-thread safe map collection otherwise use Hashtable for thread safe collection.

Use TreeMap for non-thread safe ordered map collection otherwise use synchronized TreeMap for thread safe.


Size

In this section we look at a variety of techniques for reducing the disk and memory usage of Java programs.

Class File Size

Java compilers generate. class files, with each file containing byte codes for the methods in one class. These files contain a series of sections, or attributes, and it’s possible to exercise some control over which sections actually show up in a .class file. The Code attribute contains actual bytecodes for methods, SourceFile information on the source file name used to generate this. class file, LineNumberTable mapping information between bytecode offsets and source file line numbers, and LocalVariableTable mapping information between stack frame offsets and local variable names.

You can use options to the javac compiler to control which sections are included:

javac Code, SourceFile, LineNumberTable

javac –g Code, SourceFile, LineNumberTable, LocalVariableTable

javac -g:none Code

So use of javac –g:none saves about 20% in space over the default javac, and using javac –g is about 20% larger than the default. There are also commercial tools that tackle this particular problem.

If you strip out information such as line numbers from. class files, you may have problems with debugging and exception reporting.

Wrappers

The Java libraries have a variety of what are known as wrapper classes, classes used to hold a value of a primitive type such as int or double. For example, you can say:

Integer obj = new Integer(37);

to create a wrapper class instance. This instance can then be assigned to an Object reference, have its type tested using instanceof, used with collection classes like ArrayList, and so on.

One drawback, however, is that wrapper classes have some overhead, both in time and space costs. It’s less efficient to extract a value from a wrapper instance than it is to use the value directly. And there are costs in creating all those wrapper instances, both time costs in calling new and constructors, and space costs with the overhead that comes with class instances (a very rough estimate might be that an instance of a class takes 15 bytes, in addition to the size of the actual instance variables).

1.1.1. Garbage Collection

The Java language uses a form of automatic memory management known as garbage collection. Memory for objects and arrays is allocated using new, and then reclaimed when no longer in use. Garbage collection is a process of detecting when an object no longer has any references to it, with the object’s memory then reclaimed and made available as free space.


Object Creation

  •   The number of temporary objects being used should be reduced, especially in loops. Collection objects should be pre-sized and they should be emptied before reusing them
  •   Custom conversion methods can be used for converting between data types (especially strings and streams) to reduce the number of temporary objects.
  •  Canonicalized objects should be used. These objects should be compared by identity.
  •  Flattened Objects should be used to reduce the number of nested objects.
  •  Consider using a ThreadLocal to provide threaded access to singletons with state.
  •  Clone() method should be used to avoid calling any constructors.
  •  Inheritance chains should be kept as small as possible.

 

Catching OutOfMemoryErrors

            Xmx and -Xms (-mx and -ms) specify the heap max and starting sizes. Runtime.totalMemory() gives the current process size, Runtime.maxMemory() (available from SDK 1.4) gives the -Xmx value. Repeatedly allocating memory by creating objects and holding onto them will expand the process to its maximum possible size. This technique can also be used to flush memory.

If a process gets too large, the operating system will start paging the process causing a severe decrease in performance.

It is reasonable to catch the OutOfMemoryError if you can restore your application to a known state that can proceed with processing. For example, daemon service threads can often do this.

The Random Access Interface

            A java.util.List object which implements RandomAccess should be faster when using List.get() than when using Iterator.next(). An instanceof RandomAccess can be used to test whether to use List.get() or Iterator.next() to traverse a List object.