If you want some more detail, when a virtual thread is in the runnable state, it is reachable from the scheduler (which itself is a Java object, and not a GC root); when it is blocked on a lock or IO, then the lock object or the IO mechanism must retain a reference to it, or there would be no way to unblock it. The thread object has a reference to the stack, which is a heap object (actually, it could be made up of several heap objects).
A thread that is not strongly reachable can provably no longer make progress -- it must be blocked but there's no way to unblock it -- and will be collected even if it has not terminated. It may live forever in our hearts, but not in the heap.
Interesting to read. It's a technical distinction with a primarily implementation difference, which I don't yet understand (i.e. have not taken the time to read yet), but I infer from the fragment that I did read, that there is some degree of semi-magical hoop jumping going on to make the CPU stack live in a Java heap object to which a reference can be taken in Java code.
Objects are obviously rooted for blocked virtual threads that may resume - a formal understanding of them being GC roots - but the implementation appears to be by taking a reference to the heap object containing the stack at the moment of being blocked, presumably by a JVM native method or similar.
> Objects are obviously rooted for blocked virtual threads that may resume
If by "rooted" you mean reachable in the object graph when starting the traversal from the roots, then yes. If a blocked thread isn't reachable, there is no way to call its unpark method that resumes it.
> the heap object containing the stack at the moment of being blocked, presumably by a JVM native method or similar.
Yes, we implemented virtual threads on top of continuations that, in turn, are implemented inside the VM. Their stacks are reified as heap objects.
If you want some more detail, when a virtual thread is in the runnable state, it is reachable from the scheduler (which itself is a Java object, and not a GC root); when it is blocked on a lock or IO, then the lock object or the IO mechanism must retain a reference to it, or there would be no way to unblock it. The thread object has a reference to the stack, which is a heap object (actually, it could be made up of several heap objects).
A thread that is not strongly reachable can provably no longer make progress -- it must be blocked but there's no way to unblock it -- and will be collected even if it has not terminated. It may live forever in our hearts, but not in the heap.