1

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.

morgwai
  • 2,513
  • 4
  • 25
  • 31
  • 1
    This 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 Answers2

3

For one thing you could try this for yourself by creating many Thread objects and later try to start all those Threads:

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() method

  • Thread.c declares that start0 is forwared to the native JVM_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.

Thomas Kläger
  • 17,754
  • 3
  • 23
  • 34
0

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();
        }

    }


}
Freezing
  • 31
  • 7
  • 2
    forgive 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