I think I have and understanding of a mapping between OS threads and JVM threads (first we had "green threads" with 1os:Many-green, then there was 1:1 mapping and now with jep-425 we have virtual threads with M:N mapping: please correct me if I'm wrong). I'm however quite confused about the mapping between JVM threads and Thread
instances: I'm almost sure that there is a 1:1 mapping between start()
-ed Thread
instances and JVM threads (again, please correct me if I'm wrong), but I'm not sure if a JVM thread is created and its stack allocated when a Thread
instance is constructed or only when start()
method is called. Whatever the correct answer is, I also wonder if it is a part of the JVM/Java-language spec or up to implementation, such as OpenJDK.

- 2,513
- 4
- 25
- 31
-
1This is easy to test: you can easily create one million `Thread` instances (for example with a `Runnable` that does only `Thread.sleep(10_000);`). When you try to start them your code will fail after starting some thousands of them. – Thomas Kläger Jul 20 '23 at 12:52
-
@ThomasKläger well, it is still not a definitive answer whether a JVM thread is created on `start()` or whether it's created on `Thread` instance creation, but some critical OS level resources are only allocated when it's 'start()'-ed, but it's sufficient for practical reasons: many thanks!! :) If you bother to make an actual answer from it, I will be happy to accept it :) – morgwai Jul 20 '23 at 12:59
-
@ThomasKläger also, is it possible that this JVM implementation specific? Thanks! – morgwai Jul 20 '23 at 13:17
2 Answers
For one thing you could try this for yourself by creating many Thread
objects and later try to start all those Thread
s:
public class ThreadRunner {
public static void main(String[] args) {
int maxThreads = 10_000_000;
// Create Thread instances
Thread[] threads = new Thread[maxThreads];
Runnable r = () -> {
try {
Thread.sleep(1000_000);
} catch (InterruptedException e) {
}
};
for (int i = 0; i < maxThreads; i++) {
threads[i] = new Thread(r);
}
// start Threads
int count = 0;
try {
for (Thread t : threads) {
t.start();
count++;
if (count % 1000 == 0) {
System.out.println(count);
}
Thread.yield();
}
} catch (OutOfMemoryError e) {
System.out.println(count);
}
}
}
The other way is to check the source of the OpenJDK:
Thread.java calls the native
start0()
methodThread.c declares that
start0
is forwared to the nativeJVM_StartThread
{"start0", "()V", (void *)&JVM_StartThread},
jvm.cpp contains that native function which determines the stack size for the native thread and then creates it with
jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread)); // Allocate the C++ Thread structure and create the native thread. The // stack size retrieved from java is 64-bit signed, but the constructor takes // size_t (an unsigned type), which may be 32 or 64-bit depending on the platform. // - Avoid truncating on 32-bit platforms if size is greater than UINT_MAX. // - Avoid passing negative values which would result in really large stacks. NOT_LP64(if (size > SIZE_MAX) size = SIZE_MAX;) size_t sz = size > 0 ? (size_t) size : 0; native_thread = new JavaThread(&thread_entry, sz);
Does this depend on the specific JVM implementation?
In principle, it could depend on the specific JVM implementation. It would however be silly to create an OS thread before it is really needed.
The main reason why I call this silly is the resource management. When you create an OS thread you must also destroy it. When you start()
a Thread
the OS thread is created, the threads run()
method is exeucted and after that method returns the OS thread will be destroyed.
If a hypothetical JVM implementation created the OS thread already in the Thread
constructor it would also need some way of destroying that OS thread when the Thread
instance is garbage collected which will be hassle.

- 17,754
- 3
- 23
- 34
A Thread is created only when the start() method is called. You can check it out on the JDK doc here: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Thread.html
The following code shows you how this works
import java.util.Random;
public class Threads {
private static final Random rnd = new Random();
private static int rand(int a, int b) {
synchronized (rnd) {
return rnd.nextInt(b - a) + a;
}
}
private static void loop() {
while (true) {
synchronized (rnd) {
try {
Thread.sleep(rand(200, 1000));
} catch (InterruptedException e) {}
System.out.println("Hi, i'm ");
System.out.println("thread #" + Thread.currentThread().getId());
}
}
}
public static class MyThread extends Thread {
@Override
public void run() {
loop();
}
}
public static void main(String[] args) {
Thread t1 = new MyThread();
t1.start();
new Thread(Threads::loop).start();
new MyThread().start();
new MyThread().start();
new MyThread().start();
loop();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

- 31
- 7
-
2forgive me as I can't find the relevant part there: could you please point me more exactly? I've read carefully the class level description, all constructor descriptions and `start()` method description, each 2 times, but still can't find it... Thanks! – morgwai Jul 20 '23 at 12:50
-
"The following code would then create a thread and start it running: PrimeThread p = new PrimeThread(143); p.start(); " We first create an instance of the PrimeThread, but it is not enough to begin execution. To actually run the thread and start the desired operations, we call the start() method. – Freezing Jul 20 '23 at 13:04
-
forgive me again, but I honestly fail to see how this implies that a JVM thread is created (and as a consequence its stack memory allocated) only when `start()` is called... am I missing something? – morgwai Jul 20 '23 at 13:49