Spec-Zone .ru
спецификации, руководства, описания, API
|
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.
For additional information about T1, T2, and LWPs refer to the following:
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 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 specifyingTHR_BOUND
when threads are created.THR_BOUND
is not sufficient, however, as threads that attach to the JVM might not beTHR_BOUND
and the primordial thread is notTHR_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 calledsetPriority()
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. An excellent reference for these topics is Chapter 9, "Threads,"
in Joshua Bloch's book