5.3. Performance tips
Last updated: 4 May 2016.
This page will give you tips in improving performance when building Java applications.
There is much to say about performance which is a crucial issue for an application. Rather than being an exhaustive list, the performance tips you will find in this page come from what my experience has taught me over the years. I'm not pretending to be a performance expert, but I have used some of these tips to fix performance problems that I encountered in the past.
5.3.1. Profiling tool
Use a profiling tool to analyze in depth the behaviour of your application. A profiling tool gives information about memory usage, created objects, threads, methods execution time, garbage collector activity, etc. I have used JProfiler in several occasions to fix performance bugs but other profiling tools are available as well.
5.3.2. Object instantiation
Avoid instantiating too many objects with the keyword new because object instantiation is costly. Whenever possible, reuse the same instances instead of creating new ones. The garbage collector will have much more work to do if you create too many objects. Every time garbage collection occurs, it takes up available CPU cycles at the expense of your application. Therefore, the less objects you create, the better your application performs.
5.3.3. Custom garbage collection
Even though you have strived to minimize object instantiation, your application may still create objects much faster than they are garbage collected. In that case, an OutOfMemoryError is thrown every time the application runs out of memory. Such a problem can be fixed by using a custom garbage collector instead of the default garbage collector. Depending on your application's requirements, you can fine-tune the garbage collection strategy with command line options. For example, the following command starts MyMainClass with several GC options:
java -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -XX:-DisableExplicitGC MyMainClass
- -XX:+UnlockExperimentalVMOptions simply unlocks experimental options, which means you can use experimental options after this flag is set.
- If you are using Java SE 6 or an earlier version, -XX:+UseG1GC switches to the G1 garbage collector (the default for Java SE 7) which provides more efficient garbage collection than the default Java SE 6 garbage collector.
- -XX:-DisableExplicitGC says that when System.gc() is called, it should not perform a full GC. Explicit calls to System.gc() are not recommended anyway.
5.3.4. Heap size allocation
When a Java application starts, the operating system allocates memory to the JVM. The allocated memory contains an area called the heap in which objects are created. Hence, created objects reside in the heap and when an object is garbage collected, the corresponding memory becomes available again in the heap. Professional applications usually require more heap size than the default size defined by the operating system. In that case, you can increase the heap size at startup with the -Xms and -Xmx command line options. The -Xms option sets the initial heap size whereas the -Xmx option sets the maximum heap size. Here is an example:
java -Xms32m -Xmx1024m MyMainClass
The above command sets the initial heap size to 32 MB and the maximum heap size to 1 GB at startup. If you have a sufficiently large heap, the activity of the garbage collector will be reduced as the heap takes longer to be filled with objects.
5.3.5. String concatenation
As I said in the tutorial Working with strings (section 5.3.2), when you need to perform string concatenations, use StringBuffer or StringBuilder instances instead of the operator +. In a multithreaded context, you can safely use StringBuffer since all its public methods are synchronized. In contrast, the methods of StringBuilder are not synchronized. Consequently, StringBuilder is faster than StringBuffer.
Wherever feasible, use multiple threads for your application to perform faster. In particular, if a profiling tool shows that your application does not take up much of the CPU, you can create multiple threads and run them concurrently for better performance. Also, avoid using the keyword synchronized as much as possible because serialized access is likely to slow down the execution of a program.
5.3.7. Database access tips
If some SQL requests take a long time to execute, check if the columns that are referred to in the WHERE clause have indexes. Usually, setting an index on a table's column is enough. Many times, I've seen an application run 10 times faster after an index was set on a table's column.
Another key point is that to avoid multiple database accesses, you can use a cache wherein you load the database information that your application requires. Typically, the cache is a collection of objects loaded from the database. You can load all the database information at startup (if you have enough memory) or, on demand: every time a piece of information is needed, it is first searched for in the cache. If it is not found in the cache, it is loaded from the database and added to the cache. Afterwards, instead of accessing the database to get that piece of information, you read it from the cache, which is much faster. Naturally, the cache information must be coherent, that is if some database information is updated, the corresponding object contained in the cache (if any) must also be updated. Alternatively, you can simply remove the outdated object from the cache. That way, if the corresponding data is needed later, it will be reloaded from the database and added to the cache.
Last but not least, use a connection pool to avoid creating a new connection for every SQL statement and use transactions to reduce the calls to the method commit.