This document discusses how the Java virtual machine (JVM) maps priorities for threads executing in the JVM (Java threads) onto native thread priorities on Solaris. It covers both current and past implementations of Solaris threads and the JVM.
The JVM defines a range of ten logical priorities for Java threads, including:
java.lang.Thread.MIN_PRIORITY = 1 java.lang.Thread.NORM_PRIORITY = 5 java.lang.Thread.MAX_PRIORITY = 10
These values [1..10] are passed into
Thread.setPriority(int)
to assign priorities to Java
threads. The default priority of a Java thread is
NORM_PRIORITY
. (A Java thread that doesn't explicitly
call setPriority
runs at NORM_PRIORITY
.)
A JVM is free to implement priorities in any way it chooses,
including ignoring the value.
The Java HotSpot virtual machine currently associates each Java thread with a unique native thread. The relationship between the Java thread and the native thread is stable and persists for the lifetime of the Java thread.
Prior to Solaris 9 the default libthread was the so-called
T1 libthread. T1 provided an M:N threading model where M
native threads were multiplexed on top of N kernel threads
(LWPs). The relationship between native threads and LWPs was
fluid and dynamic and could change even while a thread was running
and without the knowledge of the thread. Solaris provided the
priocntl()
system call to change the dispatching
priority of an LWP, but because the relationship between LWPs and
native threads was unstable so there was no reliable way to change
to change the dispatching priority of a native thread. (The JVM
could change the priority of the LWP on which a Java thread was
currently running, but the thread could switch to another LWP
without the JVM's knowledge).
T2, the default libthread in Solaris 9 and better, implements a much more simple and robust 1:1 threading model. Each native thread is assigned to a unique LWP, and that relationship is stable for the lifetime of the native thread.
Both T1 and T2 expose a thr_setprio()
API that
applications use to set a thread's process-local priority. The
value assigned by thr_setprio()
is a process-local
attribute and is not visible to the kernel scheduler. The
thr_setprio()
-priority controls the placement and
ordering of threads on user-level process-local sleep queues, such
as the queue of threads associated with a contended process-local
mutex. In HotSpot most mutexes are uncontended and condvars have
usually have either 0 or 1 threads. As such, the ordering imposed
by thr_setprio()
-priority has very little effect on
most Java threads. The thr_setprio()
function supports
priority values in the range 0 through 127, inclusive, with 127
representing the highest priority.
T1 also uses thread priority to implement rudimentary user-mode
preemption. T1 maintains an invariant that the
thr_setprio()
-priority of any thread on the local
ready queue must be less than or equal to the priority of any
unbound thread that's currently running and associated with an LWP.
If the invariant is threatened, T1 preempts the lowest priority
executing thread to "steal" its LWP in order to reestablish the
invariant.
Preemption can occur in these cases:
An early version of T2, referred to as the alternate libthread, appeared in Solaris 8.
In Solaris an LWP's priority influences how many CPU cycles a
thread receives relative to other threads. The Solaris scheduler
uses priority (among other factors) to determine if one thread
should preempt another thread, how often a thread runs, and how
long a thread runs. Native LWP priorities are assigned by the
priocntl()
system call.
To recap, we have Java threads, with priorities set by the
Thread.setPriority
method. Java threads run on native
threads. The thr_setprio()
function is used to change
the priority of native threads. Native threads run on LWPs. The
priocntl()
system call is used to change the priority
of LWPs.
In releases earlier than 1.4.2, when a Java thread called the
Thread.setPriority
method or when a thread was
created, HotSpot would call thr_setprio()
to map the
Java priority to an native priority. Calling
thr_setprio()
had very little effect on the execution
behavior of a Java thread. The JVM did not call
priocntl()
to adjust the priority of the underlying
LWP. This was a conscious design decision because in the 1.4.2 time
frame the only libthread available on Solaris was the older T1
libthread.
Note: The JVM could have forced native threads to be
bound 1:1 to LWPs under T1 by specifying THR_BOUND
when threads are created. THR_BOUND
is not sufficient,
however, as threads that attach to the JVM might not be
THR_BOUND
and the primordial thread is not
THR_BOUND
. Given that there's no way in Solaris to
force a thread to be bound after it's been created, the HotSpot
implementors decided that it was prudent not to change LWP priority
when a Java thread called setPriority()
In 1.4.2 HotSpot was able to determine at startup time if it was running under T1 or T2. If the JVM started under T1 the effect of priorities would be precisely the same as in earlier releases.
Under T2, however, 1.4.2 translated calls to the
Thread.setPriority
method into calls to both
thr_setprio()
(to change the native process-local
priority) and to priocntl()
(to change the dispatching
priority of the underlying LWP). The JVM called
priocntl()
only for threads that are running in the
TS (timeshare), IA (interactive), and RT
(real-time) scheduling classes. Refer to the Solaris
priocntl(2)
man page for a description of scheduling
classes. If a Java thread was not in the TS, IA, or RT scheduling
classes the JVM would not attempt to set the priority of the
underlying LWP with priocntl()
.
Unfortunately the default priority of native threads in the TS
and IA scheduling classes is the highest possible priority. The
default logical priority for Java threads is
NORM_PRIORITY
, which is midway in domain of Java
thread priorities. When the JVM maps NORM_PRIORITY
to
native and LWP priorities, the outcome is a value that is less then
the default native priority. Let's say we have a 2-CPU system
running a JVM, and that JVM has two Java threads, both at
NORM_PRIORITY
. Assume that the threads are in the IA
or TS scheduling class, as is commonly the case. When the Java
threads are created, the JVM calls priocntl()
to map
NORM_PRIORITY
to the middle of the TS or IA priority
bands. Furthermore, assume that 2 native "C" threads in another
process are running/ready concurrently with the Java threads. Both
the native C threads and the Java threads are CPU-bound and spin,
computing. The native threads will run at the highest priority in
the TS or IA scheduling class, and the JVM threads will run at the
middle priority. Since all four threads are competing for CPU
cycles, the native threads will receive relatively more CPU cycles
and the Java threads will, in a sense, be disadvantaged. This
effect occurs only when Java threads compete with normal threads
and the system is entirely saturated.
Conversely, a benefit of using lower relative priorities is that in the TS and IA scheduling classes a thread running at lower priorities receives a longer quantum, assuming that it's not preempted mid-quantum by higher priority threads that become ready. A longer quantum is often beneficial for threads in server applications as the context switch rate from preemption decreases. A thread is permitted to run on a CPU for a longer period and the cache-reload transient (the period immediately after a thread is scheduled on to a CPU: when a thread incurs a high cache miss rate as it repopulates the CPU's data caches and displaces the previous thread's data) is amortized over a longer quantum.
JRE 5.0 provides the same priority mapping as 1.4.2 except that
Java priorities in the range [10...5] are all mapped to the highest
possible TS or IA priority, while priorities in the range [1..4]
are mapped to correspondingly lower native TS or IA priorities. The
advantage of this change is that Java threads at
NORM_PRIORITY
can now compete as expected with native
threads. If neither the Java threads nor the native threads have
explicitly set priorities (which is commonly the case), both
classes of thread will compete on an equal footing, running at the
highest priority in the TS or IA scheduling class.
Assuming that Java threads don't explicitly set their priority
with setPriority()
, this change restores the behavior
and effective LWP priority of Java threads that was used prior to
1.4.2. The disadvantage to this implementation is that Java
priorities from 5 to 10 are not differentiated. A Java thread at
logical priority 8 maps to the same LWP priority as a Java thread
at priority 9, for instance.
The following statements apply to all versions of HotSpot:
Thread.setPriority
method may be an
expensive operation. Frivolous priority adjustments can reduce
performance.NORM_PRIORITY
. This, in turn,
changes the thread's native priority and potentially changes the
priority of the LWP on which the native thread is running.
Specifically, if a native thread adjusts its priority and then
attaches to a JVM, the JVM overwrites the previous priority
settings of the thread. The JVM does not "undo" or restore a native
thread's priority if the thread detaches.The Thread.setPriority
and
Thread.yield
methods are advisory. They constitute
hints from the application to the JVM. Properly written, robust,
platform-independent code can use setPriority()
and
yield()
to optimize the performance of the
application, but should not depend on these attributes for
correctness. Likewise, no assumptions should be made about the
order in which threads are granted ownership of a monitor or the
order in which threads wake in response to the notify
or notifyAll
method.