/* * @(#)LogicalVM.java 1.8 06/10/27 * * Copyright 1990-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program 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 program 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 at /legal/license.txt). * * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. * */ package sun.misc; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.InvocationTargetException; import java.util.Vector; /* * Comments (TODO) on the current Logical VM implementation: * * - Still under development. * - Java isolation API is still under discussion under JSR-121. * Hopefully this implementation will cover major requirements. * - A parent LVM can die before its children, except the main LVM * (the main LVM keeps itself alive until all the child LVMs get * terminated). * - Killing a parent LVM doesn't cause its children to die, except * the main LVM (if we terminate the main LVM, all go die). * - No security checking done yet (no SecurityException). * - No state management (no "IllegalLogicalVMStateException" thrown) * - Uses remote (asynchronous) exception mechanism (ThreadDeath) when * we terminate an LVM. Enabling and disabling remote exceptions * to protect key data structures are not completely revised yet. * - We also need to protect some of important finalizers (like one of * FileOutputStream) so that it doesn't get terminated in the middle * before freeing native resource by remote exceptions. * - Supposed to interrupt blocking operations performed by threads when * we terminate an LVM, but this part is not implemented yet. * We need to interrupt blocking I/O, monitor operations (including * monitor enter), etc. * - Monitors for Class and String object are supposed to be replicated * per LVM, but not implemented yet. CVM code assumes one Class object * per a class, so, instead of having multiple replicated Class objects * per LVM, we replicate its monitor only. There may need some other * consideration so as for a Class object not to leak any objects from * one LVM to another. As for String object, although it is immutable, * the interned string support is making the situation difficult. * Instead of going down the route to replicate the interned string * table in the VM, we just share the object and replicate its monitor * per LVM. * - None of JVMTI, JVMPI, RMI nor Personal[Basis]Profile work has done yet. */ /* * Implementation of a handle class of Logical VM */ public final class LogicalVM { private Isolate isolate; private LogicalVM(Isolate isolate) { this.isolate = isolate; } public LogicalVM(String className, String[] args, String n) { isolate = new Isolate(className, args, n); } public static LogicalVM getCurrentLogicalVM() { return new LogicalVM(Isolate.getCurrent()); } public LogicalVM getParent() { return new LogicalVM(isolate.getParent()); } public String getName() { // A String objects is immutable and shared (we use per-LVM // String object lock). return isolate.getName(); } public void start() { isolate.start(); } public void join() throws InterruptedException { isolate.join(); } public void join(long millis) throws InterruptedException { isolate.join(millis); } public void join(long millis, int nanos) throws InterruptedException { isolate.join(millis, nanos); } public void exit(int exitCode) { isolate.exit(exitCode); } public void halt(int exitCode) { isolate.halt(exitCode); } public boolean equals(Object obj) { if (obj instanceof LogicalVM) { LogicalVM other = (LogicalVM)obj; return (this.isolate == other.isolate); } return false; } } /* * Actual implementation of Logical VM */ final class Isolate { // Used by the native code to store pointer to per-Logical VM // info data structure. private int context = 0; /* TODO: What about 64 bit support. */ private String name; private Isolate parent; private Isolate main; private Thread launcher; private RequestHandler requestHandler; private String className; private String[] args; // Special attention needs to be paid for use of static fields private static Isolate current; private native void initContext(Thread initThread); private native void destroyContext(); private static native void switchContext(Isolate target); // Worm hole to invoke java.lang.Shutdown.waitAllUserThreads...() private static native void waitAllUserThreadsExitAndShutdown(); // Worm hole to invoke java.lang.ref.Finalizer.runAllFinalizers() private static native void runAllFinalizersOfSystemClass(); /* * Private constructor for the main (primordial) Logical VM. * This constructor will be invoked from native code during the JVM * start up and before completion of the JVM initialization. */ private Isolate() { // We are still in the middle of main LVM initialization. // Do not perform anything fancy (like starting a new thread). name = "main"; parent = null; main = this; current = this; } public Isolate(String className, String[] args, String name) { this.name = name; parent = getCurrent(); main = parent.main; this.className = className; // Make duplicates of arguments so as not to share mutable objects // across LVMs. String objects are immutable and shared. this.args = new String[args.length]; System.arraycopy(args, 0, this.args, 0, args.length); launcher = new Launcher("LVM " + name + " start-up thread"); } public static Isolate getCurrent() { return current; } public Isolate getParent() { return parent; } public String getName() { return name; } public void start() { // Start this LVM form the context of the main LVM. // This effectively keeps the main LVM alive until this LVM // terminates. main.remoteRequest(launcher); } public void join() throws InterruptedException { launcher.join(); } public void join(long millis) throws InterruptedException { launcher.join(millis); } public void join(long millis, int nanos) throws InterruptedException { launcher.join(millis, nanos); } public void exit(int exitCode) { remoteHaltOrExit(this, false, exitCode); } public void halt(int exitCode) { remoteHaltOrExit(this, true, exitCode); } private static void remoteHaltOrExit(Isolate target, final boolean isHalt, final int exitCode) { Isolate initiatingLVM = getCurrent(); if (target != initiatingLVM) { target.remoteRequest(new Runnable() { public void run() { haltOrExit(isHalt, exitCode); } }); } else { haltOrExit(isHalt, exitCode); } } private static void haltOrExit(boolean isHalt, int exitCode) { Runtime rt = Runtime.getRuntime(); if (isHalt) { rt.halt(exitCode); } else { rt.exit(exitCode); } } private void startRequestHandler() { requestHandler = new RequestHandler("LVM " + name +" request handler"); requestHandler.start(); } private void startMainRequestHandler() { startRequestHandler(); // The following line is for main LVM to make join() // implementation simpler. Anyway, the main LVM never terminates // while any of Java threads is running. So, it doesn't matter. launcher = requestHandler; } private void remoteRequest(Runnable proc) { requestHandler.request(proc); } /* * Helper class for executing a piece of code in this LVM's context. */ private class RequestHandler extends Thread { private Vector queue = new Vector(); public RequestHandler(String name) { super(name); setDaemon(true); } public synchronized void run() { setPriority(MAX_PRIORITY - 2); current = Isolate.this; while (!ThreadRegistry.exitRequested()) { if (queue.size() == 0) { try { wait(); } catch (InterruptedException e) { continue; } } Thread handler; Object proc = queue.remove(0); if (proc instanceof Thread) { handler = (Thread)proc; } else { handler = new Thread((Runnable)proc); } handler.setDaemon(false); handler.start(); } } public synchronized void request(Runnable proc) { queue.add(proc); notify(); } } /* * Helper class for launching an application in a new Logical VM. */ private class Launcher extends Thread { Launcher(String name) { super(name); } public void run() { // Don't allow any remote exception all the way CVM.disableRemoteExceptions(); // Initializes a new Logical VM context and switch to it. initContext(this); current = Isolate.this; setPriority(Thread.NORM_PRIORITY); startRequestHandler(); Throwable uncaughtException = null; try { // Obtain the main ThreadGroup. There should only be // one sub-group yet because of initContext(). ThreadGroup sys = Thread.currentThread().getThreadGroup(); ThreadGroup[] list = new ThreadGroup[1]; sys.enumerate(list, false); ThreadGroup mainGroup = list[0]; // Start-up the main thread of this LVM Thread main = new Thread(mainGroup, new MainExec(), "main"); main.start(); // Shutdown procedure for this LVM waitAllUserThreadsExitAndShutdown(); ThreadRegistry.waitAllSystemThreadsExit(); runAllFinalizersOfSystemClass(); } catch (Throwable t) { uncaughtException = t; } // Switch the context back to the main LVM so that this // thread gets removed from the main LVM's ThreadRegistry. // In this way, we keep the main LVM alive while any of // child LVMs is running. switchContext(main); // This is the last thread of this LVM Isolate.this.destroyContext(); // Rethrow the uncaughtException and let the caller, // Thread.startup(), deal with it. if (uncaughtException != null) { CVM.throwLocalException(uncaughtException); } } } private class MainExec implements Runnable { // Use Runnable so that a plain Thread object is used to run // the main method of the new LVM. public void run() { Throwable uncaughtException = null; try { // Load the target class using the system class loader. // This makes the main class loading procedure same // as the ordinary JVM start-up. ClassLoader scl = ClassLoader.getSystemClassLoader(); Class cls = scl.loadClass(className); Class[] argClses = {String[].class}; // Returns public method only Method mainMethod = cls.getMethod("main", argClses); if (mainMethod.getReturnType() != Void.TYPE) { throw new NoSuchMethodException("return type is not void"); } if (!Modifier.isStatic(mainMethod.getModifiers())) { throw new IllegalAccessException("main is not static"); } Object[] argObjs = {args}; mainMethod.invoke(null, argObjs); } catch (InvocationTargetException ite) { uncaughtException = ite.getTargetException(); } catch (Throwable t) { uncaughtException = t; } // Rethrow the uncaughtException and let the caller, // Thread.startup(), deal with it. if (uncaughtException != null) { CVM.throwLocalException(uncaughtException); } } } private static void dprintln(String s) { if (CVM.checkDebugFlags(CVM.DEBUGFLAG_TRACE_LVM) != 0) { System.err.println(s); } } }