<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Jvm on Vitalii Sukach</title><link>https://sukachvitally.github.io/tags/jvm/</link><description>Recent content in Jvm on Vitalii Sukach</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Thu, 03 Sep 2026 16:18:18 +0000</lastBuildDate><atom:link href="https://sukachvitally.github.io/tags/jvm/index.xml" rel="self" type="application/rss+xml"/><item><title>The Java Memory Model, Explained Without the Headache</title><link>https://sukachvitally.github.io/posts/the-java-memory-model-explained-without-the-headache/</link><pubDate>Thu, 03 Sep 2026 16:18:18 +0000</pubDate><guid>https://sukachvitally.github.io/posts/the-java-memory-model-explained-without-the-headache/</guid><description>&lt;h4 id="what-actually-happens-to-your-data-between-two-threadsand-the-small-set-of-tools-that-let-you-controlit">What actually happens to your data between two threads — and the small set of tools that let you control it&lt;/h4>
&lt;p>&lt;img loading="lazy" src="https://sukachvitally.github.io/posts/the-java-memory-model-explained-without-the-headache/image-01.png">&lt;/p>
&lt;p>Ask ten Java developers what “the Java Memory Model” means and you’ll get two different answers.&lt;/p>
&lt;p>Half of them will draw a box called &lt;em>Heap&lt;/em> and another called &lt;em>Stack&lt;/em>. The other half will start talking about volatile and happens-before.&lt;/p>
&lt;p>Both groups are talking about something real, but only the second group is talking about the actual &lt;strong>Java Memory Model (JMM)&lt;/strong>. The first group is describing the &lt;strong>runtime data areas&lt;/strong> — where objects physically live.&lt;/p></description><content:encoded><![CDATA[<h4 id="what-actually-happens-to-your-data-between-two-threadsand-the-small-set-of-tools-that-let-you-controlit">What actually happens to your data between two threads — and the small set of tools that let you control it</h4>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/the-java-memory-model-explained-without-the-headache/image-01.png"></p>
<p>Ask ten Java developers what “the Java Memory Model” means and you’ll get two different answers.</p>
<p>Half of them will draw a box called <em>Heap</em> and another called <em>Stack</em>. The other half will start talking about volatile and happens-before.</p>
<p>Both groups are talking about something real, but only the second group is talking about the actual <strong>Java Memory Model (JMM)</strong>. The first group is describing the <strong>runtime data areas</strong> — where objects physically live.</p>
<p>You need both to write correct concurrent code, so let’s go through them in order. Memory layout first, because it’s the easier one, then the JMM proper.</p>
<h3 id="part-1-where-your-datalives">Part 1: Where your data lives</h3>
<p>When the JVM starts, it carves the process memory into a few distinct regions. Each has an owner and a lifetime.</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/the-java-memory-model-explained-without-the-headache/image-02.png"></p>
<p><strong>Heap</strong> — every object and array you ever create lives here. It’s shared, which is exactly why concurrency is hard. Garbage collection happens here. Modern collectors split it further (young generation, old generation, or regions in G1/ZGC), but that’s a GC detail, not a memory model one.</p>
<p><strong>Metaspace</strong> — class metadata, bytecode, and static fields. It sits in native memory since Java 8, which is why PermGen errors disappeared and Metaspace OOM errors appeared instead.</p>
<p><strong>Thread stack</strong> — each thread gets its own. Every method call pushes a frame containing local variables, parameters, and the return address. Frame pops when the method returns. This is where StackOverflowError comes from. Nothing here is shared, so locals are never a concurrency problem — unless the <em>value</em> they point to is a shared object.</p>
<p><strong>PC register</strong> — the address of the instruction the thread is currently executing. One per thread.</p>
<p><strong>Native method stack</strong> — the same idea, but for native (JNI) calls.</p>
<p>One thing worth burning in:</p>
<pre tabindex="0"><code>Order order = new Order();
</code></pre><p>The variable order is a reference sitting on the <strong>stack</strong>. The Order object itself sits on the <strong>heap</strong>. Two threads can hold two separate stack references pointing to the same heap object. That&rsquo;s the whole source of the trouble.</p>
<h3 id="part-2-the-actual-java-memorymodel">Part 2: The actual Java Memory Model</h3>
<p>The runtime areas tell you <em>where</em> data sits. They tell you nothing about <em>when</em> one thread sees a write made by another.</p>
<p>That question is what the JMM answers. It’s a specification (JSR-133, in Chapter 17 of the Java Language Specification) that defines exactly what guarantees you get about reads and writes across threads.</p>
<h4 id="the-problem-it-exists-tosolve">The problem it exists to solve</h4>
<p>Modern CPUs are fast because they cheat, aggressively and correctly. Two things they do:</p>
<p><strong>1. Caching.</strong> Each core has its own L1/L2 cache. When a thread writes to a field, that write may sit in the core’s cache or store buffer for a while before it becomes visible to other cores.</p>
<p><strong>2. Reordering.</strong> The compiler, the JIT, and the CPU are all allowed to reorder instructions, as long as the result looks the same <em>for a single thread</em>. That last clause is the trap — nobody promised anything about how it looks to other threads.</p>
<p>So the JMM works with an abstraction:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/the-java-memory-model-explained-without-the-headache/image-03.png"></p>
<p><em>Thread A and Thread B never see each other’s working memory. Everything they share has to travel through main memory — and the JMM decides when it does.</em></p>
<p>Every thread has a conceptual <strong>working memory</strong> (registers, cache, store buffers) holding copies of the variables it touches. Threads never talk to each other directly — they communicate only through <strong>main memory</strong>. The JMM specifies when a value must be flushed to main memory and when it must be re-read from it.</p>
<p>Here’s the classic demonstration:</p>
<pre tabindex="0"><code>public class StopFlag {
    private boolean running = true;   // no volatile
    
    public void run() {
        while (running) {
            // do work
        }
        System.out.println(&#34;stopped&#34;);
    }
    public void stop() {
        running = false;
    }
}
</code></pre><p>Thread A runs run(). Thread B calls stop(). Very often, thread A never stops.</p>
<p>Nothing is broken. The JIT sees that running is never modified inside the loop, so it&rsquo;s free to hoist the read out:</p>
<pre tabindex="0"><code>boolean r = running;
while (r) { /* work */ }   // now an infinite loop
</code></pre><p>Without a memory model rule forcing it, the JVM has no obligation to ever re-read that field. Your bug is not a race in the “two writes collided” sense — it’s a <strong>visibility</strong> failure.</p>
<h4 id="the-three-guarantees">The three guarantees</h4>
<p>Every concurrency problem in Java falls into one of these buckets.</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/the-java-memory-model-explained-without-the-headache/image-04.png"></p>
<p>count++ is the textbook atomicity failure — it&rsquo;s three operations (read, add, write), and two threads can interleave in the middle. Two increments, one result.</p>
<h3 id="the-base-components-of-thejmm">The base components of the JMM</h3>
<p>Here’s the toolbox. It’s smaller than most people expect.</p>
<h4 id="1-happens-beforethe-rule-that-underpins-everything">1. happens-before — the rule that underpins everything</h4>
<p>Everything else is a special case of this one relation. If action <strong>A</strong> <em>happens-before</em> action <strong>B</strong>, then:</p>
<ul>
<li>everything A wrote is visible to B, and</li>
<li>A is guaranteed to appear to occur before B.</li>
</ul>
<p>It is <strong>not</strong> just “A ran earlier in wall-clock time.” Two actions can be ordered in time and still have no happens-before relationship, which means B may see stale data.</p>
<p>The rules the JLS gives you:</p>
<ol>
<li><strong>Program order</strong> — within one thread, each statement happens-before the ones written after it.</li>
<li><strong>Monitor lock</strong> — unlocking a monitor happens-before any later lock of the <em>same</em> monitor.</li>
<li><strong>volatile</strong> — a write to a volatile field happens-before every later read of that field.</li>
<li><strong>Thread start</strong> — Thread.start() happens-before anything in the started thread.</li>
<li><strong>Thread join</strong> — everything in a thread happens-before another thread’s successful join() on it.</li>
<li><strong>Interruption</strong> — a call to interrupt() happens-before the interrupted thread detects it.</li>
<li><strong>Constructor / finalizer</strong> — the end of a constructor happens-before the start of that object’s finalizer.</li>
<li><strong>Transitivity</strong> — if A hb B and B hb C, then A hb C.</li>
</ol>
<p>That last one does most of the practical work. It’s why writing a plain field and <em>then</em> a volatile flag safely publishes the plain field.</p>
<h4 id="2-volatilevisibility-and-ordering-no-atomicity">2. volatile — visibility and ordering, no atomicity</h4>
<pre tabindex="0"><code>private volatile boolean running = true;
</code></pre><p>This single keyword fixes the StopFlag example. It gives you:</p>
<ul>
<li><strong>Visibility</strong> — a write goes to main memory immediately; a read comes from main memory, never a cache.</li>
<li><strong>Ordering</strong> — the JVM inserts memory barriers so reads and writes can’t be reordered across the volatile access.</li>
</ul>
<p>What it does <strong>not</strong> give you is atomicity:</p>
<pre tabindex="0"><code>private volatile int count = 0;
public void increment() {
    count++;   // STILL BROKEN
}
</code></pre><p>Use volatile for flags and for single-writer state. For compound operations, use something else.</p>
<h4 id="3-synchronizedall-three-guarantees">3. synchronized — all three guarantees</h4>
<pre tabindex="0"><code>private int count = 0;
public synchronized void increment() {
    count++;   // correct
}
public synchronized int get() {
    return count;   // also needs to be synchronized
}
</code></pre><p>Entering the monitor gives mutual exclusion (atomicity). Exiting flushes to main memory, entering invalidates the local copy (visibility). Barriers around the block prevent reordering out of it.</p>
<p>The most common mistake here is synchronizing the writer but not the reader. Both sides need the <em>same</em> lock, or there’s no happens-before edge between them.</p>
<p>ReentrantLock gives you the same memory guarantees, plus tryLock, timeouts, fairness, and multiple conditions.</p>
<h4 id="4-finalsafe-publication-forfree"><strong>4.</strong> <strong>final — safe publication for free</strong></h4>
<pre tabindex="0"><code>public class Config {
    private final String url;
    public Config(String url) {
        this.url = url;   // guaranteed visible after construction
    }
}
</code></pre><p>The JMM guarantees that any thread that sees a properly constructed object sees the correct values of its final fields — no synchronization required. This is why immutable objects are the easiest possible concurrency strategy.</p>
<p>The catch is “properly constructed.” If the constructor leaks this (registering a listener, starting a thread), the guarantee is void.</p>
<h4 id="5-atomics-and-varhandle">5. Atomics and VarHandle</h4>
<p>java.util.concurrent.atomic gives you lock-free compound operations built on CPU compare-and-swap instructions:</p>
<pre tabindex="0"><code>private final AtomicInteger count = new AtomicInteger();
public void increment() {
    count.incrementAndGet();   // atomic, visible, ordered
}
</code></pre><p>Under contention LongAdder usually beats AtomicLong — it spreads writes across cells and sums them on read, avoiding cache-line contention. VarHandle (Java 9+) is the modern, supported way to reach for finer-grained modes like getAcquire / setRelease when you know exactly what you&rsquo;re doing.</p>
<h3 id="three-bugs-thisexplains">Three bugs this explains</h3>
<p><strong>Unsafe publication.</strong> Building an object and handing the reference to another thread without a happens-before edge. The other thread can see a non-null reference to a half-initialized object — fields still at their defaults. Publish through a volatile field, a lock, a final field, or a concurrent collection.</p>
<p><strong>Double-checked locking without</strong> <strong>volatile.</strong> The infamous one:</p>
<pre tabindex="0"><code>private static volatile Singleton instance;   // volatile is mandatory
public static Singleton getInstance() {
    if (instance == null) {
        synchronized (Singleton.class) {
            if (instance == null) {
                instance = new Singleton();
            }
        }
    }
    return instance;
}
</code></pre><p>Drop the volatile and it breaks, because instance = new Singleton() is three steps — allocate, construct, assign — and the last two can be reordered. Another thread can grab a reference to an object whose constructor hasn&rsquo;t finished.</p>
<p><strong>Check-then-act.</strong> if (!map.containsKey(k)) map.put(k, v); is two atomic operations, not one atomic operation. Use putIfAbsent or computeIfAbsent.</p>
<h3 id="the-practical-cheatsheet">The practical cheat sheet</h3>
<ul>
<li><strong>Shared and mutable?</strong> It needs synchronization. Every time.</li>
<li><strong>One thread writes a flag, others read it?</strong> volatile.</li>
<li><strong>Compound operation on one variable?</strong> An atomic class.</li>
<li><strong>Compound operation across several variables?</strong> synchronized or ReentrantLock.</li>
<li><strong>Can you make it immutable instead?</strong> Do that. final fields, no setters, no shared mutation, no memory model to reason about.</li>
<li><strong>Reading is also access.</strong> Unsynchronized reads of synchronized state are still broken.</li>
<li><strong>“It works on my machine” proves nothing.</strong> x86 has a strong memory model that hides many of these bugs. ARM (Apple Silicon, Graviton) doesn’t. Code that ran fine for years on Intel servers can fail the week you move it to ARM instances.</li>
</ul>
<h3 id="wrapping-up">Wrapping up</h3>
<p>The runtime areas — heap, stacks, metaspace — tell you where your data sits. The Java Memory Model tells you when one thread’s writes become visible to another, and it comes down to a handful of pieces: happens-before as the underlying rule, and volatile, synchronized, final, and the atomics as the tools that create it.</p>
<p>You don’t need to memorize the specification. You need one habit: whenever two threads touch the same data, ask what creates the happens-before edge between them. If you can’t name it, it isn’t there.</p>
<p><em>If this was useful, the natural next step is Brian Goetz’s</em> Java Concurrency in Practice <em>— still the best treatment of this material, twenty years on.</em></p>
]]></content:encoded></item><item><title>From Source to Running JVM: How Gradle Builds Your Java Application</title><link>https://sukachvitally.github.io/posts/from-source-to-running-jvm-how-gradle-builds-your-java-application/</link><pubDate>Sun, 19 Jul 2026 11:14:16 +0000</pubDate><guid>https://sukachvitally.github.io/posts/from-source-to-running-jvm-how-gradle-builds-your-java-application/</guid><description>&lt;p>Build phases, task graphs, dependency resolution, and what actually happens when the JVM opens your JAR&lt;/p>
&lt;p>You type gradle build, wait a few seconds, and out comes a JAR. Most Java developers use this pipeline every day without ever looking inside it. But when a build gets slow, a dependency conflict appears, or a NoClassDefFoundError shows up in production at 2 AM, understanding the machinery underneath is the difference between guessing and fixing.&lt;/p></description><content:encoded><![CDATA[<p>Build phases, task graphs, dependency resolution, and what actually happens when the JVM opens your JAR</p>
<p>You type gradle build, wait a few seconds, and out comes a JAR. Most Java developers use this pipeline every day without ever looking inside it. But when a build gets slow, a dependency conflict appears, or a NoClassDefFoundError shows up in production at 2 AM, understanding the machinery underneath is the difference between guessing and fixing.</p>
<p>This article walks the full journey: Gradle’s three build phases, the Java plugin’s task graph, how dependency resolution actually works stage by stage, what a JAR file really is, and how the JVM reads and executes it.</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/from-source-to-running-jvm-how-gradle-builds-your-java-application/image-01.png"></p>
<h3 id="part-1-the-three-phases-every-gradle-build-goesthrough">Part 1: The Three Phases Every Gradle Build Goes Through</h3>
<p>Before a single line of your code is compiled, Gradle itself runs through a fixed lifecycle of three phases. This is true for every build, every plugin, every project.</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/from-source-to-running-jvm-how-gradle-builds-your-java-application/image-02.png"></p>
<h4 id="phase-1initialization">Phase 1 — Initialization</h4>
<p>Gradle reads settings.gradle(.kts) and answers one question: <em>which projects are part of this build?</em> For a single-module app that&rsquo;s trivial; for a multi-module monorepo this is where include(&quot;:core&quot;, &ldquo;:api&rdquo;, &ldquo;:app&rdquo;) gets evaluated and a Project object is created for each module.</p>
<h4 id="phase-2configuration">Phase 2 — Configuration</h4>
<p>Now every build.gradle(.kts) script executes top to bottom. This is the phase people misunderstand most. Your build script is <em>code that runs</em> — and it runs here, on every invocation, regardless of which task you asked for. The output of this phase is the <strong>task graph</strong>: a directed acyclic graph (DAG) of all tasks and their dependencies. Tasks are registered and wired together, but none of their actions execute yet.</p>
<blockquote>
<p><em>A</em> <em>println at the top level of build.gradle prints during Configuration — even if you run</em> <em>gradle help. Task actions run later. This trips up nearly everyone once.</em></p></blockquote>
<h4 id="phase-3execution">Phase 3 — Execution</h4>
<p>Gradle takes the task you requested, computes the minimal subgraph of tasks needed to produce it, and executes them in dependency order. Tasks whose inputs and outputs haven’t changed since the last run are marked UP-TO-DATE and skipped entirely — this is Gradle&rsquo;s incremental build engine, and it&rsquo;s why the second build is always faster than the first.</p>
<h3 id="part-2-the-java-plugin-taskgraph">Part 2: The Java Plugin Task Graph</h3>
<p>Applying the java plugin registers a standard chain of tasks. When you run gradle build, this is the graph that gets executed:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/from-source-to-running-jvm-how-gradle-builds-your-java-application/image-03.png"></p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/from-source-to-running-jvm-how-gradle-builds-your-java-application/image-04.png"></p>
<p>The practical shortcuts worth memorizing:</p>
<pre tabindex="0"><code># fastest feedback: compile only
gradle compileJava
# package without running tests
gradle jar
# full build, skip test execution
gradle build -x test
# nuke build/ and start clean
gradle clean build
</code></pre><h3 id="part-3-how-gradle-dependency-resolution-actuallyworks">Part 3: How Gradle Dependency Resolution Actually Works</h3>
<p>This is the part of Gradle most developers treat as magic. You write three lines in a dependencies block, and somehow the right JARs appear on your classpath. Here&rsquo;s the machinery, stage by stage.</p>
<h4 id="configurations-buckets-of-dependencies">Configurations: buckets of dependencies</h4>
<p>Every dependency you declare goes into a named <strong>configuration</strong> — a bucket with specific rules about where those dependencies are visible:</p>
<pre tabindex="0"><code>dependencies {
    implementation &#39;org.springframework.boot:spring-boot-starter-web:3.3.1&#39;
    api &#39;com.fasterxml.jackson.core:jackson-databind:2.17.1&#39;
    compileOnly &#39;org.projectlombok:lombok:1.18.32&#39;
    annotationProcessor &#39;org.projectlombok:lombok:1.18.32&#39;
    runtimeOnly &#39;org.postgresql:postgresql:42.7.3&#39;
    testImplementation &#39;org.junit.jupiter:junit-jupiter:5.10.2&#39;
}
</code></pre><p><img loading="lazy" src="https://sukachvitally.github.io/posts/from-source-to-running-jvm-how-gradle-builds-your-java-application/image-05.png"></p>
<p>The implementation vs api distinction matters in multi-module builds: implementation dependencies don&rsquo;t appear on the compile classpath of modules that depend on yours. That means fewer recompilations when they change, and a cleaner API boundary. Default to implementation; reach for api only when a type from that dependency appears in your public method signatures.</p>
<h4 id="the-resolution-stages">The resolution stages</h4>
<p>Declared dependencies are just coordinates on paper. Resolution is the process of turning them into actual files, and it happens <strong>lazily</strong> — only when a task first needs a classpath (typically compileJava during Execution). The stages:</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/from-source-to-running-jvm-how-gradle-builds-your-java-application/image-06.png"></p>
<p><strong>Stage 1 — Graph construction.</strong> For each declared coordinate (group:name:version), Gradle queries your repositories <em>in the order they&rsquo;re declared</em> and downloads only the <em>metadata</em> first: the POM file or Gradle Module Metadata. That metadata lists the dependency&rsquo;s own dependencies, so Gradle recursively walks outward until the full transitive graph is known. No JARs have been downloaded yet — just descriptors.</p>
<p><strong>Stage 2 — Conflict resolution.</strong> Real dependency graphs always contain the same library requested at different versions (your app wants Jackson 2.17, some transitive path wants 2.15). Gradle’s default strategy is <em>highest version wins</em> — unlike Maven, which picks the “nearest” declaration in the tree. This default is usually right, but you can override it:</p>
<pre tabindex="0"><code>configurations.all {
    resolutionStrategy {
        // hard-pin a version, overriding everything
        force &#39;com.fasterxml.jackson.core:jackson-databind:2.17.1&#39;
        // or turn conflicts into build failures
        failOnVersionConflict()
    }
}
</code></pre><p><strong>Stage 3 — Constraints and rules.</strong> Before the graph is finalized, Gradle applies platforms (BOMs), version constraints, exclusions, and dependency substitutions. This is how Spring Boot keeps hundreds of libraries version-aligned with one line:</p>
<pre tabindex="0"><code>dependencies {
    implementation platform(&#39;org.springframework.boot:spring-boot-dependencies:3.3.1&#39;)
    // now version numbers can be omitted — the BOM supplies them
    implementation &#39;org.springframework.boot:spring-boot-starter-web&#39;
}
</code></pre><p>Exclusions cut unwanted transitive branches at the declaration site:</p>
<pre tabindex="0"><code>implementation(&#39;org.springframework.boot:spring-boot-starter-web&#39;) {
    exclude group: &#39;org.springframework.boot&#39;, module: &#39;spring-boot-starter-tomcat&#39;
}
</code></pre><p><strong>Stage 4 — Artifact download.</strong> Only now, with the final graph settled, does Gradle download the actual JAR files — and only for modules that survived resolution. Everything lands in the shared cache at ~/.gradle/caches/modules-2, keyed by checksum, shared across all projects on your machine. A dependency is downloaded once, ever, per machine (dynamic versions like 1.+ and snapshots get re-checked periodically).</p>
<p><strong>Stage 5 — Classpath assembly.</strong> The resolved files are ordered into a classpath and handed to whichever task requested them: compileClasspath to javac, runtimeClasspath to the test JVM, and so on.</p>
<h4 id="debugging-resolution">Debugging resolution</h4>
<p>Two commands answer 90% of “why is this version on my classpath” questions:</p>
<pre tabindex="0"><code># full resolved tree per configuration
gradle dependencies --configuration runtimeClasspath
# trace one library: who pulls it in, which versions were requested,
# and why the winner won
gradle dependencyInsight --dependency jackson-databind
</code></pre><h3 id="part-4-what-a-jar-file-reallyis">Part 4: What a JAR File Really Is</h3>
<p>The output of the jar task is nothing exotic — it&rsquo;s a ZIP archive with an agreed-upon internal layout. Rename it to .zip and any archive tool will open it:</p>
<pre tabindex="0"><code>myapp.jar
├── META-INF/
│   └── MANIFEST.MF          ← metadata
├── com/example/App.class     ← bytecode, path = package name
├── com/example/util/Helper.class
└── application.properties    ← resources sit alongside classes
</code></pre><p>The manifest is a plain-text key-value file. The line that matters most for a runnable JAR:</p>
<pre tabindex="0"><code>Manifest-Version: 1.0
Main-Class: com.example.App
</code></pre><p>One important caveat: the plain jar task packages <em>only your classes</em>. Your dependencies are not inside. For a self-contained deployable artifact you need a fat JAR — with the Shadow plugin, or Spring Boot&rsquo;s bootJar, which nests dependency JARs and adds its own launcher.</p>
<pre tabindex="0"><code>plugins {
    id &#39;com.github.johnrengelman.shadow&#39; version &#39;8.1.1&#39;
}
// gradle shadowJar → one JAR containing everything
</code></pre><h3 id="part-5-how-the-jvm-reads-yourjar">Part 5: How the JVM Reads Your JAR</h3>
<p>You run java -jar myapp.jar. Here&rsquo;s the sequence, and it&rsquo;s more interesting than it looks.</p>
<h4 id="step-1the-launcher-reads-themanifest">Step 1 — The launcher reads the manifest</h4>
<p>The java launcher opens the JAR as a ZIP, reads META-INF/MANIFEST.MF, and extracts Main-Class. Note a classic gotcha: with -jar, any -cp argument on the command line is <strong>silently ignored</strong>. The JAR itself (plus manifest Class-Path entries) becomes the entire application classpath.</p>
<h4 id="step-2the-classloader-hierarchy">Step 2 — The classloader hierarchy</h4>
<p>Classes are loaded by a chain of classloaders using <strong>parent-first delegation</strong>: every load request travels up to the top of the chain first, and only comes back down if no ancestor can satisfy it.</p>
<p><img loading="lazy" src="https://sukachvitally.github.io/posts/from-source-to-running-jvm-how-gradle-builds-your-java-application/image-07.png"></p>
<h4 id="step-3lazyloading">Step 3 — Lazy loading</h4>
<p>The JVM does <strong>not</strong> load every class in the JAR at startup. A class is loaded the first time it’s actively used — instantiated, statically referenced, reflected upon. This is why a missing transitive dependency can let your app boot cleanly and then blow up with NoClassDefFoundError twenty minutes later, the first time a rarely-used code path runs.</p>
<h4 id="step-4load-link-initialize">Step 4 — Load, Link, Initialize</h4>
<p>Per the JVM specification, every class goes through three phases before it’s usable:</p>
<ol>
<li><strong>Loading</strong> — the class bytes are read from the JAR, parsed, and a Class object is created in metaspace.</li>
<li><strong>Linking</strong> — three sub-steps:
<ul>
<li>Verification: the bytecode is checked for structural and type safety (stack shapes, valid jumps, access rules).</li>
<li>Preparation: static fields are allocated and set to defaults — 0, null, false. Not your values yet.</li>
<li>Resolution: symbolic references to other classes, methods, and fields are resolved to concrete ones (often lazily).</li>
</ul>
</li>
<li><strong>Initialization</strong> — static initializer blocks and static field assignments run, in source order, exactly once, on first active use. This is the point where static { &hellip; } blocks fire.</li>
</ol>
<h3 id="step-5main-and-thejit">Step 5 — main() and the JIT</h3>
<p>Once Main-Class is loaded, linked, and initialized, the JVM invokes main(String[] args) on the main thread. Execution starts in the interpreter. As methods get hot — called thousands of times — the JIT compiler (tiered: C1 first for fast, lightly-optimized code, then C2 for aggressively optimized code) compiles them to native machine code. That&rsquo;s why JVM applications famously get <em>faster</em> after warm-up.</p>
<h3 id="the-wholepicture">The Whole Picture</h3>
<pre tabindex="0"><code>src/main/java  ──javac──▶  build/classes  ──jar──▶  myapp.jar
                                                        │
                                             java -jar myapp.jar
                                                        │
                       read MANIFEST.MF → find Main-Class
                                                        │
                 classloader delegation (parent-first)
                                                        │
                 Load → Link (verify/prepare/resolve) → Init
                                                        │
                            main() runs, classes load lazily,
                            JIT compiles hot paths as you go
</code></pre><p>None of this is knowledge you need daily. But the day your build takes four minutes instead of forty seconds, or two versions of the same library fight on your classpath, or a class mysteriously fails to load in production — this is the map you’ll be reading. Better to have seen it before that day arrives.</p>
]]></content:encoded></item></channel></rss>