Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Java Application Performance Tuning Techniques

DZone's Guide to

Java Application Performance Tuning Techniques

This variety of performance tuning techniques will put you well on your way to fixing common problems and boosting performance in your Java applications.

· Performance Zone
Free Resource

Java applications, regardless of the application server they are deployed to, tend to experience the same set of problems. As a Java EE tuner, I have been exposed to a variety of environments and have made some observations about common problems. In this capacity, I see my role as similar to that of an automobile mechanic: you tell your mechanic that the engine is chirping; then he asks you a series of questions that guide you in quantifying the nature, location, and circumstances of the chirp. From this information, he forms a good idea about a handful of possible causes of the problem.

I will share some of the common problems that I have encountered in the field and their symptoms. These steps will allow us to make our code run smooth and avoid performance issues.

1) Use StringBuilder

This should be your default in almost all Java code. Try to avoid the + operator. Sure, you may argue that it is just syntax sugar for a StringBuilder anyway, as in:

String x = "a" + args.length + "b";

… which compiles to

 0  new java.lang.StringBuilder [16]
 3  dup
 4  ldc <String "a"> [18]
 6  invokespecial java.lang.StringBuilder(java.lang.String) [20]
 9  aload_0 [args]
10  arraylength
11  invokevirtual java.lang.StringBuilder.append(int) : java.lang.StringBuilder [23]
14  ldc <String "b"> [27]
16  invokevirtual java.lang.StringBuilder.append(java.lang.String) : java.lang.StringBuilder [29]
19  invokevirtual java.lang.StringBuilder.toString() : java.lang.String [32]
22  astore_1 [x]

But what happens if, later on, you need to amend your String with optional parts?

String x = "a"+ args.length + "b";   
if(args.length == 1)
x = x + args[0];

You will now have a second StringBuilder that just needlessly consumes memory off your heap, putting pressure on your GC. Write this instead:

StringBuilder x = newStringBuilder("a");
x.append(args.length);
x.append("b");
if (args.length == 1);
x.append(args[0]);

In the above example, it is probably completely irrelevant if you’re using explicit StringBuilder instances, or if you rely on the Java compiler creating implicit instances for you. But remember, we’re in the N.O.P.E. branch. Every CPU cycle that we’re wasting on something as stupid as GC or allocating a StringBuilder‘s default capacity, we’re wasting N x O x P times.

As a rule of thumb, always use a StringBuilder rather than the + operator. And if you can, keep the StringBuilder reference across several methods, if your String is more complex to build.
And for crying out loud, if you still have StringBuffer references, do replace them with StringBuilder. You really hardly ever need to synchronize on a string being created.

2) Out-of-Memory Errors

One of the most common problems that plague enterprise applications is the dreaded OutOfMemoryError. The error is typically followed by one of the following:

  • An application server crash.
  • Degraded performance.
  • A seemingly endless loop of repeated garbage collections that nearly halts processing and usually leads to an application server crash.

Regardless of the symptoms, you will most likely need to reboot the application server before performance returns to normal.

Causes of Out-of-Memory Errors

Before you attempt to resolve an out-of-memory error, first understanding how it can occur is beneficial. If the JVM runs out of memory anywhere in its process memory space, including all regions in the heap as well as the permanent memory space, and a process attempts to create a new object instance, the garbage collector executes to try to free enough memory to allow the new object's creation. If the garbage collector cannot free enough memory to hold the new object, then it throws an OutOfMemoryError.

Out-of-memory errors most commonly result from Java memory leaks. Recall from previous discussions that a Java memory leak is the result of maintaining a lingering reference to an unused object: you are finished using an object, but because one or more other objects still reference that object, the garbage collector cannot reclaim its memory. The memory occupied by that object is thus lost from the usable heap. These types of memory leaks typically occur during Web requests, and while one or two leaked objects may not crash your application server, 10,000 or 20,000 requests might. Furthermore, most objects that are leaked are not simple objects such as Integers or Doubles, but rather represent subgraphs within the heap. For example, you may inadvertently hold on to a Person object, and that Person object has a Profile object that has several PerformanceReview objects that each maintain sets of data. Rather than losing 100 bytes of memory that the Person object occupies, you lose the entire subgraph that might account for 500 KB or more of memory.

In order to identify the root of this problem, you need to determine whether a real memory leak exists or whether something else is manifesting as an OutOfMemoryError.

I use the following two techniques when making this determination:

  • Analyze deep memory statistics.
  • Inspect the growth pattern of the heap.

The JVM tuning process is not the same for all JVMs, such as Sun and IBM, but some commonalities exist.

3) Avoid Regular Expression

Regular expressions are relatively cheap and convenient. But if you’re in the N.O.P.E. branch, they’re about the worst thing you can do. If you absolutely must use regular expressions in computation-intensive code sections, at least cache the Pattern reference instead of compiling it afresh all the time:

staticfinalPattern HEAVY_REGEX = Pattern.compile("(((X)*Y)*Z)*");

But if your regular expression is really silly, like

String[] parts = ipAddress.split("\\.");

… then you really better resort to ordinary char[] or index-based manipulation. For example this utterly unreadable loop does the same thing:

intlength = ipAddress.length();
intoffset = 0;
intpart = 0;
for (inti = 0; i < length; i++) {
 if (i == length - 1 || ipAddress.charAt(i + 1) == '.') {
  parts[part] = ipAddress.substring(offset, i + 1);
  part + +;
  offset = i + 2;
 }
}

… which also shows why you shouldn’t do any premature optimization. Compared to the split() version, this is unmaintainable.

4) Do Not Use iterator()

Now, this advice is really not for general use-cases, but only applicable deep down in a N.O.P.E. branch. Nonetheless, you should think about it. Writing Java-5 style foreach loops is convenient. You can just completely forget about looping internals, and write:

for

(String value: strings) {

 // Do something useful here

}

However, every time you run into this loop, if strings is an Iterable, you will create a new Iterator instance. If you’re using an ArrayList, this is going to be allocating an object with 3 ints on your heap:

privateclassItr implementsIterator < E > {
  intcursor;

Instead, you can write the following, equivalent loop and “waste” only a single int value on the stack, which is dirt cheap:

intsize = strings.size();
for (inti = 0; i < size; i++) {
 String value: strings.get(i); // Do something useful here}

… or, if your list doesn’t really change, you might even operate on an array version of it:

for

(String value: stringArray) {

 // Do something useful here

}

5. Use Primitives and the Stack

The above example is from jOOQ, which uses a lot of generics, and thus is forced to use wrapper types for byte, short, int, and long – at least before generics are specializable in Java 10 and project Valhalla. But you may not have this constraint in your code, so you should take all measures to replace:

// Goes to the heap
Integer i = 817598;

… with this:

// Stays on the stack
inti = 817598;

Things get worse when you’re using arrays. Replace this:

// Three heap objects!
Integer[] i = { 1337, 424242};

… with this:

// One heap object.

int[] i = { 1337, 424242

};

When you’re deep down in your N.O.P.E. branch, you should be extremely wary of using wrapper types. Chances are that you will create a lot of pressure on your GC, which has to kick in all the time to clean up your mess.

A particularly useful optimisation might be to use some primitive type and create large, one-dimensional arrays of it, and a couple of delimiter variables to indicate where exactly your encoded object is located on the array.

An excellent library for primitive collections, which are a bit more sophisticated than your average int[] is trove4j, which ships with LGPL.

There is an exception to this rule: boolean and byte have few enough values to be cached entirely by the JDK. You can write:

Boolean a1 = true; // ... syntax sugar for:

Boolean a2 = Boolean.valueOf(true);

Byte b1 = (byte) 123; // ... syntax sugar for:

Byte b2 = Byte.valueOf((byte) 123);

The same is true for low values of the other integer primitive types, including char, short, int, and long. But only if you’re auto-boxing them, or calling TheType.valueOf(), not when you call the constructor!

Conclusion

In order to effectively diagnose performance problems, you need to understand how problem symptoms map the root cause of the underlying problem. If you can triage the problem to application code, then you need to forward the problem to the application support delegate, but if the problem is in the environment, then resolving it is within your control.

The root of a problem depends on many factors, but some indicators can increase your confidence when diagnosing problems and completely eliminate others. I hope this article can serve as a beginning troubleshooting guide for your Java environment that you can customize to your environment as issues arise.

Topics:
performanace ,java ,performance tuning ,java ee ,performance optimization

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}