Multi-threading

Multithreading in JNode involves the scheduling of multiple java.lang.Thread instances between 1 or more physical processors. (In reality, multiprocessor support is not yet stable). The current implementation uses the yieldpoint scheduling model as described below.

Yieldpoint scheduling

Yieldpoint scheduling means that every thread checks at certain points (called "yieldpoints") in the native code to see if it should let other threads run. The native code compiler adds yieldpoints into the native code stream at the beginning and end of a method, at backward jumps, and at method invocations. The yieldpoint code checks to see if the "yield" flag has been set for the current thread, and if is has, it issues a yield (software-)interrupt. The kernel takes over and schedules a new thread.

The "yield" flag can be set by a timer interrupt, or by the (kernel) software itself, e.g. to perform an explicit yield or in case of locking synchronization methods.

The scheduler invoked by the (native code) kernel is implemented in the VmProcessor class. This class (one instance for every processor) contains a list of threads ready to run, a list of sleeping threads and a current thread. On a reschedule, the current thread is appended to the end of the ready to run thread-list. Then the sleep list is inspected first for threads that should wake-up. These threads are added to the ready to run thread-list. After that the first thread in the ready to run thread-list is removed and used as current thread. The reschedule method returns and the (native code) kernel does the actual thread switching.

The scheduler itself runs in the context of the kernel and should not be interrupted. A special flag is set to prevent yieldpoints in the scheduler methods themselves from triggering reentrant yieldpoint interrupts. The flag is only cleared when the reschedule is complete

Why use yieldpoint scheduling?

JNode uses yield point scheduling to simplify the implementation of the garbage collector and to reduce the space needed to hold GC descriptors.

When the JNode garbage collector runs, it needs to find all "live" object references so that it can work out which objects are not garbage. A bit later, it needs to update any references for objects that have been moved in memory. Most object references live either in other objects in the heap, or in local variables and parameters held on one of the thread stacks. However, when a thread is interrupted, the contents of the hardware registers are saved in a "register save" area, an this may include object references.

The garbage collector is able to find these reference because the native compiler creates descriptors giving the offsets of references. For each class, there is a descriptor giving the offsets of its reference attributes and statics in their respective frames. For each method or constructor, another descriptor gives the corresponding stack frame layout. But we still have to deal with the saved registers.

If we allowed a JNode thread to be interrupted at any point, the native compiler would need to create descriptors all possible saved register sets. In theory, we might need a different descriptor corresponding to every bytecode. By using yield points, we can guarantee that "yields" only occur at a fixed places, thereby reducing the number of descriptors that that need to be kept.

However, the obvious downside of yieldpoints is the performance penalty of repeatedly testing the "yield" flag, especially when executing a tight loop.

Thread priorities

Thread can have different priorities, ranging from Thread.LOW_PRIORITY to Thread.HIGH_PRIORITY. In JNode these priorities are implemented via the ready to run thread-list. This list is (almost) always sorted on priority, which means that the threads with the highest priority comes first.

There is one exception on this rule, which is in the case of busy-waiting in the synchronization system. Claiming access to a monitor (internals) involves a busy-waiting loop with an explicit yield. This yield ignores the thread priority to avoid starvation of lower-priority threads, which will lead to an endless waiting time for the high priority thread.

Classes involved

The following classes are involved in the scheduling system. All of these classes are in the org.jnode.vm package.

  • VmProcessor
  • VmThread contains the internal (JNode specific) data for a single thread. This class is extended for each specific platform

i need this info

Comments below incorporated and flagged for deletion: 2009-08-26

Hi,
thanks for this piece of documentation.

Andreas

Why yieldpoints vs. scheduling directly from timer interrupt?

The explanation is quite clear, so a fiew questions on background of scheduling model arised for me:

What is the Idea behind introducing some overhead by means of compiling-in yieldpoints to native code?
Is it a problem when a thread looses control (and context switches to other thread) in the middle of operation which is sujested to be "quantum" from point of view of JVM ? For example a lot of architectures save/restore floating point coprocessor registers in the middle of fp calculations...
It's also likely that future processors or Java targeted ASIC-s would have some special hardware facilities for Java task switching

Why yieldpoints

The problem here is support for garbage collection.

The GC system needs to now where there are references to object in order to allow for exact garbage collection (or to move objects in memory).

If you allow threads to be interrupted anywhere, this would mean that you would need GC information for ALL points in the code. This would be to expensive in terms of memory usage.

By only allowing thread switches at yieldpoints, you can limit the amount of GC info that is needed.

Ewout