Why new Thread() Doesn't Create a Thread: Tracing Java Thread Birth from JVM to OS
This article traces the complete lifecycle of a Java thread from new Thread() through start() to sleep and interrupt, revealing how the JVM creates OS threads via C++ JavaThread and OSThread objects, and demonstrating the 1:1 thread model with executable verification code.
new Thread()only creates a Java object — no real thread exists yet. The Thread constructor initializes fields like threadStatus = 0 (NEW), interrupted = false, and stores the target Runnable. No native calls, no C++ objects, no OS thread allocation occur. isAlive() returns false and getState() returns NEW.
Thread t = new Thread(() -> { }, "my-worker");
System.out.println("state=" + t.getState());
System.out.println("isAlive=" + t.isAlive());Output:
state=NEW
isAlive=falsestart() Creates the Real Thread
Calling start() checks threadStatus == 0 (throws IllegalThreadStateException if not), then invokes the native start0() → JVM_StartThread(). This JVM function does three things:
Creates a JavaThread C++ object — JVM's internal representation of a Java thread. Its constructor calls os::create_thread().
Creates the OS thread — On Windows, HotSpot calls _beginthreadex() with CREATE_SUSPENDED flag, so the thread starts suspended. An OSThread C++ object is also created to hold the OS thread handle and ID.
Links Java and JVM objects — prepare() cross-references the Java Thread object and the C++ JavaThread object.
Finally, Thread::start() sets the Java thread state to RUNNABLE and calls os::start_thread() → ResumeThread() to wake the OS thread. The OS thread then invokes the Java run() method.
Thread.start()
→ start0() (native)
→ JVM_StartThread()
→ new JavaThread() // JVM C++ object
→ os::create_thread() // Create OS thread (suspended)
→ _beginthreadex() // Windows API
→ prepare() // Link Java and JVM objects
→ Thread::start()
→ os::start_thread() // Resume OS thread
→ ResumeThread() // Windows APIFour-Layer Thread Structure
A running Java thread consists of four one-to-one mapped layers:
Java layer : java.lang.Thread — The Thread object you manipulate in code
JVM layer : JavaThread (C++) — JVM-internal thread management object
JVM layer : OSThread (C++) — Holds OS thread handle and ID
OS layer : OS Thread — OS-scheduled execution unit
Java-layer method calls traverse down through JavaThread → OSThread → OS thread. OS thread state changes propagate back up to Thread.getState().
What sleep() Does Across Layers
Thread.sleep()is native → JVM_Sleep:
Checks interrupt flag; if set, throws InterruptedException immediately.
Sets OSThread state to SLEEPING; reflects as TIMED_WAITING in getState().
Calls ParkEvent.park(millis) — JVM's internal synchronization primitive. On Windows, this eventually calls WaitForSingleObject(), putting the OS thread in a wait state (no CPU time slices).
When time expires or interrupt occurs, park returns, OSThread state restores, thread becomes runnable again.
How interrupt() Wakes a Sleeping Thread
thread.interrupt()sets the Java interrupted field to true, then calls native interrupt0() → JVM_Interrupt → JavaThread::interrupt(), which does four things:
void JavaThread::interrupt() {
// Set OS-level interrupt flag
osthread()->set_interrupted(true);
// Wake thread sleeping via Thread.sleep()
_SleepEvent->unpark();
// Wake thread blocked in LockSupport.park()
parker()->unpark();
// Wake thread blocked in ObjectMonitor.wait()
_ParkEvent->unpark();
}The key is _SleepEvent->unpark(). The sleeping thread waits on a ParkEvent; unpark wakes it. The thread returns from park, checks is_interrupted(true) (which clears the flag), sees true, returns false to JVM_Sleep, which throws InterruptedException. That's why isInterrupted() returns false after catching the exception — the flag was cleared during the check.
Verification Code and Output
File ThreadLifecycle.java, run with java ThreadLifecycle.java:
public class ThreadLifecycle {
public static void main(String[] args) throws Exception {
// step 1: new Thread() - only a Java object, no real thread yet
Thread t = new Thread(() -> {
try {
System.out.println(" [worker] started, state=" + Thread.currentThread().getState());
System.out.println(" [worker] thread id=" + Thread.currentThread().getId());
System.out.println(" [worker] sleeping 5 seconds...");
Thread.sleep(5000);
System.out.println(" [worker] woke up normally");
} catch (InterruptedException e) {
System.out.println(" [worker] interrupted during sleep!");
System.out.println(" [worker] isInterrupted=" + Thread.currentThread().isInterrupted());
}
}, "my-worker");
System.out.println("[main] after new Thread():");
System.out.println(" state=" + t.getState());
System.out.println(" isAlive=" + t.isAlive());
// step 2: start() - now JVM creates the real thread
System.out.println("[main] calling start()...");
t.start();
Thread.sleep(200);
System.out.println("[main] after start():");
System.out.println(" state=" + t.getState());
System.out.println(" isAlive=" + t.isAlive());
// step 3: wait a moment, worker is sleeping
Thread.sleep(500);
System.out.println("[main] worker is now sleeping, state=" + t.getState());
// step 4: interrupt the sleeping worker
System.out.println("[main] calling interrupt()...");
t.interrupt();
t.join();
System.out.println("[main] worker finished, state=" + t.getState());
}
}Consistent output across runs:
run 1:
[main] after new Thread():
state=NEW
isAlive=false
[main] calling start()...
[worker] started, state=RUNNABLE
[worker] thread id=23
[worker] sleeping 5 seconds...
[main] after start():
state=TIMED_WAITING
isAlive=true
[main] worker is now sleeping, state=TIMED_WAITING
[main] calling interrupt()...
[worker] interrupted during sleep!
[worker] isInterrupted=false
[main] worker finished, state=TERMINATEDKey observations from output:
After new Thread(): state NEW, isAlive=false — no real thread.
After start(): worker runs, state RUNNABLE; after sleep, state TIMED_WAITING — Java thread state maps to JVM/OS layers. interrupt() wakes worker immediately (no 5-second wait), catches InterruptedException, isInterrupted() returns false because flag cleared on check.
Final state: TERMINATED.
Summary
JDK 17 uses a 1:1 thread model: one Java Thread ↔ one JVM JavaThread ↔ one OS native thread. new Thread() only creates the mapping's starting point; the real thread exists only after start().
Note: JDK 21 introduces virtual threads with an M:N model (many virtual threads multiplexed onto few OS threads), fundamentally different from the 1:1 model described here.
Reference: Java 17 OpenJDK
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.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
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.
