How JVM Works Internally?
First, we create a .java file, then compile it with javac MyApp.java, which produces a .class file containing bytecode.
#JVM #heap #stack #meta_space #native #pc_register #class_loader #boostrap #extension #application #GC #minor_gc #major_gc #eden
First, we create a .java file, then compile it with javac MyApp.java, which produces a .class file containing bytecode.
After that, the ClassLoader starts working. It follows the Parent Delegation Model — so the request goes upward first, not downward. The Application ClassLoader (where our class data is located) delegates to Extension (extra libraries for helping Java), which delegates to Bootstrap (Java core libraries like java.lang, java.util). Bootstrap tries to load the class first. If it can't find it, the request falls back to Extension, and finally to Application. This prevents malicious code from replacing core Java classes.
After finishing this process, data gets stored in 5 types of memory according to their purpose: Heap (for objects, reference types), Stack (references, primitive types, method frames), Method Area (class metadata, static fields, method bytecode), Native Method Stack (for non-Java code called through JNI), and PC Register (holds the address of the current bytecode instruction for each thread — needed for thread switching).
After all that, the Execution Engine starts working. It has 2 modes working simultaneously. The Interpreter reads bytecode instruction by instruction. While it runs, the JVM counts how many times each method is called. After a method is called around 10,000 times, it becomes "hot code" and the JIT Compiler converts it into native machine code (0s and 1s). From that point, that method runs directly as compiled code, skipping the Interpreter. This is why Java gets faster over time.
Here also GC works, which manages Heap memory by removing unreachable objects. It has Young Generation — new objects come to Eden, and after surviving Minor GC cycles they get copied between Survivor1 and Survivor2 (one is always empty). After around 15 cycles they move to Old Generation, where objects are less likely to be removed and can be cleaned by Major GC or Full GC.
#JVM #heap #stack #meta_space #native #pc_register #class_loader #boostrap #extension #application #GC #minor_gc #major_gc #eden
Why the program slow down over time?
There might be several reasons why a Java application slows down over time.
#memory_leak #gc_pressure #thread_contention #JIT_Decomposition #Resource_Exhaustion
There might be several reasons why a Java application slows down over time.
1. Memory Leak — objects that are no longer needed still have references pointing to them, so GC can't remove them. For example, adding objects to a static List and never clearing it. The Heap gradually fills up, GC runs more and more frequently, recovers almost nothing, and the application slows down. In the worst case, if GC spends more than 98% of CPU time and recovers less than 2% of the Heap, the JVM throws OutOfMemoryError: GC overhead limit exceeded.
2. GC Pressure — even without a leak, if your application creates too many short-lived objects too fast, Minor GC runs constantly. And if many objects survive long enough to reach Old Generation, Full GC kicks in — which is a stop-the-world pause that freezes the entire application.
3. Thread Contention — when multiple threads compete for the same lock or synchronized block, they wait instead of working. The application has available CPU cores but threads are stuck in a queue, so throughput drops.
4. JIT Decoptimisation — the JIT Compiler makes assumptions when optimising hot code. If those assumptions become invalid later (for example, a new subclass appears), the JIT throws away the compiled code and falls back to the Interpreter. This causes temporary performance drops.
5. Resource Exhaustion — unclosed connections, streams, or file handles accumulate over time. The application starts waiting for available resources instead of processing requests.
#memory_leak #gc_pressure #thread_contention #JIT_Decomposition #Resource_Exhaustion
GC Pauses Increased After a Small Code Change
A small code change caused GC pauses to spike. How?
Most developers add a log line, forget to close a connection, or switch from primitive to wrapper type. Looks innocent. But the chain reaction is brutal.
Too many objects created → Eden fills fast → Minor GC runs constantly → objects survive and get promoted to Old Generation → Old Gen fills up → Major GC kicks in → long Stop The World pause.
Minor GC pauses are short — milliseconds. Major GC pauses are long — hundreds of milliseconds or even seconds. That's the difference between a smooth app and one that freezes randomly.
The culprit is almost always objects living longer than they should — unclosed connections, unbounded caches, forgotten listeners.
#Java #JVM #GC #Performance
A small code change caused GC pauses to spike. How?
Most developers add a log line, forget to close a connection, or switch from primitive to wrapper type. Looks innocent. But the chain reaction is brutal.
Too many objects created → Eden fills fast → Minor GC runs constantly → objects survive and get promoted to Old Generation → Old Gen fills up → Major GC kicks in → long Stop The World pause.
Minor GC pauses are short — milliseconds. Major GC pauses are long — hundreds of milliseconds or even seconds. That's the difference between a smooth app and one that freezes randomly.
The culprit is almost always objects living longer than they should — unclosed connections, unbounded caches, forgotten listeners.
#Java #JVM #GC #Performance
Increasing Heap Size Made Performance Worse
Counterintuitive but real. Here's why.
Bigger heap means more objects can live in memory. GC runs less frequently — sounds good. But when Major GC finally kicks in it has to scan the entire heap.
512MB heap → Major GC → 200ms pause
16GB heap → Major GC → 10 second pause 💀
App doesn't crash. It just freezes for 10 seconds randomly. Users think it's dead.
More heap = less frequent but more painful GC pauses.
The real fix is not throwing more memory at the problem. Fix memory leaks first. Then if you still need large heap — switch to ZGC or Shenandoah which keep pauses under 1ms regardless of heap size.
#Java #JVM #GC #Performance #Heap
Counterintuitive but real. Here's why.
Bigger heap means more objects can live in memory. GC runs less frequently — sounds good. But when Major GC finally kicks in it has to scan the entire heap.
512MB heap → Major GC → 200ms pause
16GB heap → Major GC → 10 second pause 💀
App doesn't crash. It just freezes for 10 seconds randomly. Users think it's dead.
More heap = less frequent but more painful GC pauses.
The real fix is not throwing more memory at the problem. Fix memory leaks first. Then if you still need large heap — switch to ZGC or Shenandoah which keep pauses under 1ms regardless of heap size.
#Java #JVM #GC #Performance #Heap