/*
* Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.thread;
import java.util.*;
import com.sun.max.annotate.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.hosted.BootImage.Header;
import com.sun.max.vm.monitor.modal.modehandlers.lightweight.thin.*;
import com.sun.max.vm.monitor.modal.sync.*;
import com.sun.max.vm.monitor.modal.sync.JavaMonitorManager.VmLock;
import com.sun.max.vm.monitor.modal.sync.nat.*;
import com.sun.max.vm.object.*;
import com.sun.max.vm.runtime.*;
/**
* The {@code VmThreadMap} class contains all the active threads in the VM.
*/
public final class VmThreadMap {
/**
* Specialized JavaMonitor intended to be bound to {@link VmThreadMap#THREAD_LOCK} at image build time.
*
* MonitorEnter semantics are slightly modified to
* halt a meta-circular regression arising from thread termination clean-up.
*
* In addition, it is
* {@linkplain VmThreadMap#nativeSetGlobalThreadLock(Pointer) exposed}
* to the native code so that it can be locked when attaching a thread to the VM.
*
*/
static final class VMThreadMapJavaMonitor extends StandardJavaMonitor {
@Override
public void allocate() {
super.allocate();
NativeMutex nativeMutex = (NativeMutex) mutex;
nativeSetGlobalThreadLock(nativeMutex.asPointer());
}
@Override
public void monitorEnter() {
final VmThread currentThread = VmThread.current();
if (currentThread.state() == Thread.State.TERMINATED) {
if (ownerThread != currentThread) {
mutex.lock();
ownerThread = currentThread;
recursionCount = 1;
} else {
recursionCount++;
}
} else {
super.monitorEnter();
}
}
}
/**
* The global thread map of active threads in the VM.
*/
public static final VmThreadMap ACTIVE = new VmThreadMap();
/**
* The global lock used to synchronize access to the {@link #ACTIVE global thread list}.
* This lock is also help by the {@link VmOperationThread} when executing a {@link VmOperation}.
*/
public static final Object THREAD_LOCK = new VmLock("THREAD_LOCK");
static {
JavaMonitorManager.bindStickyMonitor(THREAD_LOCK, new VMThreadMapJavaMonitor());
}
/**
* The {@code IDMap} class manages thread IDs and a mapping between thread IDs and
* the corresponding {@code VmThread} instance.
* The id 0 is reserved and never used to aid the modal monitor scheme ({@link ThinLockword64}).
*
* Note that callers of {@link #acquire(VmThread)} or {@link #release(int)} must synchronize explicitly on {@link VmThreadMap#THREAD_LOCK} to ensure that
* the TERMINATED state is not disturbed during thread tear down.
*/
private static final class IDMap {
private int nextID = 1;
private int[] freeList;
private VmThread[] threads;
IDMap(int initialSize) {
freeList = new int[initialSize];
threads = new VmThread[initialSize];
for (int i = 0; i < freeList.length; i++) {
freeList[i] = i + 1;
}
}
/**
* Acquires an ID for a VmThread.
*
* <b>NOTE: This method is not synchronized. It is required that the caller synchronizes on {@link #THREAD_LOCK}.</b>
*
* @param thread the VmThread for which an ID should be assigned
* @return the ID assigned to {@code thread}
*/
int acquire(VmThread thread) {
int id = thread.id();
if (id != 0) {
FatalError.check(get(id) == thread, "Thread's ID identifies another thread");
return id;
}
final int length = freeList.length;
if (nextID >= length) {
// grow the free list and initialize the new part
final int[] newFreeList = Arrays.copyOf(freeList, length * 2);
for (int i = length; i < newFreeList.length; i++) {
newFreeList[i] = i + 1;
}
freeList = newFreeList;
// grow the threads list and copy
final VmThread[] newVmThreads = new VmThread[length * 2];
for (int i = 0; i < length; i++) {
newVmThreads[i] = threads[i];
}
threads = newVmThreads;
}
id = nextID;
nextID = freeList[nextID];
threads[id] = thread;
thread.setID(id);
return id;
}
/**
* <b>NOTE: This method is not synchronized. It is required that the caller synchronizes on {@link #THREAD_LOCK}.</b>
*
* @param id
*/
void release(int id) {
freeList[id] = nextID;
threads[id] = null;
nextID = id;
}
@INLINE
VmThread get(int id) {
// this operation may be performance critical, so avoid the bounds check
return UnsafeCast.asVmThread(ArrayAccess.getObject(threads, id));
}
}
/**
* Informs the native code of the mutex used to synchronize on {@link #THREAD_LOCK}
* which serves as a global thread creation and GC lock.
*
* @param mutex the address of a platform specific mutex
*/
@C_FUNCTION
private static native void nativeSetGlobalThreadLock(Pointer mutex);
private final IDMap idMap = new IDMap(64);
// The main thread is not counted by the normal mechanisms so we start accounting from 1
/**
* The number of live daemon and non-daemon threads.
*/
private volatile int liveThreads = 1;
/**
* Total number of threads started since VM began.
*/
private volatile int totalStarted = 1;
/**
* Peak live thread count.
*/
private volatile int peakThreadCount = 1;
/**
* The number of currently running non-daemon threads running, excluding
* the {@linkplain VmThread#mainThread main} thread.
*/
private volatile int nonDaemonThreads;
/**
* The head of the VM thread locals list.
*
* The address of this field in {@link #ACTIVE} is exposed to native code via
* {@link Header#tlaListHeadOffset}.
* This allows a debugger attached to the VM to discover all Java threads without using
* platform specific mechanisms (such as thread_db on Solaris and Linux or Mach APIs on Darwin).
*/
private Pointer tlaListHead = Pointer.zero();
/**
* Once true, no more threads can be started.
*/
private volatile boolean vmTerminating;
public void setVMTerminating() {
vmTerminating = true;
}
@INLINE
private static Pointer getPrev(Pointer tla) {
return VmThreadLocal.BACKWARD_LINK.load(tla);
}
@INLINE
private static Pointer getNext(Pointer tla) {
return VmThreadLocal.FORWARD_LINK.load(tla);
}
@INLINE
private static void setPrev(Pointer tla, Pointer prev) {
if (!tla.isZero()) {
VmThreadLocal.BACKWARD_LINK.store3(tla, prev);
}
}
@INLINE
private static void setNext(Pointer tla, Pointer next) {
if (!tla.isZero()) {
VmThreadLocal.FORWARD_LINK.store3(tla, next);
}
}
/**
* Adds a pre-allocated thread to the map. This reserves an ID for the thread
* but does not add its thread locals to the global list of running threads.
*
* @param thread a pre-allocated thread
*/
public static void addPreallocatedThread(VmThread thread) {
ACTIVE.idMap.acquire(thread);
}
/**
* Adds the specified thread locals to the ACTIVE thread map and initializes several of its
* important values (such as its ID and VM thread reference).
*
* <b>NOTE: This method is not synchronized. It is required that the caller synchronizes on {@link #THREAD_LOCK}.</b>
*
* @param thread the VmThread to add
* @param tla a pointer to the VM thread locals for the thread
* @param daemon specifies if {@code thread} is a daemon
*/
public static void addThreadLocals(VmThread thread, Pointer tla, boolean daemon) {
setNext(tla, ACTIVE.tlaListHead);
setPrev(ACTIVE.tlaListHead, tla);
ACTIVE.tlaListHead = tla;
}
/**
* Increments the number of active non-daemon threads by 1.
*
* <b>NOTE: This method is not synchronized. It is required that the caller synchronizes on {@link #THREAD_LOCK}.</b>
*
* @return {@code true} if the non-daemon thread can continue running; {@code false} if the main thread is in the process of exiting
*/
static boolean incrementNonDaemonThreads() {
if (ACTIVE.vmTerminating) {
return false;
}
if (VmThread.TraceThreads) {
boolean lockDisabledSafepoints = Log.lock();
Log.print("Adding non-daemon thread - ");
Log.print(ACTIVE.nonDaemonThreads + 1);
Log.println(" non-daemon threads now running");
Log.unlock(lockDisabledSafepoints);
}
ACTIVE.nonDaemonThreads++;
return true;
}
/**
* Decrements the number of active non-daemon threads by 1.
*
* <b>NOTE: This method is not synchronized. It is required that the caller synchronizes on {@link #THREAD_LOCK}.</b>
*/
static void decrementNonDaemonThreads() {
if (VmThread.TraceThreads) {
boolean lockDisabledSafepoints = Log.lock();
Log.print("Removed non-daemon thread - ");
Log.print(ACTIVE.nonDaemonThreads - 1);
Log.println(" non-daemon threads remain");
Log.unlock(lockDisabledSafepoints);
}
ACTIVE.nonDaemonThreads--;
THREAD_LOCK.notify();
}
/**
* Remove the specified VM thread locals from this thread map.
*
* <b>NOTE: This method is not synchronized. It is required that the caller synchronizes on {@link #THREAD_LOCK}.</b>
*
* @param thread the thread for the locals to remove from this map
*/
public void removeThreadLocals(VmThread thread) {
Pointer tla = thread.tla();
if (tlaListHead == tla) {
// this vm thread locals is at the head of list
tlaListHead = getNext(tlaListHead);
} else {
// this vm thread locals is somewhere in the middle
final Pointer prev = getPrev(tla);
final Pointer next = getNext(tla);
setPrev(next, prev);
setNext(prev, next);
}
// set this vm thread locals' links to zero
setPrev(tla, Pointer.zero());
setNext(tla, Pointer.zero());
// release the ID for a later thread's use
idMap.release(thread.id());
if (!thread.daemon && thread != VmThread.mainThread) {
decrementNonDaemonThreads();
}
liveThreads--;
}
private VmThreadMap() {
}
/**
* Creates the native thread for a VM thread and start it running.
*
* @param thread the VM thread to create
* @param stackSize the requested stack size
* @param priority the initial priority of the thread
*/
public void startThread(VmThread thread, Size stackSize, int priority) {
synchronized (THREAD_LOCK) {
final int id = idMap.acquire(thread);
thread.daemon = thread.javaThread().isDaemon();
if (!thread.daemon) {
if (!incrementNonDaemonThreads()) {
throw new IllegalStateException("Cannot start " + thread.javaThread() + " after the main thread has exited");
}
}
final Word nativeThread = VmThread.nativeThreadCreate(id, stackSize, priority);
if (nativeThread.isZero()) {
/* This means that we did not create the native thread at all so there is nothing to
* terminate. Most likely we ran out of memory allocating the stack, so we throw
* an out of memory exception. There is a small possibility that the failure was in the
* actual OS thread creation but that would require a way to disambiguate.
*/
decrementNonDaemonThreads();
throw new OutOfMemoryError("Unable to create new native thread");
}
totalStarted++;
liveThreads++;
if (liveThreads > peakThreadCount) {
peakThreadCount = liveThreads;
}
}
}
/**
* Waits for all non-daemon threads to finish.
*
* This must only be called by the {@linkplain VmThread#mainThread main} thread.
*/
public void joinAllNonDaemons() {
FatalError.check(VmThread.current() == VmThread.mainThread, "Only the main thread should join non-daemon threads");
synchronized (THREAD_LOCK) {
while (nonDaemonThreads > 0) {
if (VmThread.TraceThreads) {
boolean lockDisabledSafepoints = Log.lock();
Log.print("Main thread waiting for ");
Log.print(nonDaemonThreads);
Log.println(" non-daemon threads to terminate");
Log.unlock(lockDisabledSafepoints);
}
try {
THREAD_LOCK.wait();
} catch (Exception exception) {
FatalError.unexpected("Error waiting for all non-daemon threads", exception);
}
}
}
if (VmThread.TraceThreads) {
Log.println("Main thread finished waiting for all non-daemon threads to terminate");
}
}
/**
* Iterates over all the VM thread locals in this thread map and run the specified procedure.
* <b>NOTE: It is recommended that the caller synchronizes on {@link #THREAD_LOCK}.</b>
*
* @param predicate a predicate to check on the VM thread locals
* @param procedure the procedure to apply to each VM thread locals
*/
public void forAllThreadLocals(Pointer.Predicate predicate, Pointer.Procedure procedure) {
Pointer tla = tlaListHead;
while (!tla.isZero()) {
if (predicate == null || predicate.evaluate(tla)) {
procedure.run(tla);
}
tla = getNext(tla);
}
}
/**
* Gets the {@code VmThread} object associated with the specified thread id.
*
* @param id the thread id
* @return a reference to the {@code VmThread} object for the specified id
*/
@INLINE
public VmThread getVmThreadForID(int id) {
return idMap.get(id);
}
/**
* Gets a snapshot of the currently executing threads.
* JVMTI agent threads can be included optionally.
* The VMOperation thread is never included.
*
*
* @param includeJVMTIAgentThreads specifies whether {@linkplain VmThread#isJVMTIAgentThread() JVMTI agent threads}
* are to be included in the snapshot
* @return a snapshot of the currently executing threads
*/
public static Thread[] getThreads(final boolean includeJVMTIAgentThreads) {
final ArrayList<Thread> threads = new ArrayList<Thread>();
Pointer.Procedure proc = new Pointer.Procedure() {
public void run(Pointer tla) {
VmThread vmThread = VmThread.fromTLA(tla);
if (vmThread.javaThread() != null && !vmThread.isVmOperationThread() && (includeJVMTIAgentThreads || !vmThread.isJVMTIAgentThread())) {
threads.add(vmThread.javaThread());
}
}
};
synchronized (THREAD_LOCK) {
VmThreadMap.ACTIVE.forAllThreadLocals(null, proc);
}
return threads.toArray(new Thread[threads.size()]);
}
public static int getTotalStartedThreadCount() {
synchronized (THREAD_LOCK) {
return ACTIVE.totalStarted;
}
}
public static int getPeakThreadCount() {
synchronized (THREAD_LOCK) {
return ACTIVE.peakThreadCount;
}
}
public static void resetPeakThreadCount() {
synchronized (THREAD_LOCK) {
ACTIVE.peakThreadCount = ACTIVE.liveThreads;
}
}
public static int getLiveTheadCount() {
synchronized (THREAD_LOCK) {
return ACTIVE.liveThreads;
}
}
public static int getDaemonThreadCount() {
synchronized (THREAD_LOCK) {
// nonDaemonThreads does not include main but liveThreads does
return ACTIVE.liveThreads - (ACTIVE.nonDaemonThreads + 1);
}
}
}