Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Java performance programming, Part 1: Smart object-management saves the day

Learn how to reduce program overhead and improve performance by controlling object creation and garbage collection

  • Print
  • Feedback

Page 2 of 6

The developer generally doesn't need to be directly involved in this garbage collection process. Objects drop out of the reachable set and become eligible for recycling as they're replaced with other objects, or as methods return and their variables are dropped from the calling thread's stack. The JVM runs garbage collection periodically, either when it can, because the program threads are waiting for some external event, or when it needs to, because it's run out of memory for creating new objects. Despite the automatic nature of the process, it's important to understand that it's going on, because it can be a significant part of the overhead of Java programs.

Besides the time overhead of garbage collection, there's also a significant space overhead for objects in Java. The JVM adds internal information to each allocated object to help in the garbage collection process. It also adds other information required by the Java language definition, which is needed in order to implement such features as the ability to synchronize on any object. When the storage used internally by the JVM for each object is included in the size of the object, small objects may be substantially larger than their C/C++ counterparts. Table 1 shows the user-accessible content size and actual object memory size measurements for several simple objects on various JVMs, illustrating the memory overhead added by the JVMs.

Table 1. Measured memory usage (bytes)
  Content (bytes) JRE 1.1.8 (Sun) JRE 1.1.8 (IBM) JRE 1.2.2 (Classic) JRE 1.2.2 (HotSpot 2.0 beta)
java.lang.Object

0

26

31

28

18

java.lang.Integer

4

26

31

28

26

int[0]

4 (length)

26

31

28

26

java.lang.String
(4 characters)

8 + 4

58

63

60

58



This space overhead is a per object value, so the percentage of overhead decreases with larger objects. It can lead to some unpleasant surprises when you're working with large numbers of small objects, though -- a program juggling a million Integers will have most systems down on their knees, for example!

Comparison with C/C++

For most operations, Java performance is now within a few percent of C/C++. The just-in-time (JIT) compilers included with most JVMs convert Java byte codes to native code with amazing efficiency, and in the latest generation (represented by IBM's JVM and Sun's HotSpot) they're showing the potential to start beating C/C++ performance for computational (CPU intensive) applications.

However, Java performance can suffer by comparison with C/C++ when many objects are being created and discarded. This is due to several factors, including initialization time for the added overhead information, garbage collection time, and structural differences between the languages. Table 2 shows the impact these factors can have on program performance, comparing C/C++ and Java versions of code repeatedly allocating and freeing arrays of byte values.

Table 2. Memory management performance (time in seconds)
  JRE 1.1.8 (Sun) JRE 1.1.8 (IBM) JRE 1.2.2 (Classic) JRE 1.2.2 (HotSpot 2.0 beta) C++
Short-term Allocations (7.5 M blocks, 331 MB)

30

22

26

14

9

Long-term Allocations (7.6 M blocks, 334 MB)

48

28

39

33

13



For both short- and long-term allocations, the C++ program is considerably faster than the Java program running on any JVM. Short-term allocations have been one focus area for optimization in HotSpot. Results show that -- with the Server 2.0 beta used in this test -- this is the closest any JVM comes to the C++ code, with a 50 percent longer test time. For long-term allocations, the IBM JVM gives better performance than the HotSpot JVM, but both trail far behind the performance of the C++ code for this type of operation.

Even the relatively good performance of HotSpot on short-term allocations is not necessarily a cause for joy. In general, C++ programs tend to allocate short-lived objects on the stack, which would give a lower overhead than the explicit allocation and deallocation used in this test. C++ also has a big advantage in the way it allocates composite objects, using a single block of memory for the combined entity. In Java, each object needs to be allocated by its own block.

We'll certainly see more performance improvements for object allocation as vendors continue to work on their VMs. Given the above advantages, though, it seems unlikely the performance will ever match C++ in this area.

Does this mean your Java programs are eternally doomed to sluggish performance? Not at all -- object creation and recycling is just one aspect of program performance, and, providing you're sensible about creating objects in heavily used code, it's easy to avoid the object churn cycle! In the remainder of this article we'll look at ways to keep your programs out of the churn by reducing unnecessary object creation.

Keep it primitive

Probably the easiest way to reduce object creation in your programs is by using primitive types in place of objects. This approach doesn't apply very often -- usually there's a good reason for making something an object in the first place, and just replacing it with a primitive type is not going to fill the same design function. In the cases where this technique does apply, though, it can eliminate a lot of overhead.

The primitive types in Java are boolean, byte, char, double, float, int, long, and short. When you create a variable of one of these types, there is no object creation overhead, and no garbage collection overhead when you're done using it. Instead, the JVM allocates the variable directly on the stack (if it's a local method variable) or within the memory used for the containing object (if it's a member variable).

Java defines wrappers for each of these primitive types, which can sometimes confuse Java novices. The wrapper classes represent immutable values of the corresponding primitive types. They allow you to treat values of a primitive type as objects, and are very useful when you need to work with generic values that may be of any type. For instance, the standard Java class libraries define the java.util.Vector, java.util.Stack, and java.util.Hashtable classes for working with object collections. Wrapper classes provide a way to use these utility classes with values of primitive types (not necessarily a good approach from the performance standpoint, for reasons we'll cover in the next article in this series, but a quick and easy way to handle some common needs).

  • Print
  • Feedback

Resources
  • Benchmarks
  • Recent JavaWorld articles covering object pools for resource management