However, in Figure 6, the character x is directly appended to the StringBuilder
object that str points to,
and no objects are created.
public class myClass {
public static int N = 10000;
public static void main(String [] argv) {
StringBuilder str = new StringBuilder();
for (int i = 0; i < N; i++)
str.append(“x”);
}
}
Figure 6. Example of avoiding unnecessary allocations: More efficient version of Figure 3a, no new objects created
Figure 7 shows the performance of the examples shown in Figure 4, Figure 5 and Figure 6. As you
can see, the performance improvements are significant when allocations are avoided inside loops. All
performance measurements that are mentioned in this paper were obtained on an IBM System x™
machine using Intel® Xeon™ Quad Core system running at 3.2 GHz with 8 GB of RAM.
Here are a few guidelines to improve the locality of the Java application:
•
Carefully design the application’s class hierarchy. It is better to place less data in the super
classes, unless all the subclasses in the hierarchy access the data. This greatly reduces the
size of the objects.
•
Move rarely used field members into separate classes. Although this can increase the
memory footprint of the Java application, this separation has benefits in the form of reduced
cache misses.
•
Try to avoid the unnecessary creation of objects. Instead, you should design the application
so that it reuses objects when possible. If the application is multithreaded, you can use
thread-local data for temporary buffers. In fact, use the ThreadLocal class wherever possible.
IBM Just-In-Time Compiler (JIT) for Java
Best practices and coding guidelines for improving performance
Figure 7. Performance of string concatenation
Guideline: Minimize object allocations, if possible
Minimizing the number of objects that an application allocates also improves the locality of the
application. Objects reside more often in the lower levels of the memory hierarchy (cache), which
results in better application performance. However, you should avoid the allocation of a large number
of short-lived objects, as this tends to result in poor cache locality of the application. If you need to
create short-lived objects, then the recommendation is that you use the IBM JDK’s generational
garbage collection policy (-Xgcpolicy:gencon) [10].
As explained in the previous section, the TRJIT compiler tries to aggressively convert heap
allocations to stack allocations through an optimization called escape analysis [3]. When possible,
you can follow a few guidelines when writing Java application code to facilitate the stack allocation of
objects. In the following cases, the TRJIT compiler cannot convert a heap allocation into a stack
allocation:
•
An object escapes if it is stored into a static field or a field of another object.
•
If the object allocation is passed as a parameter into a method call that cannot be inlined.
•
If the allocation is returned from a method call that cannot be inlined.
IBM Just-In-Time Compiler (JIT) for Java
Best practices and coding guidelines for improving performance
•
At lower optimization levels, the TRJIT compiler does not perform this optimization.
Reducing the number of allocations also reduces the load on the J9 JVM garbage collector. There are
fewer objects on the heap, which results in fewer garbage collections, in turn reducing the pause
times of the application. A last point to be noted here is about memory leaks. To avoid memory leaks
in the application, release any references to obsolete objects (by storing a NULL into such
references). References from static fields or objects with a long life time are not garbage collected
until the application ends.
Guideline: Use immutable fields
The TRJIT compiler aggressively optimizes immutable fields. You can follow these guidelines to
make fields immutable:
•
Declare fields as
final when possible.
•
Declare fields as
private and write to such fields only in constructors.
When an object is declared as final static, the TRJIT compiler can apply optimizations based on an
analysis of the contents of the object on the heap. For example, the TRJIT compiler can determine
the length of an array, which can help optimize loops that iterate over the array. (Note: Java has no
way to declare multidimensional arrays to be final — only the first dimension of a final array is fixed.)
Methods
As explained in the previous section, the TRJIT compiler attempts to focus optimization efforts on the
most frequently run parts of the application. Therefore, a method that is invoked frequently has a much
greater chance of being optimized aggressively, as compared to a method that is only invoked a few
times. It is also beneficial to structure the Java application so that most frequently run methods (hot)
invoke other frequently run methods, rather than infrequently run (cold) methods. This helps the TRJIT
compiler to aggressively inline method calls within hot methods.
Guideline: Keep methods small
From a Java development perspective, it is advantageous to keep methods small (about 10 lines of
Java code), as this allows the TRJIT compiler to inline callee methods into their callers. Some
heuristics are outlined in this paper because they cause a call more likely to be inlined by the TRJIT
compiler:
•
Calls in loops: The inlining of method calls within loops is likely to benefit performance.
•
Specialized calls: Calls that have constant arguments and arguments with a specific type
(rather than a generic type) are more likely to be inlined.
You should declare parameters to method calls to be of a final or leaf type in the class hierarchy of
the Java application. This allows the TRJIT compiler to optimize virtual calls on such parameters by
devirtualizing, without using a virtual guard. If parameters are declared to be Object or an abstract or
interface type, then the compiler might not be able to optimize virtual calls as effectively. Because
devirtualization is important for optimizations of the Java code, keep the class hierarchy as simple as
possible to allow the TRJIT compiler to devirtualize as many call sites as possible.
IBM Just-In-Time Compiler (JIT) for Java
Best practices and coding guidelines for improving performance