Things like code refactorings, insufficient code coverage testing, poor coding standards or mere oversight can often lead to redundant code. Generally all commercial JVMs apply a certain level of escape analysis and dead code elimination optimizations while executing Java code. However, there are certain cases where JVM is unable to eliminate redundancy; for example, unused instance variables of a class.
IDEs such as Eclipse can warn the Developers about local variables and instance variables not being read by the program. It might be easier for JVMs to identify and optimize away redundant local variables. However, it would not be possible for a JVM to eliminate unused instance variables that can potentially be accessed using mechanisms such as Java Reflection. These unused instance variables will increase object size and, in turn, the memory footprint of the Java application if a large number of such objects are allocated by the program.
You might think that such unused/unread instance variables might not have a big impact on the application performance. Contrary to this belief, such redundant variables can substantially affect application performance. In fact, this could lead to pathological cases where application performance can be hampered due to inefficient processor memory cache usage. For example, a redundant instance variable which gets mapped to the end of object layout in the memory can increase the object size such that the resulting object does not fit into a single cache line. On the other hand, when such a field gets mapped into middle parts of the object layout, it can lead to memory holes and result in redundant garbage collection cost.
The following benchmark demonstrates performance effects of unused instance variables:
import java.util.LinkedList;
public final class ObjectTrimming {
private long redundantField1 = 0L;
private long redundantField2 = 0L;
private long redundantField3 = 0L;
private long redundantField4 = 0L;
private long redundantField5 = 0L;
private long redundantField6 = 0L;
private long redundantField7 = 0L;
private byte[] usedField = null;
private ObjectTrimming(){
usedField = new byte[968];
}
public static void main(String[] args) throws Exception {
LinkedList listStore = new LinkedList();
long totalTime = 0L;
for(int j=0; j<10;j++)
{
long then = System.currentTimeMillis();
for(int i=0; i< 104857; i++)
{
listStore.addLast(new ObjectTrimming());
}
long now = System.currentTimeMillis();
totalTime += now - then;
System.out.println("After Iteration " +j + " total number of elements in the Linked List: " +listStore.size());
}
System.out.println("Execution Time: " + totalTime);
}
}
As you can see, this application creates approximately 1024M of data. The ObjectTrimming class has 7 fields of long data type and a byte array. Since each long field occupies 64 bits of data, objects of type ObjectTrimming class will have 56 bytes of space occupied by long fields. We are using the byte array to allocate another 968 bytes of data which will bring each ObjectTrimming object to a size of 1KB. We are creating 104857 such objects in a loop which iterates for 10 times. Thus in the end, listStore linked list will contain approximately 1024M of data.
We executed this benchmark three times on a system with configuration of 2 chips, 8 CPUs, 4 cores per chip AMD Opteron 8384 processor running at 2.7GHz, with 8G of RAM and it took an average of 9894 milliseconds. None of the long variables are used by the ObjectTrimming class. Neither could they be accessed by other classes, so we decided to remove these fields and rerun the modified benchmark. This time the execution time for the three runs averaged to 6493 milliseconds. That is 52% improvement in application performance. It is unlikely that a class will have a large unused-fields-to-used-fields ratio as in our example above. However, the example above shows that saving 56 bytes of data in a 1K object can have a big impact on memory footprint of the application.
We used Sun Java SE Runtime Environment (build 1.6.0_06-p-b01) with Java HotSpot Server VM (build 14.0-b09, mixed mode) for the above experiments. The JVM command-line flags used to run the benchmark were: java -server -Xms1175M -Xmx1175M
As noted above, JVMs cannot necessarily eliminate such unused fields, thus it is the Java Developer's responsibility to optimally define classes.
-------------------------
The information presented in this document is for informational purposes only and may contain technical inaccuracies, omissions and typographical errors. Links to third party sites are for convenience only, and no endorsement is implied.