/* * 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 static com.sun.max.platform.Platform.*; import static com.sun.max.vm.VMConfiguration.*; import static com.sun.max.vm.VMOptions.*; import static com.sun.max.vm.actor.member.InjectedReferenceFieldActor.*; import static com.sun.max.vm.intrinsics.MaxineIntrinsicIDs.*; import static com.sun.max.vm.thread.VmThreadLocal.*; import static com.sun.max.vm.type.ClassRegistry.*; import java.lang.Thread.State; import java.security.*; import sun.misc.*; import com.sun.max.annotate.*; import com.sun.max.atomic.*; import com.sun.max.lang.*; import com.sun.max.memory.*; import com.sun.max.unsafe.*; import com.sun.max.vm.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.bytecode.refmaps.*; import com.sun.max.vm.code.*; import com.sun.max.vm.compiler.target.*; import com.sun.max.vm.heap.*; import com.sun.max.vm.hosted.*; import com.sun.max.vm.jdk.*; import com.sun.max.vm.jni.*; import com.sun.max.vm.log.*; import com.sun.max.vm.monitor.modal.sync.*; import com.sun.max.vm.object.*; import com.sun.max.vm.reference.*; import com.sun.max.vm.runtime.*; import com.sun.max.vm.stack.*; import com.sun.max.vm.ti.*; import com.sun.max.vm.value.*; /** * The MaxineVM VM specific implementation of threads. * * A thread's stack layout is as follows: * * <pre> * High addresses * * +---------------------------------------------+ <-- highestSlotAddress * | OS thread specific data | * | and native frames | * +---------------------------------------------+ * | | * | Frames of Java methods, | * stack pointer --> | native stubs, and | * | native functions | <-- lowestActiveSlotAddress * | | * +---------------------------------------------+ * | X X X Stack overflow detection X X X | * | X X X (yellow zone) X X X | * page aligned --> +---------------------------------------------+ <-- yellowZone * | X X X Stack overflow detection X X X | * | X X X (red zone) X X X | * page aligned --> +---------------------------------------------+ <-- redZone * * Low addresses * </pre> * * The stack layout for each thread is traced when a thread starts up if * the {@link #TraceThreads -XX:+TraceThreads} VM option is used. * * @see VmThreadLocal */ public class VmThread { static boolean TraceThreads; static { VMOptions.addFieldOption("-XX:", "TraceThreads", VmThread.class, "Trace thread start-up and shutdown.", MaxineVM.Phase.PRISTINE); } private static final Size DEFAULT_STACK_SIZE = Size.K.times(256); private static final VMSizeOption STACK_SIZE_OPTION = register(new VMSizeOption("-Xss", DEFAULT_STACK_SIZE, "Stack size of new threads."), MaxineVM.Phase.PRISTINE); @HOSTED_ONLY private static final ThreadLocal<CompactReferenceMapInterpreter> HOSTED_COMPACT_REFERENCE_MAP_INTERPRETER = new ThreadLocal<CompactReferenceMapInterpreter>() { @Override protected CompactReferenceMapInterpreter initialValue() { return new CompactReferenceMapInterpreter(); } }; private static java.util.concurrent.atomic.AtomicInteger nextUUid = new java.util.concurrent.atomic.AtomicInteger(1); public static final ThreadGroup systemThreadGroup; public static final ThreadGroup mainThreadGroup; public static final VmThread referenceHandlerThread; public static final VmThread finalizerThread; /** * Single instance of {@link VmOperationThread}. */ public static final VmThread vmOperationThread; /** * Single instance of {@link SignalDispatcher}. */ public static final VmThread signalDispatcherThread; /** * The main thread created by the primordial thread at runtime. */ public static final VmThread mainThread; @HOSTED_ONLY public static ThreadGroup hostSystemThreadGroup; @HOSTED_ONLY public static ThreadGroup hostMainThreadGroup; @HOSTED_ONLY public static Thread hostReferenceHandlerThread; @HOSTED_ONLY public static Thread hostFinalizerThread; @HOSTED_ONLY public static Thread hostMainThread; static { hostSystemThreadGroup = null; hostMainThreadGroup = null; hostReferenceHandlerThread = null; hostFinalizerThread = null; hostMainThread = null; hostSystemThreadGroup = Thread.currentThread().getThreadGroup(); for (ThreadGroup parent = hostSystemThreadGroup.getParent(); parent != null; parent = hostSystemThreadGroup.getParent()) { hostSystemThreadGroup = parent; } for (Thread thread : getThreads(hostSystemThreadGroup)) { if (thread.getClass().equals(JDK.java_lang_ref_Reference$ReferenceHandler.javaClass())) { hostReferenceHandlerThread = thread; } else if (thread.getClass().equals(JDK.java_lang_ref_Finalizer$FinalizerThread.javaClass())) { hostFinalizerThread = thread; } } for (ThreadGroup group : getThreadGroups(hostSystemThreadGroup)) { if (group.getName().equals("main")) { hostMainThreadGroup = group; for (Thread thread : getThreads(group)) { if (thread.getName().equals("main")) { hostMainThread = thread; } } } } assert hostSystemThreadGroup != null; assert hostMainThreadGroup != null; assert hostReferenceHandlerThread != null; assert hostFinalizerThread != null; assert hostMainThread != null; systemThreadGroup = new ThreadGroup(hostSystemThreadGroup.getName()); systemThreadGroup.setMaxPriority(hostSystemThreadGroup.getMaxPriority()); WithoutAccessCheck.setInstanceField(systemThreadGroup, "parent", null); ReferenceValue systemThreadGroupRef = ReferenceValue.from(systemThreadGroup); mainThreadGroup = new ThreadGroup(systemThreadGroup, hostMainThreadGroup.getName()); mainThread = initVmThread(copyProps(hostMainThread, new Thread(mainThreadGroup, hostMainThread.getName()))); Thread vmOperationJavaThread = new VmOperationThread(systemThreadGroup); vmOperationThread = initVmThread(vmOperationJavaThread); // the VmOperationThread thread is completely hidden; we could, with more complexity, // put it in a hidden group, but since the external world never sees it, it suffices to detach it. // N.B. at this point it is unstarted so not actually a child of systemThreadGroup WithoutAccessCheck.setInstanceField(vmOperationJavaThread, "group", null); signalDispatcherThread = initVmThread(new SignalDispatcher(systemThreadGroup)); try { referenceHandlerThread = initVmThread(copyProps(hostReferenceHandlerThread, (Thread) ReferenceHandler_init.invokeConstructor(systemThreadGroupRef, ReferenceValue.from(hostReferenceHandlerThread.getName())).asObject())); finalizerThread = initVmThread(copyProps(hostFinalizerThread, (Thread) FinalizerThread_init.invokeConstructor(systemThreadGroupRef).asObject())); } catch (Exception e) { throw FatalError.unexpected("Error initializing VM threads", e); } } @HOSTED_ONLY static Thread[] getThreads(ThreadGroup group) { Thread[] list = new Thread[group.activeCount()]; group.enumerate(list); return list; } @HOSTED_ONLY static ThreadGroup[] getThreadGroups(ThreadGroup group) { ThreadGroup[] list = new ThreadGroup[group.activeGroupCount()]; group.enumerate(list); return list; } @HOSTED_ONLY static VmThread initVmThread(Thread javaThread) { VmThread vmThread = VmThreadFactory.create(javaThread); VmThreadMap.addPreallocatedThread(vmThread); return vmThread; } @HOSTED_ONLY static Thread copyProps(Thread src, Thread dst) { dst.setDaemon(src.isDaemon()); dst.setPriority(src.getPriority()); return dst; } @CONSTANT_WHEN_NOT_ZERO private Thread javaThread; private volatile Thread.State state = Thread.State.NEW; private volatile boolean interrupted = false; private Throwable terminationCause; private int id; private int parkState; /** * Guaranteed unique for the lifetime of the VM. */ public final int uuid; /** * Denotes if this thread was started as a daemon. This property is only set once a thread * is about to run (for a VM created thread) or is running (for an attached thread) * and never changes thereafter. */ boolean daemon; /** * The stack guard page(s) used to detect recoverable stack overflow. */ private Pointer yellowZone = Pointer.zero(); /** * The safepoint-enabled thread locals associated with this thread. */ private Pointer tla = Pointer.zero(); /** * The name of this thread which is kept in sync with the name of the associated {@link #javaThread()}. */ @INSPECTED private String name; @CONSTANT protected Word nativeThread = Word.zero(); private final VmStackFrameWalker stackFrameWalker = new VmStackFrameWalker(Pointer.zero()); private final VmStackFrameWalker stackDumpStackFrameWalker = new VmStackFrameWalker(Pointer.zero()); @CONSTANT_WHEN_NOT_ZERO private VmStackFrameWalker samplingProfilerStackFrameWalker; private final StackReferenceMapPreparer stackReferenceMapPreparer = new StackReferenceMapPreparer(true, true); private final StackReferenceMapPreparer stackReferenceMapVerifier = new StackReferenceMapPreparer(true, false); private final CompactReferenceMapInterpreter compactReferenceMapInterpreter = new CompactReferenceMapInterpreter(); public JavaMonitor protectedMonitor; private ConditionVariable waitingCondition = ConditionVariableFactory.create(); public final HeapScheme.GCRequest gcRequest = VMConfiguration.vmConfig().heapScheme().createThreadLocalGCRequest(this); /** * A "monitor" used to suspend the thread by {@link VmOperation}. */ public final OSMonitor.SuspendMonitor suspendMonitor = new OSMonitor.SuspendMonitor(); /** * Marks this as a JVMTI agent thread. These are not visible to calls like {@link Thread#getThreads}. */ private boolean jvmtiAgent; /** * Holds the exception object for the exception currently being raised. This value will only be * non-null during the unwinding process between calls to {@link #storeExceptionForHandler(Throwable, TargetMethod, int)} * and {@link #loadExceptionForHandler()}. */ private Throwable exception; private boolean yellowZoneUnprotected; /** * Number of shadow zone pages for overflow checking. */ public static final int STACK_SHADOW_PAGES = 2; /** * Records the fact that native code has unprotected the yellow zone. */ public final void nativeTrapHandlerUnprotectedYellowZone() { VmStackFrameWalker sfw = stackFrameWalker; if (sfw.isInUse()) { Log.println("stack overflow occurred while raising another exception or reference map preparing"); Log.println("may need to increase safetyMargin in VmThread.checkYellowZoneForRaisingException()"); FatalError.unexpected("stack overflow occurred while raising another exception or reference map preparing"); } yellowZoneUnprotected = true; } /** * Determines if execution on this thread is "too close" to the yellow zone * for safely unwinding to an exception handler without running the risk * of "banging" the yellow zone. Too close is determined to be within * ({@link #STACK_SHADOW_PAGES} + 1) pages of the yellow zone. * If it is, then the yellow zone is unguarded to mitigate the * chance of exception raising itself causing stack overflow. The exception * handler will reset the guard pages when it calls {@link #loadExceptionForHandler()}. */ public void checkYellowZoneForRaisingException() { if (platform().isa == ISA.AMD64) { if (!yellowZoneUnprotected) { Pointer sp = VMRegister.getCpuStackPointer(); int safetyMargin = (1 + STACK_SHADOW_PAGES) * platform().pageSize; if (sp.minus(yellowZoneEnd()).toInt() < safetyMargin) { VirtualMemory.unprotectPages(yellowZone, YELLOW_ZONE_PAGES); yellowZoneUnprotected = true; } } else { // Yellow zone was unprotected in the native trap handler - see nativeTrapHandlerUnprotectedYellowZone() } } else { throw FatalError.unimplemented(); } } /** * Stores the exception object being raised. This will subsequently be {@linkplain #loadExceptionForHandler() loaded} * by the handler when the stack is unwound. * * @param e the exception being raised * @param handler the target method that will handle the exception * @param pos the position in {@code handler} of the handler entry point */ public final void storeExceptionForHandler(Throwable e, TargetMethod handler, int pos) { if (exception != null) { e.printStackTrace(Log.out); FatalError.unexpected("Previous exception never loaded", exception); } if (Throw.TraceExceptions > 0) { boolean lockDisabledSafepoints = Log.lock(); Log.printThread(VmThread.current(), false); Log.print(": "); Throw.logFrame("Caught in ", handler, handler.codeAt(pos).toPointer()); Log.unlock(lockDisabledSafepoints); } exception = e; } /** * Loads the exception object being raised. * * This method also: * <ol> * <li>Re-enables safepoints (they were disabled in {@link Throw#raise(Throwable, Pointer, Pointer, CodePointer)}).</li> * <li>Executes a safepoint.</li> * <li>Reprotects the yellow zone if the raising process unprotected it.</li> * </ol> */ public final Throwable loadExceptionForHandler() { SafepointPoll.enable(); SafepointPoll.safepointPoll(); Throwable e = exception; exception = null; FatalError.check(e != null, "Exception object lost during unwinding"); // Re-protect yellow page if is unprotected AND we're no longer too close to it if (yellowZoneUnprotected) { Pointer sp = VMRegister.getCpuStackPointer(); int safetyMargin = (1 + STACK_SHADOW_PAGES) * platform().pageSize; if (sp.minus(yellowZoneEnd()).toInt() >= safetyMargin) { VirtualMemory.protectPages(yellowZone, VmThread.YELLOW_ZONE_PAGES); yellowZoneUnprotected = false; } } return e; } /** * Gets the exception currently in flight. * * @return {@code null} if there is not exception in flight */ public final Throwable pendingException() { return exception; } /** * Exception thrown by a {@linkplain JniFunctions JNI function}. */ private Throwable jniException; /** * The number of VM operations {@linkplain VmOperationThread#submit(VmOperation) submitted} * by this thread for execution that have not yet completed. */ private volatile int pendingOperations; /** * The pool of JNI local references allocated for this thread. */ private JniHandles jniHandles; /** * Next thread waiting on the same monitor this thread is {@linkplain Object#wait() waiting} on. * Any thread can only be waiting on at most one monitor. * This thread is {@linkplain #isOnWaitersList() not} on a monitor's waiting thread list if * the value of this field is the thread itself. * * @see StandardJavaMonitor#monitorWait(long) * @see StandardJavaMonitor#monitorNotify(boolean) */ public VmThread nextWaitingThread = this; /** * Determines if this thread is on a monitor's list of waiting threads. */ public final boolean isOnWaitersList() { return nextWaitingThread != this; } public final void unlinkFromWaitersList() { nextWaitingThread = this; } /** * A stack of elements that support {@link AccessController#doPrivileged(PrivilegedAction)} calls. */ private PrivilegedElement privilegedStackTop; @HOSTED_ONLY public static Size stackSize() { return DEFAULT_STACK_SIZE; } public static VmThread fromJava(Thread javaThread) { return (VmThread) Thread_vmThread.getObject(javaThread); } @C_FUNCTION protected static native Word nativeThreadCreate(int id, Size stackSize, int priority); /** * Gets the current {@linkplain VmThreadLocal TLA}. * * @return the value of the safepoint {@linkplain SafepointPoll#getLatchRegister() latch} register. */ @INLINE public static Pointer currentTLA() { return SafepointPoll.getLatchRegister(); } /** * Gets a pointer to the JNI environment data structure for the current thread. * * @return a value of C type JNIEnv* */ @INLINE public static Pointer jniEnv() { if (MaxineVM.isHosted()) { return Pointer.zero(); } return JNI_ENV.addressIn(currentTLA()); } @INLINE public static VmThread current() { if (MaxineVM.isHosted()) { return mainThread; } return UnsafeCast.asVmThread(VM_THREAD.loadRef(currentTLA()).toJava()); } /** * Determines if the current thread is still attaching to the VM. In this state, * synchronization and garbage collection are disabled. */ public static boolean isAttaching() { return current() == null; } private static void executeRunnable(VmThread vmThread) throws Throwable { try { if (vmThread == mainThread) { // JVMTIEvent.THREAD_START is dispatched in JavaRunScheme vmConfig().runScheme().run(); } else { VMTI.handler().threadStart(vmThread); vmThread.javaThread.run(); } } finally { // 'stop0()' support. if (vmThread.terminationCause != null) { // We arrive here because an uncatchable non-Throwable object has been propagated as an exception. throw vmThread.terminationCause; } } } /** * A pre-allocated thread object used to service JNI AttachCurrentThread requests. * The need for such an object arises from the fact that synchronization and allocation * are not allowed when {@linkplain #add(int, boolean, Address, Pointer, Pointer, Pointer, Pointer) adding} * a thread to the global list of running threads. * * As part of the call to {@link #attach(Pointer, JniHandle, boolean, Pointer, Pointer, Pointer)} * made when attaching thread, a new pre-allocated thread object is created. */ private static final AtomicReference threadForAttach = new AtomicReference(); static { VmThread thread = VmThreadFactory.create(null); threadForAttach.set(thread); VmThreadMap.addPreallocatedThread(thread); } /** * Adds the current thread to the global list of running threads. * * This method must perform no synchronization or heap allocation and must disable safepoints. In addition, the * native caller must hold the global GC and thread list lock for the duration of this call. * * After returning a value of {@code 0} or {@code 1} this thread is now in a state where it can safely participate in a GC cycle. * A {@code -1} return value can only occur when this is a native thread being attached to the VM and it loses the * race to acquire the pre-allocated {@linkplain #threadForAttach thread-for-attach} object. * * @param id if {@code id > 0}, then it's an identifier reserved in the thread map for the thread being started. * Otherwise, {@code id} must be negative and is a temporary identifier (derived from the native thread * handle) of a thread that is being attached to the VM. * @param daemon indicates if the thread being added is a daemon thread. This value is ignored if {@code id > 0}. * @param nativeThread a handle to the native thread data structure (e.g. a pthread_t value) * @param etla the address of the safepoints-enabled TLA * @param stackBase the lowest address (inclusive) of the stack (i.e. the stack memory range is {@code [stackBase .. * stackEnd)}) * @param stackEnd the highest address (exclusive) of the stack (i.e. the stack memory range is {@code [stackBase .. * stackEnd)}) * @param yellowZone the stack page(s) that have been protected to detect stack overflow * @return {@code 1} if the thread was successfully added and it is the {@link VmOperationThread}, * {@code 0} if the thread was successfully added, {@code -1} if this attaching thread needs to try again * and {@code -2} if this attaching thread cannot be added because the main thread has exited */ @VM_ENTRY_POINT private static int add(int id, boolean daemon, Address nativeThread, Pointer etla, Pointer stackBase, Pointer stackEnd, Pointer yellowZone) { // Disable safepoints: SafepointPoll.setLatchRegister(DTLA.load(etla)); JNI_ENV.store3(etla, NativeInterfaces.jniEnv()); // Add the VM thread locals to the active map VmThread thread; boolean isAttaching = id < 0; if (isAttaching) { thread = UnsafeCast.asVmThread(threadForAttach.get()); if (thread == null || !threadForAttach.compareAndSet(thread, null)) { // We could not exclusively get the pre-allocated thread-for-attach object. // So we have to return to native code, release the global GC and thread list lock // which gives the winner a chance to allocate and register the next available // thread-for-attach object. Then we try again... return -1; } if (!daemon && !VmThreadMap.incrementNonDaemonThreads()) { return -2; } ID.store3(etla, Address.fromLong(thread.id)); } else { thread = VmThreadMap.ACTIVE.getVmThreadForID(id); daemon = thread.javaThread().isDaemon(); thread.daemon = daemon; } for (VmThreadLocal threadLocal : VmThreadLocal.valuesNeedingInitialization()) { threadLocal.initialize(); } HIGHEST_STACK_SLOT_ADDRESS.store3(etla, stackEnd); LOWEST_STACK_SLOT_ADDRESS.store3(etla, yellowZone.plus(platform().pageSize)); thread.nativeThread = nativeThread; thread.tla = etla; thread.stackFrameWalker.setTLA(etla); thread.stackDumpStackFrameWalker.setTLA(etla); thread.yellowZone = yellowZone; VM_THREAD.store3(etla, Reference.fromJava(thread)); VmThreadMap.addThreadLocals(thread, etla, daemon); return thread.isVmOperationThread() ? 1 : 0; } /** * Calls the {@link Runnable} associated with the current thread. This is called from native * thread startup code <b>after</b> the current thread has been {@linkplain #add(int, boolean, Address, Pointer, Pointer, Pointer, Pointer) added} * to the global list of running threads. * * ATTENTION: this signature must match 'VmThreadRunMethod' in "com.oracle.max.vm.native/substrate/threads.h". * * @param etla the address of the safepoints-enabled TLA * @param stackBase the lowest address (inclusive) of the stack (i.e. the stack memory range is {@code [stackBase .. stackEnd)}) * @param stackEnd the highest address (exclusive) of the stack (i.e. the stack memory range is {@code [stackBase .. stackEnd)}) */ @VM_ENTRY_POINT @INSPECTED private static void run(Pointer etla, Pointer stackBase, Pointer stackEnd) { // Enable safepoints: Pointer anchor = JniFunctions.prologue(JNI_ENV.addressIn(etla)); // JniFunctions.prologue calls VMTI.beginUpcallVM but we aren't actually in an upcall into the VM VMTI.handler().endUpcallVM(); final VmThread thread = VmThread.current(); VMLog.vmLog().threadStart(); thread.initializationComplete(); thread.traceThreadAfterInitialization(stackBase, stackEnd); // If this is the main thread, then start up the VM operation thread and other special VM threads if (thread == mainThread) { // NOTE: // The main thread must now bring the VM to the pristine state so as to // provide basic services (most importantly, heap allocation) before starting the other "system" threads. // // The main thread manages to avoid the normal runtime mechanism that sets this value thread.suspendMonitor.init(); // Initialize VMTI agents VMTI.handler().initialize(); // Code manager initialization must happen after parsing of pristine options // It must also be performed before pristine initialization of the heap scheme. // This is a temporary issue due to all code managers being instances of // FixedAddressCodeManager and assuming to be allocated directly after the boot region. // If the heap scheme is initialized first, it might take this address first, causing failure. // In the future, code manager initialization will be dictated by the heap scheme directly, // and this issue will disappear. Code.initialize(); vmConfig().initializeSchemes(MaxineVM.Phase.PRISTINE); // We can now start the other system threads. VmThread.vmOperationThread.startVmSystemThread(); SpecialReferenceManager.initialize(MaxineVM.Phase.PRISTINE); VmThread.signalDispatcherThread.startVmSystemThread(); } try { executeRunnable(thread); } catch (Throwable throwable) { thread.traceThreadForUncaughtException(throwable); final Thread javaThread = thread.javaThread(); // Uncaught exception should be passed by the VM to the uncaught exception handler defined for the thread. // Exception thrown by this one should be ignored by the VM. try { javaThread.getUncaughtExceptionHandler().uncaughtException(javaThread, throwable); } catch (Throwable ignoreMe) { } thread.terminationCause = throwable; } // inform any VMTI handlers VMTI.handler().threadEnd(thread); // possibly flush the log VMLog.vmLog().flush(VMLog.FLUSHMODE_EXIT, thread); // If this is the main thread terminating, initiate shutdown hooks after waiting for other non-daemons to terminate if (thread == mainThread) { VmThreadMap.ACTIVE.joinAllNonDaemons(); // Calling java.lang.Shutdown.exit() ensures all the shutdown hooks and finalizers are run JavaLangShutdown.exit(0); } JniFunctions.epilogue(anchor); } /** * ATTENTION: this signature must match 'VmThreadAttachMethod' in "com.oracle.max.vm.native/substrate/threads.h". * * @param stackBase the lowest address (inclusive) of the stack (i.e. the stack memory range is {@code [stackBase .. stackEnd)}) * @param stackEnd the highest address (exclusive) of the stack (i.e. the stack memory range is {@code [stackBase .. stackEnd)}) * @param tla the address of a thread locals area */ @VM_ENTRY_POINT private static int attach( Pointer nameCString, JniHandle groupHandle, boolean daemon, Pointer stackBase, Pointer stackEnd, Pointer tla) { // Enable safepoints: Pointer anchor = JniFunctions.prologue(JNI_ENV.addressIn(ETLA.load(tla))); VmThread thread = VmThread.current(); // Create next thread-for-attach FatalError.check(threadForAttach.get() == null, "thread-for-attach should be null"); try { VmThread newThread = VmThreadFactory.create(null); synchronized (VmThreadMap.THREAD_LOCK) { VmThreadMap.addPreallocatedThread(newThread); } threadForAttach.set(newThread); } catch (OutOfMemoryError oome) { } // Synchronization can only be performed on this thread after the above two // statements have been executed. try { String name = nameCString.isZero() ? null : CString.utf8ToJava(nameCString); ThreadGroup group = (ThreadGroup) groupHandle.unhand(); if (group == null) { group = mainThread.javaThread.getThreadGroup(); } JDK_java_lang_Thread.createThreadForAttach(thread, name, group, daemon); thread.initializationComplete(); thread.traceThreadAfterInitialization(stackBase, stackEnd); return JniFunctions.JNI_OK; } catch (OutOfMemoryError oome) { return JniFunctions.JNI_ENOMEM; } catch (Throwable throwable) { throwable.printStackTrace(Log.out); return JniFunctions.JNI_ERR; } finally { JniFunctions.epilogue(anchor); } } @INSPECTED @NEVER_INLINE private static void detached() { } /** * Cleans up a thread that is terminating. * * This method is called from the destructor * function (i.e. 'threadLocalsBlock_destroy()' in threadLocals.c) associated with the * key used to access the thread specifics of the native thread. This function will be * called for both threads created by the VM as well as threads attached to the VM * allowing a single mechanism to be used for both types of threads. * * ATTENTION: this signature must match 'VmThreadDetachMethod' in "com.oracle.max.vm.native/substrate/threads.h". */ @VM_ENTRY_POINT private static void detach(Pointer tla) { // Disable safepoints: Pointer anchor = JniFunctions.prologue(JNI_ENV.addressIn(DTLA.load(tla))); VmThread thread = VmThread.current(); // Report and clear any pending JNI exception Throwable jniException = thread.jniException(); if (jniException != null) { thread.setJniException(null); System.err.print("Exception in thread \"" + thread.getName() + "\" "); jniException.printStackTrace(); } thread.terminationPending(); synchronized (thread.javaThread) { // Must set TERMINATED before the notify in case a joiner is already waiting thread.state = Thread.State.TERMINATED; thread.javaThread.notifyAll(); } thread.traceThreadAfterTermination(); // GC may now reclaim or prepare any of its resources before the thread vanishes forever. vmConfig().heapScheme().notifyCurrentThreadDetach(); synchronized (VmThreadMap.THREAD_LOCK) { // It is the monitor scheme's responsibility to ensure that this thread isn't // reset to RUNNABLE if it blocks here. VmThreadMap.ACTIVE.removeThreadLocals(thread); } if (MaxineVM.isDebug()) { detached(); } // Monitor acquisition after point this MUST NOT HAPPEN as it may reset state to RUNNABLE thread.nativeThread = Address.zero(); thread.tla = Pointer.zero(); thread.id = -1; thread.waitingCondition = null; thread.suspendMonitor.destroy(); JniFunctions.epilogue(anchor); } static class JavaLangShutdown { @ALIAS(declaringClassName = "java.lang.Shutdown", name = "exit") static native void exit(int code); } public static VmThread fromJniEnv(Pointer jniEnv) { final Pointer tla = jniEnv.minus(JNI_ENV.offset); return fromTLA(tla); } @INLINE public static VmThread fromTLA(Pointer tla) { if (MaxineVM.isHosted()) { return mainThread; } return UnsafeCast.asVmThread(VM_THREAD.loadRef(tla).toJava()); } public static void yield() { nativeYield(); } private static native void nativeYield(); private static native void nativeSetPriority(Word nativeThread, int newPriority); /** * This exists for the benefit of the primordial thread. * * The primordial thread cannot (currently) safely call JNI functions because it is not a "real" Java thread. This * is a workaround - obviously it would be better to find a way to relax this restriction. * * @param numberOfMilliSeconds */ public static void nonJniSleep(long numberOfMilliSeconds) { nonJniNativeSleep(numberOfMilliSeconds); } @C_FUNCTION private static native void nonJniNativeSleep(long numberOfMilliSeconds); private static native boolean nativeSleep(long numberOfMilliSeconds); public static void sleep(long millis) throws InterruptedException { final VmThread current = current(); State oldState = current.state(); current.setState(State.TIMED_WAITING); boolean interrupted = current.sleep0(millis); current.setState(oldState); if (interrupted) { current.interrupted = false; throw new InterruptedException(); } } public static native void nativeInterrupt(Word nativeThread); /** * Number of yellow zone pages used for detecting recoverable stack overflow. * This space must also accommodate the execution of stack over handling from * the trap stub all the way until the stack is unwound for the {@link StackOverflowError} * exception. If this is too little, the red stack zone will be hit. * The maximum space required is very dependent on the compiler but 2 pages * seems to be enough on AMD64. */ public static final int YELLOW_ZONE_PAGES = 2; /** * Number of red zone pages used for detecting unrecoverable stack overflow. */ public static final int RED_ZONE_PAGES = 1; /** * Gets the size of the yellow stack guard zone. */ public static int yellowZoneSize() { return YELLOW_ZONE_PAGES * platform().pageSize; } /** * Gets the size of the red stack guard zone. */ public static int redZoneSize() { return RED_ZONE_PAGES * platform().pageSize; } private static Address traceRegion(String label, Address areaStart, Address regionStart, Address regionEnd, Address lastRegionStart, int areaSize) { return traceRegion(label, areaStart, regionStart, regionEnd.minus(regionStart).toInt(), lastRegionStart, areaSize); } private static Address traceRegion(String label, Address areaStart, Address regionStart, int regionSize, Address lastRegionStart, int areaSize) { FatalError.check(lastRegionStart.isZero() || regionStart.lessEqual(lastRegionStart), "Overlapping regions"); if (regionSize > 0) { final Address regionEnd = regionStart.plus(regionSize); FatalError.check(lastRegionStart.isZero() || regionEnd.lessEqual(lastRegionStart), "Overlapping regions"); final int startOffset = regionStart.minus(areaStart).toInt(); final int endOffset = startOffset + regionSize; if (lastRegionStart.isZero() || !lastRegionStart.equals(regionEnd)) { Log.print(" +--------- "); Log.print(regionEnd); Log.print(" ["); Log.print(endOffset >= 0 ? "+" : ""); Log.print(endOffset); Log.println("]"); } Log.println(" |"); Log.print(" | "); Log.print(label); Log.print(" ["); Log.print(regionSize); Log.print(" bytes, "); Log.print(((float) regionSize * 100) / areaSize); Log.println("%]"); Log.println(" |"); Log.print(" +--------- "); Log.print(regionStart); Log.print(" ["); Log.print(startOffset >= 0 ? "+" : ""); Log.print(startOffset); Log.println("]"); } return regionStart; } /** * Creates an unbound VmThread that will be bound later. */ public VmThread() { uuid = nextUUid.getAndIncrement(); } /** * Create a VmThread. * * @param javaThread if not {@code null}, then the created VmThread is bound to {@code javaThread} */ public VmThread(Thread javaThread) { this(); if (javaThread != null) { setJavaThread(javaThread, JDK_java_lang_Thread.getName(javaThread)); } } /** * Gets a preallocated, thread local object that can be used to walk the frames in this thread's stack. * * <b>This must only be used when {@linkplain Throw#raise(Throwable, Pointer, Pointer, CodePointer)} throwing an exception}. * Allocation must not occur in this context.</b> * * @param throwable the exception being raised. If {@code null}, then a StackOverflowError is about to be raised. */ public final VmStackFrameWalker unwindingStackFrameWalker(Throwable throwable) { FatalError.check(stackFrameWalker != null, "Thread-local stack frame walker cannot be null for a running thread"); VmStackFrameWalker sfw = stackFrameWalker; if (sfw.isInUse()) { if (throwable != null) { Log.println("exception thrown while raising another exception or reference map preparing"); } else { Log.println("stack overflow occurred while raising another exception or reference map preparing"); Log.println("may need to increase safetyMargin in VmThread.checkYellowZoneForRaisingException()"); } if (!sfw.isDumpingFatalStackTrace()) { sfw.reset(); sfw.setIsDumpingFatalStackTrace(true); Throw.stackDumpWithException(throwable); sfw.setIsDumpingFatalStackTrace(false); } FatalError.unexpected("exception thrown while raising another exception"); } return stackFrameWalker; } /** * Gets a preallocated, thread local object that can be used to walk the frames in this thread's stack. * * <b>This must only be used when {@linkplain StackReferenceMapPreparer preparing} a stack reference map. * Allocation must not occur in this context.</b> */ public final VmStackFrameWalker referenceMapPreparingStackFrameWalker() { FatalError.check(stackFrameWalker != null, "Thread-local stack frame walker cannot be null for a running thread"); return stackFrameWalker; } /** * Gets a preallocated, thread local object that can be used to walk the frames in this thread's stack * and check their reference maps. */ public final VmStackFrameWalker referenceMapVerifyingStackFrameWalker() { FatalError.check(stackDumpStackFrameWalker != null, "Thread-local stack frame walker cannot be null for a running thread"); return stackDumpStackFrameWalker; } /** * Gets a preallocated, thread local object that can be used to log a stack dump without incurring any allocation. */ public final VmStackFrameWalker stackDumpStackFrameWalker() { FatalError.check(stackDumpStackFrameWalker != null, "Thread-local stack frame walker cannot be null for a running thread"); return stackDumpStackFrameWalker; } /** * Gets a dynamically allocated, thread local object that can be used by the sample profiler without incurring any allocation. */ public final VmStackFrameWalker samplingProfilerStackFrameWalker() { if (samplingProfilerStackFrameWalker == null) { samplingProfilerStackFrameWalker = new VmStackFrameWalker(ETLA.load(VmThread.currentTLA())); } return samplingProfilerStackFrameWalker; } /** * Gets the thread-local object used to prepare the reference map for this stack's thread during garbage collection. */ public final StackReferenceMapPreparer stackReferenceMapPreparer() { return stackReferenceMapPreparer; } /** * Gets the thread-local object used to verify the reference map for this stack's thread. */ public final StackReferenceMapPreparer stackReferenceMapVerifier() { return stackReferenceMapVerifier; } public CompactReferenceMapInterpreter compactReferenceMapInterpreter() { if (MaxineVM.isHosted()) { return HOSTED_COMPACT_REFERENCE_MAP_INTERPRETER.get(); } return compactReferenceMapInterpreter; } public final Thread.State state() { return state; } public final void setState(Thread.State state) { this.state = state; } public final Thread javaThread() { return javaThread; } public final Word nativeThread() { return nativeThread; } /** * Gets the identifier used to identify this thread in the {@linkplain VmThreadMap thread map}. * A thread that has not been added to the thread map, will have an identifier of 0 and * a thread that has been terminated and removed from the map will have an identifier * of -1. * * This value is identical to the {@link VmThreadLocal#ID} value of a running thread. * * N.B. This value is only guaranteed unique among the currently active threads, @see #uuid */ public final int id() { return id; } /** * @see #id() */ final void setID(int id) { this.id = id; } public final String getName() { return name; } public final void setName(String name) { this.name = name; } @INLINE public final ConditionVariable waitingCondition() { return waitingCondition; } /** * Sets the interrupted status of this thread to true. */ public final void setInterrupted() { this.interrupted = true; } /** * Determines if this is the single {@link VmOperationThread}. */ public final boolean isVmOperationThread() { return vmOperationThread == this; } public final boolean isJVMTIAgentThread() { return jvmtiAgent; } public final void setAsJVMTIAgentThread() { jvmtiAgent = true; } /** * Bind the given {@code Thread} to this VmThread. * @param javaThread thread to be bound * @param name the name of the thread */ public final VmThread setJavaThread(Thread javaThread, String name) { this.javaThread = javaThread; this.name = name; return this; } private void traceThreadAfterInitialization(Pointer stackBase, Pointer stackEnd) { if (TraceThreads) { final boolean lockDisabledSafepoints = Log.lock(); Log.print("Initialization completed for thread[id="); Log.print(id); Log.print(", name=\""); Log.print(name); Log.print("\", native id="); Log.print(nativeThread); Log.println("]:"); Log.println("Stack layout:"); Address lastRegionStart = Address.zero(); final int stackSize = stackEnd.minus(stackBase).toInt(); final Pointer stackPointer = VMRegister.getCpuStackPointer(); lastRegionStart = traceRegion("OS thread specific data and native frames", stackBase, stackPointer, stackEnd, lastRegionStart, stackSize); lastRegionStart = traceRegion("Frame of Java methods, native stubs and native functions", stackBase, yellowZoneEnd(), stackPointer, lastRegionStart, stackSize); lastRegionStart = traceRegion("Stack yellow zone", stackBase, yellowZone, yellowZoneEnd(), lastRegionStart, stackSize); lastRegionStart = traceRegion("Stack red zone", stackBase, redZone(), redZoneEnd(), lastRegionStart, stackSize); lastRegionStart = Address.zero(); Address ntl = NATIVE_THREAD_LOCALS.load(currentTLA()); Pointer ttla = TTLA.load(currentTLA()); Pointer etla = ETLA.load(currentTLA()); Pointer dtla = DTLA.load(currentTLA()); Pointer tlb = ttla.roundedDownBy(platform().pageSize); Address refMap = STACK_REFERENCE_MAP.load(currentTLA()); Address tlbEnd = refMap.plus(STACK_REFERENCE_MAP_SIZE.load(currentTLA()).asAddress()); int tlbSize = tlbEnd.minus(tlb).toInt(); Log.println(); Log.println("Thread locals block layout:"); lastRegionStart = traceRegion("reference map", tlb, refMap, tlbEnd, lastRegionStart, tlbSize); lastRegionStart = traceRegion("native thread locals", tlb, ntl, refMap, lastRegionStart, tlbSize); lastRegionStart = traceRegion("safepoints-disabled TLA", tlb, dtla, ntl, lastRegionStart, tlbSize); lastRegionStart = traceRegion("safepoints-enabled TLA", tlb, etla, dtla, lastRegionStart, tlbSize); lastRegionStart = traceRegion("safepoints-triggered TLA", tlb, ttla, etla, lastRegionStart, tlbSize); lastRegionStart = traceRegion("unmapped page", tlb, tlb, ttla, lastRegionStart, tlbSize); Log.println(); Log.printThreadLocals(tla, true); Log.unlock(lockDisabledSafepoints); } } private void traceThreadForUncaughtException(Throwable throwable) { if (TraceThreads) { final boolean lockDisabledSafepoints = Log.lock(); Log.print("VmThread[id="); Log.print(id); Log.print(", name=\""); Log.print(name); Log.print("] Uncaught exception of type "); Log.println(ObjectAccess.readClassActor(throwable).name); Log.unlock(lockDisabledSafepoints); } } private void traceThreadAfterTermination() { if (TraceThreads) { final boolean lockDisabledSafepoints = Log.lock(); Log.print("Thread terminated [id="); Log.print(id); Log.print(", name=\""); Log.print(name); Log.println("\"]"); Log.unlock(lockDisabledSafepoints); } } /** * The address of the safepoint-enabled TLA for this thread. */ @INLINE public final Pointer tla() { return tla; } public final JniHandle createLocalHandle(Object object) { if (jniHandles == null) { jniHandles = new JniHandles(); } return JniHandles.createLocalHandle(jniHandles, object); } /** * Gets the JNI handles for this thread. This should only be called when the caller expects * the JNI handles to have been allocated for this thread. */ @INLINE public final JniHandles jniHandles() { return jniHandles; } /** * Return the "top" (i.e. current size) of JNI handles for this thread * * NOTE: This code is called from a {@linkplain NativeStubGenerator JNI stub} * * @return -1 if the JNI handles have not been allocated for this thread */ @INLINE public final int jniHandlesTop() { return jniHandles == null ? -1 : jniHandles.top(); } /** * Resets the "top" (i.e. current size) of JNI handles for this thread * * NOTE: This code is called from a {@linkplain NativeStubGenerator JNI stub} * * @param newTop the value to which the top should be reset or -1 if no resetting is to occur */ @INLINE public final void resetJniHandlesTop(int newTop) { if (newTop != -1) { jniHandles.resetTop(newTop); } } /** * Gets the JNI handles for this thread, creating them first if necessary. */ @INLINE public final JniHandles makeJniHandles() { if (jniHandles == null) { jniHandles = new JniHandles(); } return jniHandles; } /** * Sets or clears the exception to be thrown once this thread returns from the native function most recently entered * via a {@linkplain NativeStubGenerator native stub}. This mechanism is used to propagate exceptions through native * frames and should not be used for any other purpose. * * @param exception if non-null, this exception will be raised upon returning from the closest native function on * this thread's stack. Otherwise, the pending JNI exception is cleared. */ public final void setJniException(Throwable exception) { this.jniException = exception; } /** * Gets the exception thrown by the most recently called JNI function. This will be re-thrown once this * thread returns from the native function most recently entered via a {@linkplain NativeStubGenerator native stub}. * * @return the exception that will be raised upon returning from the closest native function on this thread's stack * or null if there is no such pending exception */ public final Throwable jniException() { return jniException; } /** * Raises the pending JNI exception on this thread (if any) and clears it. * Called from a {@linkplain NativeStubGenerator JNI stub} after a native function returns. */ public final void throwJniException() throws Throwable { final Throwable pendingException = this.jniException; if (pendingException != null) { this.jniException = null; throw pendingException; } } /** * Gets the number of VM operations {@linkplain VmOperationThread#submit(VmOperation) submitted} * by this thread for execution that have not yet completed. */ public final int pendingOperations() { return pendingOperations; } /** * Increments the {@linkplain #pendingOperations() pending operations} count. */ public final void incrementPendingOperations() { ++pendingOperations; } /** * Decrements the {@linkplain #pendingOperations() pending operations} count. */ public void decrementPendingOperations() { --pendingOperations; FatalError.check(pendingOperations >= 0, "pendingOperations should never be negative"); } private static class ThreadGroupAlias { @INTRINSIC(UNSAFE_CAST) public static native ThreadGroupAlias asThreadGroupAlias(Object object); @ALIAS(declaringClass = ThreadGroup.class) private native void add(Thread t); @ALIAS(declaringClass = ThreadGroup.class) int nUnstartedThreads; } /** * Start a VM boot image system group thread. * System threads created in this class are {@code ThreadGroup.addUnstarted} when created in the boot image. * This method causes them to actually be added to the thread group data structure. */ public final void startVmSystemThread() { ThreadGroupAlias threadGroupAlias = ThreadGroupAlias.asThreadGroupAlias(systemThreadGroup); if (this == vmOperationThread) { // hidden threadGroupAlias.nUnstartedThreads--; } else { threadGroupAlias.add(javaThread); } start0(); } /** * Causes this thread to begin execution. */ public final void start0() { assert state == Thread.State.NEW; state = Thread.State.RUNNABLE; Thread_vmThread.setObject(javaThread, this); suspendMonitor.init(); VmThreadMap.ACTIVE.startThread(this, STACK_SIZE_OPTION.getValue().alignUp(platform().pageSize).asSize(), javaThread.getPriority()); } public final boolean isInterrupted(boolean clearInterrupted) { final boolean interrupted = this.interrupted; if (clearInterrupted) { this.interrupted = false; } return interrupted; } /** * This can be called in two contexts: 1. During the creation of a thread (i.e. during the Thread constructor) 2. * During the execution of a thread. In case 1 the native thread does not exist as it is not created until the * {@link #start0()} method is called, so there is nothing to do (the priority is passed down by start0). In case 2 * we call a native function to change the priority. * * @param newPriority the new thread priority */ public final void setPriority0(int newPriority) { if (nativeThread.isZero()) { // native thread does not exist yet } else { nativeSetPriority(nativeThread, newPriority); } } /* * use protected member method so that Maxine VE's SchedThread is able to implement its own sleep method */ protected boolean sleep0(long numberOfMilliSeconds) { return VmThread.nativeSleep(numberOfMilliSeconds); } public final void stop0(Object throwable) { terminationCause = (Throwable) throwable; FatalError.unimplemented(); Throw.raise(this); // not a Throwable => uncatchable - see 'run()' above } public final void suspend0() { new VmOperation.SuspendThreadSet(this).submit(); } public final void resume0() { new VmOperation.ResumeThreadSet(this).submit(); } public final void interrupt0() { interrupted = true; if (!nativeThread.isZero()) { // Set to true as default. Will be cleared on this VmThread's // native thread if an InterruptedException is thrown after the // interruption. nativeInterrupt(nativeThread); } } @Override public String toString() { return "VM" + javaThread; } /** * Gets the address of the stack zone used to detect recoverable stack overflow. * This zone covers the range {@code [yellowZone() .. yellowZoneEnd())}. */ public final Address yellowZone() { return yellowZone; } /** * Gets the end address of the stack zone used to detect recoverable stack overflow. * This zone covers the range {@code [yellowZone() .. yellowZoneEnd())}. */ public final Address yellowZoneEnd() { return yellowZone.plus(yellowZoneSize()); } /** * Gets the address of the stack zone used to detect unrecoverable stack overflow. * This zone is immediately below the {@linkplain #yellowZone yellow} zone and covers * the range {@code [redZone() .. redZoneEnd())}. */ public final Address redZone() { return yellowZone.minus(redZoneSize()); } /** * Gets the end address of the stack zone used to detect unrecoverable stack overflow. * This zone is immediately below the {@linkplain #yellowZone yellow} zone and covers * the range {@code [redZone() .. redZoneEnd())}. */ public final Address redZoneEnd() { return yellowZone; } /** * This method is called when the VmThread initialization is complete. * A subclass can override this method to do whatever subclass-specific * initialization that depends on that invariant. */ protected void initializationComplete() { } /** * This method is called when the VmThread termination is pending. * A subclass can override this method to do whatever subclass-specific * termination that depends on that invariant. * N.B. In order for this callback to be usable, the state is not * set to TERMINATED until after this method returns. */ protected void terminationPending() { } /** * This method parks the current thread according to the semantics of {@link Unsafe#park(boolean, long)}. * @throws InterruptedException */ public final void park() throws InterruptedException { synchronized (this) { if (parkState == 1) { parkState = 0; } else { parkState = 2; wait(); } } } /** * This method parks the current thread according to the semantics of {@link Unsafe#park(boolean, long)}. * @throws InterruptedException */ public final void park(long wait) throws InterruptedException { synchronized (this) { if (parkState == 1) { parkState = 0; } else { parkState = 2; wait(wait / 1000000, (int) (wait % 1000000)); } } } /** * This method unparks the current thread according to the semantics of {@link Unsafe#unpark(Object)}. */ public final void unpark() { synchronized (this) { parkState = 1; notifyAll(); } } public final void pushPrivilegedElement(ClassActor classActor, long frameId, AccessControlContext context) { privilegedStackTop = new PrivilegedElement(classActor, frameId, context, privilegedStackTop); } public final void popPrivilegedElement() { privilegedStackTop = privilegedStackTop.next; } public final PrivilegedElement getTopPrivilegedElement() { return privilegedStackTop; } public static class PrivilegedElement { private PrivilegedElement next; public ClassActor classActor; public long frameId; public AccessControlContext context; PrivilegedElement(ClassActor classActor, long frameId, AccessControlContext context, PrivilegedElement next) { this.classActor = classActor; this.frameId = frameId; this.context = context; this.next = next; } } }