Boost Java Performance: 15 Proven Code Optimization Tricks
This article presents a collection of practical Java performance tips, covering visibility reduction, bit‑shift arithmetic, minimizing repeated calculations, avoiding RuntimeException catches, using local variables, lazy loading, proper static access, StringBuilder concatenation, hashCode implementation, collection pre‑sizing, loop object handling, efficient Map traversal, thread‑safe random usage, LongAdder counters, and reflection reduction.
Introduction
Today I share some frequently used code optimization techniques from my daily work.
Main Content
Minimize Visibility of Class Members and Methods
Example: Delete a private method if it is no longer needed. For a public service method or public field, remove it without overthinking.
Use Bit‑Shift Operations Instead of Multiplication/Division
Computers use binary representation; bit‑shifts greatly improve performance.
Left shift (<<) equals multiplication by 2; right shift (>>) equals division by 2; unsigned right shift (>>>) also divides by 2 but fills the left bits with 0.
a = val << 3;
b = val >> 1;Avoid Repeated Variable Calculations
Method calls incur overhead such as stack frame creation and state preservation.
// Bad example
for (int i = 0; i < list.size(); i++) {
System.out.println("result");
}
// Good example
for (int i = 0, length = list.size(); i < length; i++) {
System.out.println("result");
}When list.size() is large, caching the size reduces cost.
Do Not Catch RuntimeException
RuntimeException should be avoided through code logic rather than catch blocks. For example, check bounds before accessing a list instead of catching IndexOutOfBoundsException.
public String test1(List<String> list, int index) {
try {
return list.get(index);
} catch (IndexOutOfBoundsException ex) {
return null;
}
}
// Good example
public String test2(List<String> list, int index) {
if (index >= list.size() || index < 0) {
return null;
}
return list.get(index);
}Use Local Variables to Avoid Heap Allocation
Objects allocated on the heap increase GC pressure. Using local variables keeps data on the stack, which is reclaimed after method execution.
Reduce Variable Scope
Limit the lifetime of variables to the smallest possible scope. Move variable declarations inside conditional blocks when appropriate.
public void test(String str) {
final int s = 100;
if (!StringUtils.isEmpty(str)) {
int result = s * s;
}
}Adopt Lazy‑Loading Strategy
String str = "example";
if (name == "public") {
list.add(str);
}
// Better: create the object only when needed
if (name == "public") {
String str = "example";
list.add(str);
}Access Static Variables via Class Name
Using an object to access static members adds an extra lookup step.
// Bad example
int i = objectA.staticMethod();
// Good example
int i = ClassA.staticMethod();String Concatenation Using StringBuilder
Prefer StringBuilder (or StringBuffer) over the '+' operator for concatenation.
// Bad example
String str = "111";
str += "222";
str += "333";
System.out.println(str);
// Good example
StringBuilder sb = new StringBuilder("111");
sb.append("222");
sb.append("333");
System.out.println(sb.toString());Override Object's hashCode Properly
Returning a constant value (e.g., 0) for hashCode degrades HashMap performance because it disables effective hashing.
Initialize Collections with Appropriate Size
Specifying an initial capacity for collections like ArrayList or StringBuilder reduces resizing overhead.
Avoid Creating Object References Inside Loops
// Bad example
for (int i = 1; i <= size; i++) {
Object obj = new Object();
}
// Good example
Object obj = null;
for (int i = 0; i <= size; i++) {
obj = new Object();
}Creating many objects inside a loop can consume large amounts of memory.
Iterate Maps Using entrySet
Using entrySet() avoids an extra get call required when iterating with keySet().
Set<Map.Entry<String, String>> entrySet = nmap.entrySet();
for (Map.Entry<String, String> entry : entrySet) {
System.out.println(entry.getKey() + "," + entry.getValue());
}Do Not Share a Single Random Instance Across Threads
Concurrent access to Random causes contention; use ThreadLocalRandom in multithreaded contexts.
public static void main(String[] args) {
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("Thread1:" + threadLocalRandom.nextInt(10));
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println("Thread2:" + threadLocalRandom.nextInt(10));
}
});
thread1.start();
thread2.start();
}Prefer LongAdder Over AtomicLong for High‑Concurrency Counters
AtomicLong uses CAS and may cause contention under heavy load; LongAdder provides better scalability.
public class Test {
public int longAdderTest(Blackhole blackhole) throws InterruptedException {
LongAdder longAdder = new LongAdder();
for (int i = 0; i < 1024; i++) {
longAdder.add(1);
}
return longAdder.intValue();
}
}Minimize Use of Reflection
Reflection incurs performance overhead due to bytecode parsing. Cache reflective objects (e.g., Method) or use alternatives like java.lang.invoke introduced in Java 7.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
macrozheng
Dedicated to Java tech sharing and dissecting top open-source projects. Topics include Spring Boot, Spring Cloud, Docker, Kubernetes and more. Author’s GitHub project “mall” has 50K+ stars.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
