Living in the digital era is as much a blessing as it is a frustration. When computers and applications run smoothly, we tend to take them for granted. But when a computer freezes or an application bogs down a laptop or mobile device, poor performance increases stress.
Whether you’re a developer who has access to source code or simply an end user trying to speed up an irritatingly-slow Java application, we have good news: there are steps you can take to improve performance.
Causes of poor application performance
The causes of an underperforming application are vast, though the main causes are a small handful of problems. Naturally, the first two causes are bad design and sub-par coding.
Though IDE’s and code editors highlight test and objects to make them more human-readable, there is a communication gap between computers and humans. You’re going to make mistakes as you build an application – there’s no way around it.
The computer sometimes doesn’t understand your intention. Rather, it only carries out the instructions it is given. In addition to algorithmic and coding flaws, resource deficiencies are another common cause.
Local hardware like RAM and CPU cycles affect application performance, as do other resources like network bandwidth. In addition, note that local operating system problems often affect application performance.
How to improve Java application performance – 10 Steps
Before we dig deeper into each specific step, we wanted to give you a high-level overview regarding how to improve the performance & speed of Java applications, as follows:
- Perform profiling and load testing – profiling your Java application will help uncover pain points and bottlenecks.
- Clean up code and revise algorithms – clean code is easier to maintain, edit, read, and refine; adjusting your algorithms reduces tautological function calls.
- Avoid recursion when possible – recursion is a necessary evil in some cases, though it should be used sparingly.
- Upgrade system hardware – poor application performance may not be rooted in poor code and algorithms; instead, the underlying cause may be related to the system running the application.
- Increase LAN and WAN bandwidth – not all functions interact with local hardware; if your application talks with remote systems, your network infrastructure may be to blame.
- Tidy up your operating system – messy operating systems need maintenance to run applications to the best of their ability.
- Employ StringBuilder for improved efficiency – strings are nearly unavoidable in most applications, but the “+=” operation is highly inefficient.
- Exploit caching for improved performance – caching is one of the best ways to reduce redundant object instantiation and function calls.
- Favor the stack over the heap – the stack is more efficient with regards to system resources because it maintains itself and frees up the memory which defunct objects occupy.
- Influence memory management – as a final resort, accessing heap memory is sometimes necessary. It will require you to choose the right garbage collection method.
With that said, let’s delve a little deeper into each method to improve Java application performance.
Related post: Apache Tomcat Monitoring
1. Perform profiling and load testing
The first step to improving the performance of Java applications is to identify areas for improvement. Without knowing where the largest problems are, trying to speed up an application is like throwing a dart at a dartboard while blindfolded. Fortunately, there are software tools that gather data regarding application performance.
The first tool we would recommend using is Java-profiling software. One such example is YourKit, which is a solution that’s appropriate for enterprises as well as individual users. YourKit offers a variety of licenses such as enterprise, academic, open-source, and per-seat options.
Its features include, but are not limited to, the following:
- CPU performance profiling
- Memory performance profiling
- Memory leak detection
- Threads and synchronization analysis
- Web, database, and I/O performance testing
This tool was designed to be used on local systems, remote systems, projects currently in development, and even production software. YourKit isn’t the only quality Java-profiler, however, and has strong competition from other solutions like EJ-Technologies, which created the JProfiler.
This tool is very similar to YourKit and provides testing options for local and remote sessions, offline triggers, snapshot comparisons, request tracking (HTTP, RMI, web service, EJB, AWT events, thread starts, etc.), and custom probe creation among many other features. Lastly, JProfiler also has hardware, thread, telemetry, and database profiling tools as well.
2. Clean up code and revise algorithms
Software source code is like the first draft of a novel – it’s never perfect after the first iteration and requires significant revisions before it’s finally polished. First and foremost, you should strive to write clean code that’s saturated with comments so you can navigate through your text and easily remember why each object was instantiated and why each function was called.
Though keeping your code clean won’t on its own speed up a Java application, it does help you more efficiently prune unnecessary code and look for weaknesses and redundancy in your algorithms. Clean code makes the debugging process more smooth too and helps you correct errors (like memory leaks) that you identified after profiling and load testing your software.
Another option worth considering is the Java profiler that is part of the Datadog APM package. This application manager is a cloud-based system. It tracks Java application resource usage by class and method and offers a step-through view of the Java code, following the execution through APIs and function calls. The service also tracks wider application and website performance metrics, such as availability and response times.
3. Avoid recursion when possible
Recursion is a basic programming strategy in which a function calls itself to solve a problem. Recursion isn’t necessarily evil, however, it is a double-edged sword. It does have benefits and practical uses. For instance, recursion makes code cleaner and more compact and reduces tautological iterations of loops and functions.
Because recursion reduces the complexity and volume of code, it helps make a Java application easier to maintain. Unfortunately, these benefits come at the price of application performance. Recursion and function calls are expensive with regards to speed and performance because they require pop and push functions on the stack.
A more efficient solution to recursion, on the other hand, is looping. Looping operations may be harder to read and are composed of more characters and source code than recursion, but they are preferable when application performance must be maximized. It may not always be practical to forgo recursion altogether, but mitigating it as much as possible will help improve the performance of Java applications.
4. Upgrade system hardware
On the surface, it may seem that an underperforming Java application must be caused by a software issue. Though it may seem counter-intuitive, the root cause of poor performance may be a hardware issue. Whether you’re running a Java application locally or remotely, the system in which the Java application is running may not have enough system resources to execute the code satisfactorily.
The easiest component to upgrade that typically directly impacts software performance is RAM. By expanding the amount of physical memory in your computer, you reduce the chance of the system running out of storage space for application data and being forced to revert to using the hard drive as impromptu RAM. This practice is known as a page file, and though it does have practical uses, page files are more undesirable for high-performance applications than RAM because the read and write process is significantly slower on a hard drive.
Furthermore, if your Java application interacts significantly with the hard drive (such as a data backup or syncing application), upgrading the hard drive can improve system performance. For instance, you could upgrade to a drive with higher RPMs for faster read and write procedures. However, note that the fastest hard drives are SSDs (Solid State Drives) and contain no moving parts. Lastly, some systems, but not all, are capable of expanding the CPU as well, though it’s not as easy to upgrade as RAM.
5. Increase LAN and WAN bandwidth
Though local and server hardware is a key component of Java applications – including Java web applications – it isn’t the only potential bottleneck. Whether you host Java web applications locally on a LAN, on an intranet, or remotely, bandwidth plays a crucial role with regards to the availability and performance of your applications – especially during times of peak load.
Generally speaking, LAN bandwidth is typically plentiful and inexpensive, whereas WAN bandwidth is expensive and in short supply. Nevertheless, other LAN traffic may be crowding out the local network to which the server running Java web applications is connected. This problem can be dealt with in one of two ways.
First, you can choose to upgrade switches and local network infrastructure to a Gigabit Ethernet or 10Gigabit Ethernet solution to alleviate network congestion. Alternatively, you could implement a QoS (Quality of Service) policy that prioritizes traffic to and from the server hosting Java applications above other types of traffic.
Furthermore, in addition to increasing WAN bandwidth (which may require selecting a different ISP), you can implement load balancing to better take advantage of redundant ISP connections. Remember, poor performing Java applications may not necessarily be caused by software issues and can be affected by network congestion.
6. Tidy up your operating system
Operating systems require housekeeping and maintenance to run at peak performance and to efficiently use system resources. An unkempt operating system has a tendency to slow down and feel sluggish. For example, you may have many invisible processes running in the background you didn’t even know about.
Furthermore, the installation procedure of many applications automatically adds new software to the list of applications that launch on startup. Not only do these applications cause a system to boot more slowly, but they also hog vital system resources like RAM and CPU cycles that could otherwise be used to run a Java application.
Pruning necessary and undesirable process and applications will help tidy up your operating system, as will running a registry cleaner on Windows systems. A registry cleaner can mitigate and eliminate DLL errors and runtime errors by removing erroneous entries in the registry and regenerating index structures.
Furthermore, another housekeeping task – which should be used sparingly – is defragmenting your hard drive. Over time, as files are continuously written to and deleted from a hard drive, they can become a sprawling, chaotic mess. A defrag-tool rounds up all of your data and rewrites it into contiguous blocks, which reduces the time for the hard drive to read information.
But be forewarned: the defragmentation process is very demanding and taxing on the hard drive. If you defragment it daily, weekly, or even monthly, you will drastically decrease the lifespan of your hard drive. A more reasonable and practical frequency is about two to three times per year.
7. Employ StringBuilder for improved efficiency
Strings are a common data type in the vast majority of applications – they’re pretty hard to avoid or ignore. Unfortunately, string concatenation is as inefficient as it is frequent. It may seem like the best way to concatenate strings is with the “+=” operation, but this is an overused operation that is wildly inefficient.
The problem is that the “+=” operation allocates an entirely new string every time it is used, which can create a lot of redundant and unnecessary string data. Though using this operation now and then won’t cause your Java application to grind to a halt, every little operation adds up. Imagine how many times this inefficiency could be magnified in a loop?
Fortunately, there is a more efficient alternative. The StringBuilder object was designed as an efficient alternative to basic concatenation operations. Get into the habit of using this data type instead of using the lazy shorthand of the “+=” operation if you want to improve the performance of Java applications.
8. Exploit caching for improved performance
Caching is one of the most effective ways to increase the efficiency of Java applications. The core concept of caching is to consolidate multiple calls and functions into one reusable resource. Though there are many forms of caching, when most people here the word ‘caching,’ the first thing that springs to mind is database technology.
Not only can the data contained within a database be cached locally to conserve bandwidth, but so too can the high volume of database connections that would otherwise slow down an application. Nevertheless, be aware that caching shouldn’t be overused. You might intuitively think everything should be cached in case you need to use it again in the near future. However, you need to ensure that the data and connections you cache are numerous enough to see a gain in performance.
9. Favor the stack over the heap
For improved Java application performance, store your data on the stack instead of the heap. Why you ask? Because it uses memory more efficiently and removes data that has already served its purpose. Not only does the stack more efficiently use resources, but it also keeps overhead in check. The best way to favor the stack over the heap is to avoid instantiating wrapper classes.
Just like the use of recursion, it isn’t always feasible (or even possible) to forgo the use of a wrapper class. But as a general rule of thumb, you should default to using primitive data types like “int” as opposed to Integer. The number of performance gains you can reap from employing this tactic depends on a myriad of factors. Nevertheless, favoring the stack is a good guide to find the path towards the leanest and most efficient Java code.
There are tools on the market to help you successfully update Java patches. One such tool is SolarWinds Patch Manager.
You can download a fully-functioning 30-day free trial
Related: Best Patch Management Tools
In addition, you should check out the SolarWinds Java Monitoring Tool for Application Servers with Server & Application Monitor to improve performance. You can download a fully-functioning 30-day free trial.
10. Influence memory management
Although using the stack is better than accessing heap memory, sometimes it is unavoidable. The big performance issue is with memory availability because a shortage of memory will slow down all of your applications and Java applications are particularly heavily dependent on memory.
The JVM takes care of releasing memory by removing unreferenced objects, which are no longer in scope, through garbage collectors. However, it is a myth that garbage collection is a black box and there is no way to influence its behavior. You can adjust the heap size and alter the heap garbage collection algorithm. It is also possible to alter the proportion of new-generation heap to old-generation heap size. So, there are many ways to improve memory management and alter the frequency of collection of abandoned memory space.
A big performance issue with garbage collectors is that they freeze application threads during memory release operations. So, it is important to choose the right garbage collection strategy according to whether you prioritize speed, scalability, or CPU optimization.
The four options are:
- Serial Garbage Collector – For a single-threaded environment. Good for CPU optimization but not good for scalability.
- Parallel Garbage Collector – This is the default and it is designed for multi-thread systems. Like Serial GC, it freezes all application threads while collecting. Uses a lot of CPU.
- Concurrent Mark Sweep – Selectively removes memory handlers, freeing up patches of space. Will freeze only processes that are accessing the same memory area that is being adjusted. Very CPU intensive but great for scalability.
- G1 Garbage Collector – Segments large heap memory spaces into segments. Only blocks applications trying to access a region that is under collection. Good for CPU optimization and scalability but creates overhead in memory.
Java Application Performance FAQs
How do you handle performance issues in Java?
Most performance issues with Java code don’t relate to the code itself but to system resource availability. Therefore, expect issues to arise once the code is live that weren’t spotted during development. Re-examine performance at each stage of the system lifecycle.
How do you optimize Java application code?
Avoid oversized heap allocation in your code by starting with the minimum memory allocation in the code and then add space on later if it seems that more is required. Look to tighten up any procedure that can lock resources and optimize SQL data fetches by examining SQL optimization – resource shortages and interactions with other systems provide the biggest causes of performance problems in Java code.
Will converting my Java application to Node.js improve performance?
Be careful about switching systems, especially to new programming frameworks that you are not familiar with. Node.js is lauded for its speed but it might not have all of the capabilities that you need for your application. If you are tempted by Node.js, write a new application in that environment first rather than trying to port over an existing Java program. If you have issues with Java performance it is most likely to be an issue of resource availability, which will continue no matter which framework you wrote the code in.
If you have any personal stories or anecdotes regarding speeding up Java applications, we invite you to comment below.