1. Garbage collection & Just-In-Time (JIT) compilation
Welcome back. Now, we'll dive into two critical components of the Java Virtual Machine: Garbage Collection and Just-In-Time Compilation. These features work behind the scenes but have a profound impact on our application's performance!
2. Garbage collection - the basics
Let's start with Garbage Collection. In languages like C or C++, programmers must manually allocate and free memory, which is prone to leaks.
Java, however, handles memory management automatically through garbage collection.
The JVM's garbage collector identifies objects that are no longer referenced by our program and reclaims their memory. This prevents memory leaks, but the process isn't free - it requires CPU time that temporarily pauses our application execution, which we call "stop-the-world" pauses.
3. How garbage collection works
The garbage collector uses a "mark and sweep" approach:
For 'Mark', the JVM identifies all reachable (live) objects by following references from "root objects".
Then, for 'Sweep', it removes unreachable objects and compacts memory.
4. Garbage collection and performance
While garbage collection, or GC, simplifies development, it comes with performance considerations as mentioned before.
Modern JVMs offer several GC algorithms optimized for different scenarios. The choice depends on the application's requirements for responsiveness versus throughput.
A great guide regarding various GC algorithms can be found in the Baeldung website.
5. GC performance impact
Garbage collection can significantly impact performance. Creating numerous short-lived objects, as shown in the first example, generates excessive garbage that triggers frequent collections. The second example still creates the same number of objects, but maintains only one reference, which is slightly better. However, object creation and collection still have a cost. For performance-critical sections, consider reusing objects or avoiding unnecessary object creation entirely!
6. GC-friendly coding
Let's look at a very common GC-friendly coding pattern. This example compares string concatenation approaches. The first method creates thousands of String objects that immediately become garbage. The second approach uses `StringBuilder`, a Java class that is used to modify strings without creating excess objects. Generally, it's a good practice to avoid creating an excessive number of objects, unless absolutely required.
7. Just-In-Time compilation
Now let's explore Just-In-Time, or JIT, compilation. When we run a Java program, the source code is compiled to platform-independent bytecode. The JVM initially interprets this bytecode line by line, but it also monitors execution to identify "hot" methods - code that's executed frequently. These hot methods are then compiled to highly optimized native machine code by the JIT compiler. This gives Java both the portability of interpretation and performance close to native code.
8. JIT-friendly code patterns
Let's examine some JIT-friendly coding patterns. The JIT compiler performs better with predictable code. Write code with predictable branch paths, as shown here. Follow the single responsibility principle to create focused methods that are easier to optimize. Small, frequently called methods are ideal candidates for inlining. Finally, remember that complex code with many code paths may be optimized less effectively.
9. Summary
To summarize, optimize our code by minimizing object creation in performance-critical paths, and write predictable code that aligns with JIT optimization capabilities.
Most importantly, focus optimization efforts on the code that actually matters - measuring first to identify real bottlenecks rather than optimizing prematurely.
10. Let's practice!
Now, let's practice!