Most developers take it for granted that performance optimization is complex and requires a lot of experience and knowledge. Well, it can't be said that this is completely wrong. Optimizing your application for optimal performance is not an easy task. However, this does not mean that you can't do anything if you don't have this knowledge. Here are 11 easy-to-follow tips and best practices to help you create a well-performing application.
Most of the suggestions are for Java. But there are several suggestions that are language-independent and can be applied to all applications and programming languages. Before discussing performance tuning techniques specifically for Java, let's take a look at general tips.
1. Don't optimize until you know it is necessary
This is probably one of the most important performance tuning tips. You should follow common best practices and try to implement your use cases efficiently. However, this does not mean that you should replace any standard library or build complex optimizations before you prove necessary.
In most cases, premature optimization will not only take up a lot of time, but also make the code difficult to read and maintain. Worse, these optimizations usually don't bring any benefits, because what you spend a lot of time optimizing is the non-critical part of the application.
So, how do you prove that you need to optimize something?
First, you need to define how fast your application code is, for example, specifying a maximum response time for all API calls, or specifying the number of records to be imported in a specific time range. Once you've done this, you can measure which parts of the application are too slow to improve. Then, let’s look at the second trick.
2. Use the analyzer to find the real bottleneck
After you followed the first suggestion and determined that some parts of the application need improvement, then where to start?
You can solve the problem in two ways:
Check out your code and start with the part that looks suspicious or you feel may be problematic.
Or use the analyzer and get detailed information about the behavior and performance of each part of the code.
Hopefully I don't need to explain why I should always follow the second method.
It's obvious that a parser-based approach allows you to better understand the performance impact of your code and enable you to focus on the most critical parts. If you've used a parser, you must remember how surprised you were to find out which parts of the code had performance problems. To be honest, my first guessing has led me to the wrong direction more than once.
3. Create a performance test suite for the entire application
This is another universal tip that can help you avoid many unexpected problems that often occur after deploying performance improvements to production. You should always define a performance test suite that tests the entire application and run it before and after performance improvements.
These additional test runs will help you identify the functional and performance side effects of the changed and make sure that no harm than good updates are caused. This is especially important if you work on components used by several different parts of the application, such as databases or caches.
4. First deal with the biggest bottleneck
After creating a test suite and analyzing the application using the analyzer, you can list a range of issues that need to be addressed to improve performance. This is great, but it still doesn't answer the question where you should start. You can focus on quick-acting solutions, or start with the most important questions.
Quick-acting schemes can be attractive at first because you can quickly show the first result. But sometimes, you may need to convince other team members or management that performance analysis is worth it - because there is no effect at the moment.
But overall, I recommend dealing with the most important performance issues first. This will provide you with the greatest performance improvements and may no longer need to solve some of these issues to meet performance requirements.
Common performance tuning tips end here. Let's take a closer look at some Java-specific tips.
5. Use StringBuilder to connect String programmatically
There are many different options to connect Strings in Java. For example, you can use simple + or + =, as well as StringBuffer or StringBuilder.
So, which method should you choose?
The answer depends on the code connecting the String. If you are adding new content to a String programmatically, for example in a for loop, then you should use StringBuilder. It's easy to use and offers better performance than StringBuffer. But remember that compared to StringBuffer, StringBuilder is not thread-safe and may not be suitable for all use cases.
You just need to instantiate a new StringBuilder and call the append method to add a new part to the String. After you have added all the parts, you can call the toString() method to retrieve the connected String.
The following code snippet shows a simple example. During each iteration, this loop converts i to a String and adds it to StringBuilder sb along with a space. So, at the end, this code will write "This is a test0 1 2 3 4 5 6 7 8 9" in 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 provide the first element of the String to the constructor. This will create a new StringBuilder that contains the provided String and 16 extra characters capacity. When you add more characters to the StringBuilder, the JVM will dynamically increase the size of the StringBuilder.
If you already know how many characters your String will contain, you can provide that number to different constructors to instantiate a StringBuilder with defined capacity. This further improves efficiency because it does not require dynamic expansion of its capacity.
6. Use + to concatenate the String in a statement
When you implement your first application in Java, someone may have told you that you should not use + to connect to String. If you are concatenating strings in application logic, this is correct. Strings are immutable, and the results of the concatenation of each string are stored in a new String object. This requires extra memory and will slow down your application, especially if you concatenate multiple strings within a loop.
In these cases you should follow Tip 5 and use StringBuilder.
But if you just split the string into multiple lines to improve the readability of your code, that's different.
Query q = em.createQuery("SELECT a.id, a.firstName, a.lastName"
+ “FROM Author a”
+ “WHERE a.id = :id”);
In these cases, you should use a simple + to concatenate your string. The Java compiler optimizes this and performs connections at compile time. So, at runtime, your code will only use 1 String and no connection is required.
7. Use primitives as much as possible
Another easy and quick way to avoid any overhead and improve application performance is to use the basic types instead of their wrapper classes. Therefore, it is best to use int instead of Integer and double instead of Double. This allows the JVM to store values on the stack rather than the heap to reduce memory consumption and make more efficient processing.
8. Try to avoid BigInteger and BigDecimal
Since we are discussing data types, let's take a quick look at BigInteger and BigDecimal. In particular, the latter is popular for its accuracy. But there is a price.
BigInteger and BigDecimal require more memory than simple long or double and will significantly slow down all calculations. So, if you need extra precision, or the numbers will exceed the long range, it is best to think twice before doing it. This is probably the only way you need to change to solve performance problems, especially when implementing mathematical algorithms.
9. First check the current log level
This suggestion should be obvious, but unfortunately, many programmers mostly ignore it when writing code. Before you create a debug message, you should always check the current log level first. Otherwise, you might create a log message string that will be ignored later.
Here are two negative examples.
// don't do thislog.debug("User [" + userName + "] called method X with [" + i + "]");// or thislog.debug(String.format("User [%s] called method X with [%d]", userName, i));In both cases, you will perform all the necessary steps to create a log message without knowing whether the log framework will use the log message. Therefore, it is best to check the current log level before creating a debug message.
// do thisif (log.isDebugEnabled()) { log.debug("User [" + userName + "] called method X with [" + i + "]");}10. Use Apache Commons StringUtils.Replace instead of String.replace
Generally speaking, the String.replace method works fine and is very efficient, especially when using Java 9. However, if your application requires a lot of replacement operations and is not updated to the latest Java version, then it is still necessary to find faster and more efficient alternatives.
There is an alternative answer to Apache Commons Lang's StringUtils.replace method. As Lukas Eder described in a recent blog post, the StringUtils.replace method is far superior to Java 8's String.replace method.
And it only requires minor changes. That is, add the Maven dependencies of the Apache Commons Lang project to the application pom.xml, and replace all calls to the String.replace method with the StringUtils.replace method.
// replace thistest.replace("test", "simple test");// with thisStringUtils.replace(test, "test", "simple test");11. Cache expensive resources, such as database connections
Caching is a popular solution to avoid repeated execution of expensive or common code snippets. The general idea is simple: it is cheaper to reuse these resources than to repeatedly create new resources.
A typical example is a database connection in a cache pool. New connections take time to create, which can be avoided if you reuse existing connections.
You can also find other examples in the Java language itself. For example, the valueOf method of the Integer class caches values between -128 and 127. You might say that creating a new Integer is not too expensive, but because it is often used, caching the most commonly used values can also provide performance benefits.
However, when you consider caching, remember that cache implementations can also incur overhead. You need to spend extra memory to store reusable resources, so you may need to manage caches to make resources accessible, as well as delete outdated resources.
So, before you start caching any resources, make sure that implementing the cache is worth it, that is, you have to use them enough.
Summarize
As you can see, sometimes it doesn't take much work to improve the performance of your application. Most of the suggestions in this article require just a little effort to apply them to your code.
But the most important thing is the tricks that are not related to what programming language it is:
Don't optimize using analyzers to find real bottlenecks before you know it's necessary. First deal with the biggest bottlenecks.
The above is all the content shared in this article about the very simple and easy-to-understand Java performance tuning techniques. I hope it will be helpful to everyone. Interested friends can continue to refer to this site:
Java implements detailed code for sharing functions of WeChat public platform WeChat Moments
Comparative analysis of performance instances of Java native serialization and Kryo serialization
Exploration on whether Java programming subclass can rewrite the static method of the parent class
If there are any shortcomings, please leave a message to point it out. Thank you friends for your support for this site!