Share 11 practical tips for Java performance tuning

  • 2020-11-30 08:22:56
  • OfStack

Most developers believe that performance optimization is a complex issue that requires a lot of experience and knowledge. Yes, there's nothing wrong with that. It's true that optimizing your application for maximum performance is not an easy task, but that doesn't mean you can't do anything without gaining this experience and knowledge. Here are some easy-to-follow tips and best practices to help you create a well-performing application.

Most of these recommendations are based on Java, but none are definitive, while some are applicable to all applications and programming languages. Before we share Java-based performance tuning tips, let's discuss 1 of these general performance tuning tips.

1. Don't optimize until you have to

This is probably one of the most important performance tuning techniques. You should follow common best practices and try to implement your use cases effectively. But that doesn't mean replacing any standard libraries or building complex optimizations until it proves necessary.

In most cases, premature optimization takes up a lot of time, making the code difficult to read and maintain. What's worse, these optimizations usually do no good because you spend a lot of time optimizing non-critical parts of your application.

So, how do you prove that you need to optimize something?

First, you need to determine the speed of your application code, for example, by specifying a maximum response time for all API calls, or by specifying the number of records to import within a specific time range. Once you're done, you can measure which parts of your application are too slow to improve. When you have done so, move on to the second tuning tip.

2. Use the profiler to find the real bottleneck

After you've followed tip 1 and determined that some parts of your application really need to be improved, ask yourself where to start.

You can solve this problem in two ways:

You can take a look at your code and start with the parts that look suspicious or that you think might cause problems. Or use a profiler to get detailed information about the behavior and performance of each part of the code.

As for why you should always follow method 2.

The answer should be obvious: a profile-based approach gives you a better understanding of the performance implications of your code and allows you to focus on the most critical parts. If you've ever used a profiler, you'll be surprised at what parts of your code are causing performance problems. Many times, however, your first guess will lead you in the wrong direction.

Create a performance test suite for the entire application

This is another general tip that will help you avoid many of the unexpected problems that usually occur after performance improvements are deployed to production. You should always define a performance test suite that tests the entire application and run it before and after you complete the performance improvement.

These additional test runs will help you identify the functional and performance impact of the changes and ensure that you do not release an update that will do more harm than good. This is especially important if your task runs in multiple different parts of your application, such as a database or cache.

Address the biggest bottlenecks first

After you have created the test suite and used the profiler to analyze the application, you have a list of questions to improve performance, which is fine, but it still doesn't answer the question of where you should start. You can start with things that you can get done quickly, or with the most important questions.

The former is tempting, of course, because it is quick to see results. Sometimes, it may be necessary to convince other team members or your management that performance analysis is worth it.

But in general, I recommend tackling the most important performance issues first. This will provide you with the maximum performance improvement, and you may only need to fix a few of these problems to address your performance requirements.

With general performance tuning techniques in mind, let's take a closer look at some Java-specific tuning techniques.

5. Use StringBuilder to programmatically connect strings

There are many different options for connection strings in Java. For example, you can use a simple + or + =, the old StringBuffer, or StringBuilder.

So, which method should you choose?

The answer depends on the code that concatenates the string. If you programmatically add something new to a string, for example, in the for loop, you should use StringBuilder. It is easier to use and provides better performance than StringBuffer. Keep in mind, however, that unlike StringBuilder, it is not thread-safe and may not be suitable for all use cases.

All you need to do is instantiate a new StringBuilder and call the append method to add a new part to the string. When you have added all the parts, you can call the toString() method to retrieve the connection string.

The following code snippet shows a simple example. During each iteration, the loop converts i to a string and adds it to the space of StringBuilder sb, so at the end, this code writes "this is test0123456789" to the log file.


StringBuilder sb = new StringBuilder( " This is a test " );
for (int i=0; i<10; i++) {
 sb.append(i);
 sb.append( "   " );
}
log.info(sb.toString());

As you can see in the code snippet, you can supply the constructor method with the first element of a string. This will create a new StringBuilder containing the supplied string and capacity of 16 additional characters. As you add more characters to StringBuilder, JVM dynamically changes the size of StringBuilder.

If you already know how many characters your string contains, you can supply this number to different constructor methods to instantiate an StringBuilder with defined capacity. This step increases its efficiency because it does not need to dynamically expand its capacity.

6. Use the + connection string in the declaration

When you implemented your first application in Java, you were probably told that you shouldn't use + to concatenate strings. This is true if the string is concatenated in the application logic. Strings are immutable, and the result of each string concatenation is stored in a new string object. This requires extra memory and slows down the application, especially if multiple strings are concatenated in a loop.

In these cases, you should follow tip 5 and use StringBuilder.

But if you're just breaking a string into lines to make your code more readable, that's not the case.


Query q = em.createQuery( " SELECT a.id, a.firstName, a.lastName  " 
+  " FROM Author a  " 
+  " WHERE a.id = :id " );

In these cases, you should concatenate your string with a simple +. The Java compiler optimizes it and executes the connection at compile time. Therefore, at run time, the code USES only 1 character and does not require concatenation.

7. Use basic data types whenever possible

Another quick way to avoid overhead and improve application performance is to use raw data types instead of their wrapper classes. Therefore, it is better to use int instead of Integer, or double instead of Double. This will allow the JVM to store the value on the stack to reduce memory consumption and process it more efficiently.

8. Avoid BigInteger and BigDecimal as much as possible

Now that we've discussed data types, let's look at BigInteger and BigDecimal. The latter, in particular, is popular for its high precision. But it comes at a price. BigInteger and BigDecimal require more memory than simple long or double, and significantly reduce the speed of all calculations. So, if you need extra precision, or if your number exceeds an long range, think twice. This is probably the only thing you need to change to improve performance, especially if you are implementing a mathematical algorithm.

9. First check the current log level

This suggestion is obvious, but unfortunately, you'll find a lot of code that ignores it. Before creating debug messages, you should check the current log level.

Here are two examples of why you shouldn't.


// don't do this
log.debug( " User [ "  + userName +  " ] called method X with [ "  + i +  " ] " );
// or this
log.debug(String.format( " User [%s] called method X with [%d] " , userName, i));

In both cases, you will perform all the steps required to create the log message without knowing whether the logging framework USES the log message or not. It is a good idea to check the current log level before creating debug messages.

10. Use Apache Commons StringUtils. Replace instead of String

1 In general, the ES139en.replace method works well and is very efficient, especially if you are using Java 9. However, if your application requires a lot of replacement operations, and you haven't updated to the latest version of Java, it still makes sense to check for faster and more efficient alternatives.

One candidate is the Apache Commons Lang 's ES149en.replace method. As Lukas Eder described in a recent blog post, it vastly exceeds the ES154en.replace method of Java 8. It requires very little change. You only need to add one Maven dependency to your pom.xml project and replace all calls to String.replace with StringUtils.replace.


// replace this
test.replace( " test " ,  " simple test " );
// with this
StringUtils.replace(test,  " test " ,  " simple test " );

Cache expensive resources, such as database connections

Caching is a popular solution to avoid repetitive execution of expensive or frequently used code snippets. The idea of 1 is simple: it's much cheaper to reuse these resources than to create a new resource over and over again.

A typical example is caching database connections in a pool. Creating new connections takes time, which can be avoided if you reuse existing connections.

Additional examples can be found in the Java language itself. For example, the valueOf method of the Integer class caches values between -128 and 127. You might say that it's not too expensive to create a new integer, but it's often used, and caching the most commonly used values provides a performance benefit.

But when you think about caching, remember that caching implementations also incur overhead. You need to spend extra memory to store reusable resources, so you may need to manage your cache so that resources can access or remove outdated resources.

So before you start caching any resources, make sure you use them regularly.

conclusion

As you can see, improving application performance sometimes doesn't require a lot of work. Most of the Suggestions in this article can be applied to code with a little effort.

But often the most important advice is very language-neutral:

Don't optimize until you know it's necessary Use the profiler to find the real bottleneck Address the biggest bottlenecks first

11 Simple Java Performance Tuning Tips (coordinating editor/wei wei)


Related articles: