Mitigating the Challenges
The question is: What can you do to mitigate these challenges? In this article, we’ll delve into the latest approaches that are being utilized for improving the performance of Java applications. Starting with common Java code level optimizations as well as best coding practices.
After that we will do a deep dive into defining measurable performance goals and to look at different tools to measure, monitor application performance and identify bottlenecks.
Finally, we’ll look at the how and why of JVM specific tuning tips and improving the performance of Java applications with architectural changes.
Understanding your performance goals
Even before we begin to performance test your Java applications and then setting in place a plan for improving the applications, we need to define and understand our non-functional requirements around key areas such as scalability, performance, availability, etc.
Typical web applications often have consistent frequently used performance:
1. Average application response time
2. Average concurrent users must the system support
3. Expected requests per second during peak load
Using metrics like these which can be measured via different load testing and application monitoring tools helps to find key bottlenecks and tune performance accordingly.
Optimization tools help to identify bottlenecks
Application Performance Management (APM) solutions and Load testing tools are commonly used to track and optimize the performance of Java applications. The latest APM tools are key to identifying bottlenecks with full range load tests around different application scenarios while simultaneously monitoring CPU, IO, and Heap usage.
One of the best load testing tools is Gatling , which provides excellent support of the HTTP protocol – which makes it an excellent choice for load testing any HTTP server.
One of the best ways to determine the baseline of your application is to use Stackify’s Retrace , which is knows to be a mature APM solution with a rich set of features. One of the key components of Retrace is its code profiling which collects runtime information without slowing down the application.
Retrace also provides widgets for monitoring Memory, Threads, and Classes for a running JVM based application. Other than application metrics, it also supports monitoring CPU and IO usage of the server which is hosting our application.
So, a full-fledged monitoring tool such as Retrace covers the first part of unlocking the performance potential of your application. The second part is being able to reproduce real-world usage and load in your system.
That’s harder to achieve than it looks, and it’s also critical to understand the current performance profile of the application. That’s what we’re going to be focusing on next.
Load testing the API’s of an application is helpful in finding subtle, hard to find bugs like DB connections getting exhausted, requests getting timed out during high loads, undesirable high heap usage due to memory leaks, etc.
Good Coding starts with optimizations
Good Coding practices can go a long way to ensure performance issues are at a minimum. Load testing and application monitoring are quite helpful in identifying some of the key issues in the application.
• Using StringBuilder for String Concatenation
String concatenation is a very common operation, and an inefficient one. Simply put, the problem with using += to append Strings is that it will cause an allocation of a new String with every new operation.
Using the StringBuilder in the code above is significantly more efficient, especially given just how common these String-based operations can be.
Before we move on, note that the current generation of JVMs does perform compile and or runtime optimizations on Strings operations.
• Avoid Recursion
Recursive code logic leading to StackOverFlowError is another common scenario in Java applications.
If we cannot do away with recursive logic, tail recursive as an alternative is better.
Other JVM languages, such as Scala, already have compiler-level support to optimize tail recursive code, and there’s discussion around bringing this type of optimization to Java as well.
• Use Regular Expressions Carefully
Regular expressions are useful in many coding scenarios, but they often have a very big performance cost. It’s also important to be aware of a variety of JDK String methods, which use regular expressions, such as String.replaceAll(), or String.split().
If you absolutely must use regular expressions in computation-intensive code sections, it’s worth caching the Pattern reference instead of compiling repeatedly:
Using a popular library like Apache Commons Lang is also a good alternative, especially for manipulation of Strings.
• Creating and destroying threads can cause big performance issues
Creating and disposing of threads is a common cause of performance issues on the JVM, as thread objects are relatively heavy to create and destroy.
If your application uses a large number of threads, using a thread pool makes a lot of sense, to allow these expensive objects to be reused.
Monitoring applications=high performance
Monitoring your applications is one of the first steps to understanding what your challenges are. To get started with using Retrace for a Java application, the first step is to sign up for a free trial here, on Stackify.
Next, you’ll need to configure Spring Boot application as Linux service. Installing Retrace agent on the server where your application is hosted as mentioned here.
Once the Retrace agent is started and the Java application is being monitored, we can go to Retrace dashboard and click AddApp link. Once this is done, Retrace will start monitoring our application.
• Identifying the slowest part of your stack
Retrace automatically instruments our application and tracks usage of dozens of common frameworks and dependencies, including SQL, MongoDB, Redis, Elasticsearch, etc. Retrace makes it easy to quickly identify why our application is having performance problems like:
• Is a certain SQL statement slowing us down?
• Is Redis slower all of a sudden?
• Specific HTTP web service down or slow?
JVM Tuning is not always straightforward
• Heap Size Tuning
The first step to JVM heap size is determine the predictable memory requirements by asking answering the following questions:
- How many different applications we are planning to deploy to a single JVM process, e.g., the number of EAR files, WAR files, jar files, etc.
- How many Java classes will be potentially loaded at runtime; including third party API’s
- Estimate the footprint necessary for in-memory caching, e.g., internal cache data structures loaded by our application (and third party API’s) such as cached data from a database, data read from a file, etc.
- Estimate the number of Threads that the application will create
These numbers are difficult to estimate without some real-world testing.The most reliable way to get a good idea about the what the application needs are – is to run a realistic load test against the application and track metrics at runtime. The Gatling-based tests we discussed earlier are a great way to do that.
• Choosing the Right Garbage Collector
In Java, a common issue in application performance is inefficient garbage collection (GC). GC problems cause distinct issues for the responsiveness and overall Java performance in the progression of applications at the point where GC takes place, as opposed to a general slowdown.
However, the current generation of garbage collectors has mostly solved that issue and, with proper tuning and sizing, can lead to having no noticeable collection cycles. That being said, it does take an in-depth understanding of both GC on the JVM as a whole, but also the specific profile of the application – to get there.
Tools like a profiler, heap dumps, and verbose GC logging can certainly help. And again, these all need to be captured on real-world load patterns, which is where the Gatling performance tests we discussed earlier come in.
Since most companies are moving to continuous development and deployment, they also need to ensure they continue to continuously test the applications to sustain performance levels. Developers know that the place where problems most commonly arise is in a change cycle, where time pressure and a focus on features can override awareness of performance issues.
Putting into practice the best plan
With this article, we explored a number of different concepts around improving the performance of a Java application. Ensuring you are using good APM tools and load testing often while keeping an eye on your server performance is important.
Having and adhering to best practices around writing performant Java code is above all the most important. Database side optimizations and architectural changes to scale your application and specific JVM tuning tips are not to be missed.