Java 源码 - java.lang.Runtime

503 阅读11分钟

前言

Runtime类是一个与JVM运行时环境有关的Singleton类,有以下几个值得注意的地方:

  • Runtime.getRuntime()可以取得当前JVM的运行时环境,这也是在Java中唯一得到运行时环境的方法。
  • Runtime上其他大部分的方法都是实例方法,也就是说每次进行运行时调用时都要用到getRuntime方法。
  • Runtime中的exit方法是退出当前JVM的方法,估计也是唯一的。System类中的exit实际上也是通过调用Runtime.exit()来退出JVM的。

Java对Runtime返回值的一般规则,0代表正常退出,非0代表异常中止,这只是Java的规则,在各个操作系统中总会发生一些小的混淆。

  • Runtime.addShutdownHook()方法可以注册一个hook在JVM执行shutdown的过程中,方法的参数只要是一个初始化过但是没有执行的Thread实例就可以。(注意,Java中的Thread都是执行过了就不值钱的哦)

说到addShutdownHook这个方法就要说一下JVM运行环境是在什么情况下shutdown或者abort的。

  1. Shutdown:当最后一个非精灵进程退出或者收到了一个用户中断信号、用户登出、系统shutdown、Runtime的exit方法被调用时JVM会启动shutdown的过程,在这个过程开始后,他会并行启动所有登记的shutdown hook(注意是并行启动,这就需要线程安全和防止死锁)。当shutdown过程启动后,只有通过调用halt方法才能中止shutdown的过程并退出JVM。
  2. Abort: abort退出时JVM就是停止运行但并不一定进行shutdown。这只有JVM在遇到SIGKILL信号或者windows中止进程的信号、本地方法发生类似于访问非法地址一类的内部错误时会出现。这种情况下并不能保证shutdown hook是否被执行。
  • Runtime.exec()方法的所有重载。这里要注意的是:

public Process exec(String[] cmdarray, String[] envp, File dir);

这个方法中cmdArray是一个执行的命令和参数的字符串数组,数组的第一个元素是要执行的命令往后依次都是命令的参数,envp中是name=value形式的环境变量设置,如果子进程要继承当前进程的环境时是null。

JDK解释

 * Every Java application has a single instance of class
 * <code>Runtime</code> that allows the application to interface with
 * the environment in which the application is running. The current
 * runtime can be obtained from the <code>getRuntime</code> method.
 * <p>
 * An application cannot create its own instance of this class.

静态变量

private static Runtime currentRuntime = new Runtime();

构造函数

    /** Don't let anyone else instantiate this class */
    private Runtime() {}

基本方法

getRuntime()

    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class <code>Runtime</code> are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the <code>Runtime</code> object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }

exit()

    /**
     * Terminates the currently running Java virtual machine by initiating its
     * shutdown sequence.  This method never returns normally.  The argument
     * serves as a status code; by convention, a nonzero status code indicates
     * abnormal termination.
    **/    
    public void exit(int status) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkExit(status);
        }
        Shutdown.exit(status);
    }

addShutdownHook()

    //Registers a new virtual-machine shutdown hook.
    public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }

removeShutdownHook()

    //De-registers a previously-registered virtual-machine shutdown hook


    public boolean removeShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        return ApplicationShutdownHooks.remove(hook);
    }

halt()

    /**
     * Forcibly terminates the currently running Java virtual machine.  This
     * method never returns normally.
    */
    public void halt(int status) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkExit(status);
        }
        Shutdown.halt(status);
    }

runFinalizersOnExit()

    /**
     * Enable or disable finalization on exit; doing so specifies that the
     * finalizers of all objects that have finalizers that have not yet been
     * automatically invoked are to be run before the Java runtime exits.
     * By default, finalization on exit is disabled.
    **/
    @Deprecated
    public static void runFinalizersOnExit(boolean value) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            try {
                security.checkExit(0);
            } catch (SecurityException e) {
                throw new SecurityException("runFinalizersOnExit");
            }
        }
        Shutdown.setRunFinalizersOnExit(value);
    }

exec()

    //Executes the specified string command in a separate process.
    public Process exec(String command) throws IOException {
        return exec(command, null, null);
    }

    /**
     * Executes the specified string command in a separate process with the
     * specified environment.
    **/
    public Process exec(String command, String[] envp) throws IOException {
        return exec(command, envp, null);
    }

    /**
     * Executes the specified string command in a separate process with the
     * specified environment and working directory.
    **/
    public Process exec(String command, String[] envp, File dir)
        throws IOException {
        if (command.length() == 0)
            throw new IllegalArgumentException("Empty command");

        StringTokenizer st = new StringTokenizer(command);
        String[] cmdarray = new String[st.countTokens()];
        for (int i = 0; st.hasMoreTokens(); i++)
            cmdarray[i] = st.nextToken();
        return exec(cmdarray, envp, dir);
    }

    //Executes the specified command and arguments in a separate process.    public Process exec(String cmdarray[]) throws IOException {
        return exec(cmdarray, null, null);
    }

    /**
     * Executes the specified command and arguments in a separate process
     * with the specified environment.
    **/
    public Process exec(String[] cmdarray, String[] envp) throws IOException {
        return exec(cmdarray, envp, null);
    }

    /**
     * Executes the specified command and arguments in a separate process with
     * the specified environment and working directory.
     *
     * <p>Given an array of strings <code>cmdarray</code>, representing the
     * tokens of a command line, and an array of strings <code>envp</code>,
     * representing "environment" variable settings, this method creates
     * a new process in which to execute the specified command.
     *
     * <p>This method checks that <code>cmdarray</code> is a valid operating
     * system command.  Which commands are valid is system-dependent,
     * but at the very least the command must be a non-empty list of
     * non-null strings.
    **/
   
    public Process exec(String[] cmdarray, String[] envp, File dir)
        throws IOException {
        return new ProcessBuilder(cmdarray)
            .environment(envp)
            .directory(dir)
            .start();
    }

availableProcessors()

    /**
     * Returns the number of processors available to the Java virtual machine.
     *
     * <p> This value may change during a particular invocation of the virtual
     * machine.  Applications that are sensitive to the number of available
     * processors should therefore occasionally poll this property and adjust
     * their resource usage appropriately. </p>
     *
     * @return  the maximum number of processors available to the virtual
     *          machine; never smaller than one
     */
    public native int availableProcessors();

freeMemory()

    /**
     * Returns the amount of free memory in the Java Virtual Machine.
     * Calling the
     * <code>gc</code> method may result in increasing the value returned
     * by <code>freeMemory.</code>
     *
     * @return  an approximation to the total amount of memory currently
     *          available for future allocated objects, measured in bytes.
     */
    public native long freeMemory();

totalMemory()

    /**
     * Returns the total amount of memory in the Java virtual machine.
     * The value returned by this method may vary over time, depending on
     * the host environment.
     * <p>
     * Note that the amount of memory required to hold an object of any
     * given type may be implementation-dependent.
     *
     * @return  the total amount of memory currently available for current
     *          and future objects, measured in bytes.
     */
    public native long totalMemory();

maxMemory()

    /**
     * Returns the maximum amount of memory that the Java virtual machine will
     * attempt to use.  If there is no inherent limit then the value {@link
     * java.lang.Long#MAX_VALUE} will be returned.
     *
     * @return  the maximum amount of memory that the virtual machine will
     *          attempt to use, measured in bytes
     */
    public native long maxMemory();

gc()

    /**
     * Runs the garbage collector.
     * Calling this method suggests that the Java virtual machine expend
     * effort toward recycling unused objects in order to make the memory
     * they currently occupy available for quick reuse. When control
     * returns from the method call, the virtual machine has made
     * its best effort to recycle all discarded objects.
    **/
public native void gc();

runFinalization()

    /**
     * Runs the finalization methods of any objects pending finalization.
     * Calling this method suggests that the Java virtual machine expend
     * effort toward running the <code>finalize</code> methods of objects
     * that have been found to be discarded but whose <code>finalize</code>
     * methods have not yet been run. When control returns from the
     * method call, the virtual machine has made a best effort to
     * complete all outstanding finalizations.
    **/

    public void runFinalization() {
        runFinalization0();
    }

    /* Wormhole for calling java.lang.ref.Finalizer.runFinalization */
    private static native void runFinalization0();

traceInstructions()

    /**
     * Enables/Disables tracing of instructions.
     * If the <code>boolean</code> argument is <code>true</code>, this
     * method suggests that the Java virtual machine emit debugging
     * information for each instruction in the virtual machine as it
     * is executed. The format of this information, and the file or other
     * output stream to which it is emitted, depends on the host environment.
     * The virtual machine may ignore this request if it does not support
     * this feature. The destination of the trace output is system
     * dependent.
     * <p>
     * If the <code>boolean</code> argument is <code>false</code>, this
     * method causes the virtual machine to stop performing the
     * detailed instruction trace it is performing.
     *
     * @param   on   <code>true</code> to enable instruction tracing;
     *               <code>false</code> to disable this feature.
     */
    public native void traceInstructions(boolean on);

traceMethodCalls()

    /**
     * Enables/Disables tracing of method calls.
     * If the <code>boolean</code> argument is <code>true</code>, this
     * method suggests that the Java virtual machine emit debugging
     * information for each method in the virtual machine as it is
     * called. The format of this information, and the file or other output
     * stream to which it is emitted, depends on the host environment. The
     * virtual machine may ignore this request if it does not support
     * this feature.
     * <p>
     * Calling this method with argument false suggests that the
     * virtual machine cease emitting per-call debugging information.
     *
     * @param   on   <code>true</code> to enable instruction tracing;
     *               <code>false</code> to disable this feature.
     */
    public native void traceMethodCalls(boolean on);

load()

    /**
     * Loads the native library specified by the filename argument.  The filename
     * argument must be an absolute path name.
     * (for example
     * <code>Runtime.getRuntime().load("/home/avh/lib/libX11.so");</code>).
    **/

    @CallerSensitive
    public void load(String filename) {
        load0(Reflection.getCallerClass(), filename);
    }

    synchronized void load0(Class<?> fromClass, String filename) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkLink(filename);
        }
        if (!(new File(filename).isAbsolute())) {
            throw new UnsatisfiedLinkError(
                "Expecting an absolute path of the library: " + filename);
        }
        ClassLoader.loadLibrary(fromClass, filename, true);
    }

    /**
     * Loads the native library specified by the <code>libname</code>
     * argument.  The <code>libname</code> argument must not contain any platform
     * specific prefix, file extension or path. If a native library
     * called <code>libname</code> is statically linked with the VM, then the
     * JNI_OnLoad_<code>libname</code> function exported by the library is invoked.
     * See the JNI Specification for more details.
     *
     * Otherwise, the libname argument is loaded from a system library
     * location and mapped to a native library image in an implementation-
     * dependent manner.
    **/

    @CallerSensitive
    public void loadLibrary(String libname) {
        loadLibrary0(Reflection.getCallerClass(), libname);
    }

    synchronized void loadLibrary0(Class<?> fromClass, String libname) {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkLink(libname);
        }
        if (libname.indexOf((int)File.separatorChar) != -1) {
            throw new UnsatisfiedLinkError(
    "Directory separator should not appear in library name: " + libname);
        }
        ClassLoader.loadLibrary(fromClass, libname, false);
    }

getLocalizedInputStream()

    /**
     * Creates a localized version of an input stream. This method takes
     * an <code>InputStream</code> and returns an <code>InputStream</code>
     * equivalent to the argument in all respects except that it is
     * localized: as characters in the local character set are read from
     * the stream, they are automatically converted from the local
     * character set to Unicode.
     * <p>
     * If the argument is already a localized stream, it may be returned
     * as the result.
     */
    @Deprecated
    public InputStream getLocalizedInputStream(InputStream in) {
        return in;
    }

getLocalizedOutputStream()

    /**
     * Creates a localized version of an output stream. This method
     * takes an <code>OutputStream</code> and returns an
     * <code>OutputStream</code> equivalent to the argument in all respects
     * except that it is localized: as Unicode characters are written to
     * the stream, they are automatically converted to the local
     * character set.
     * <p>
     * If the argument is already a localized stream, it may be returned
     * as the result.
     *
     * @deprecated As of JDK&nbsp;1.1, the preferred way to translate a
     * Unicode character stream into a byte stream in the local encoding is via
     * the <code>OutputStreamWriter</code>, <code>BufferedWriter</code>, and
     * <code>PrintWriter</code> classes.
     */
    @Deprecated
    public OutputStream getLocalizedOutputStream(OutputStream out) {
        return out;
    }

关于exec()的使用

不正确的调用exitValue

public class BadExecJavac {  
    public static void main(String args[]) {  
        try {  
            Runtime rt = Runtime.getRuntime();  
            Process proc = rt.exec("java");  
            int exitVal = proc.exitValue();  
            System.out.println("Process exitValue: " + exitVal);  
        } catch (Throwable t) {  
            t.printStackTrace();  
        }  
    }  
}  

输出

ava.lang.IllegalThreadStateException: process has not exited  
    at java.lang.ProcessImpl.exitValue(Native Method)  
    at BadExecJavac.main(BadExecJavac.java:26)  

错误分析

主要问题就是错误的调用了exitValue来取得外部命令的返回值。因为exitValue方法是非阻塞的,在调用这个方法时外部命令并没有返回所以引起异常。阻塞形式的方法是waitFor,它会一直等待外部命令执行完毕,然后返回执行的结果。 当你在一个Process上调用waitFor方法时,当前线程是阻塞的,如果外部命令无法执行结束,那么你的线程就会一直阻塞下去,这种意外会影响我们程序的执行。所以在我们不能判断外部命令什么时候执行完毕而我们的程序还需要继续执行的情况下,我们就应该循环的使用exitValue来取得外部命令的返回状态,并在外部命令返回时作出相应的处理。  

不正确的调用waitFor

public class BadExecJavac2 {  
    public static void main(String args[]) {  
        try {  
            Runtime rt = Runtime.getRuntime();  
            Process proc = rt.exec("javac");  
            int exitVal = proc.waitFor();  
            System.out.println("Process exitValue: " + exitVal);  
        } catch (Throwable t) {  
           t.printStackTrace();  
        }  
    }  
}  

不幸的是,这个程序也无法执行完成,它没有输出但却一直悬在那里!

错误分析

JDK文档中的解释:因为本地的系统对标准输入和输出所提供的缓冲池有效,所以错误的对标准输出快速的写入和从标准输入快速的读入都有可能造成子进程的锁,甚至死锁。 JDK仅仅说明为什么问题会发生,却并没有说明这个问题怎么解决。 

解决方法

执行完外部命令后我们要控制好Process的所有输入和输出(视情况而定),在这个例子里边因为调用的是Javac,而他在没有参数的情况下会将提示信息输出到标准出错,所以在下面的程序中我们要对此进行处理。

一种可接受的调用方式

public class MediocreExecJavac {  
    public static void main(String args[]) {  
        try {  
            Runtime rt = Runtime.getRuntime();  
            Process proc = rt.exec("javac");  
            InputStream stderr = proc.getErrorStream();  
            InputStreamReader isr = new InputStreamReader(stderr);  
            BufferedReader br = new BufferedReader(isr);  
            String line = null;  
            System.out.println("<error></error>");  
            while ((line = br.readLine()) != null)  
                System.out.println(line);  
            System.out.println("");  
            int exitVal = proc.waitFor();  
            System.out.println("Process exitValue: " + exitVal);  
        } catch (Throwable t) {  
            t.printStackTrace();  
        }  
    }  
}  

输出

<error></error>  
Usage: javac <options></options> <source files=""></source>  
  
...  
  
Process exitValue: 2  

不良好的重定向命令输出

错误的认为exec方法会接受所有你在命令行或者Shell中输入并接受的字符串。这些错误主要出现在命令作为参数的情况下,程序员错误的将所有命令行中可以输入的参数命令加入到exec中。下面的例子中就是一个程序员想重定向一个命令的输出。

public class BadWinRedirect {  
    public static void main(String args[]) {  
        try {  
            Runtime rt = Runtime.getRuntime();  
            Process proc = rt.exec("java jecho 'Hello World' > test.txt");  
            // any error message?  
            StreamGobbler errorGobbler = new 
                                  StreamGobbler(proc.getErrorStream(), "ERROR");  
            // any output?  
            StreamGobbler outputGobbler = new 
                                  StreamGobbler(proc.getInputStream(), "OUTPUT");  
            // kick them off  
            errorGobbler.start();  
            outputGobbler.start();  
            // any error???  
            int exitVal = proc.waitFor();  
            System.out.println("ExitValue: " + exitVal);  
        } catch (Throwable t) {  
            t.printStackTrace();  
        }  
    }  
}  

程序员的本意是将Hello World这个输入重订向到一个文本文件中,但是这个文件并没有生成,jecho仅仅是将命令行中的参数输出到标准输出中,用户觉得可以像dos中重定向一样将输出重定向到一个文件中,但这并不能实现,用户错误的将exec认为是一个shell解释器,但它并不是,如果你想将一个程序的输出重定向到其他的程序中,你必须用程序来实现他。可用java.io中的包

良好的重定向输出示例

public class StreamGobbler extends Thread {  
    InputStream is;  
    String      type;  
    OutputStream os;  
    StreamGobbler(InputStream is, String type) {  
        this(is, type, null);  
    }  
    StreamGobbler(InputStream is, String type, OutputStream redirect) {  
        this.is = is;  
        this.type = type;  
        this.os = redirect;  
    }  
    public void run() {  
        try {  
            PrintWriter pw = null;  
            if (os != null)  
                pw = new PrintWriter(os);  
            InputStreamReader isr = new InputStreamReader(is);  
            BufferedReader br = new BufferedReader(isr);  
            String line = null;  
            while ((line = br.readLine()) != null) {  
                if (pw != null)  
                    pw.println(line);  
                System.out.println(type + ">" + line);  
            }  
            if (pw != null)  
                pw.flush();  
        } catch (IOException ioe) {  
            ioe.printStackTrace();  
        }  
    }  
}  

public class GoodWinRedirect {  
    public static void main(String args[]) {  
        args = new String[1];  
        args[0]="g:\\out.txt";  
        if (args.length < 1) {  
            System.out.println("USAGE java GoodWinRedirect <outputfile></outputfile>");  
            System.exit(1);  
        }  
        try {  
            FileOutputStream fos = new FileOutputStream(args[0]);  
            Runtime rt = Runtime.getRuntime();  
            Process proc = rt.exec("java jecho 'Hello World'");  
            // any error message?  
            StreamGobbler errorGobbler = new 
                                StreamGobbler(proc.getErrorStream(), "ERROR");  
            // any output?  
            StreamGobbler outputGobbler = new 
                                StreamGobbler(proc.getInputStream(), "OUTPUT", fos);  
            // kick them off  
            errorGobbler.start();  
            outputGobbler.start();  
            // any error???  
            int exitVal = proc.waitFor();  
            System.out.println("ExitValue: " + exitVal);  
            fos.flush();  
            fos.close();  
        } catch (Throwable t) {  
            t.printStackTrace();  
        }  
    }  
}  

彩蛋

问:为什么Runtime.exec("ls")没有任何输出?

答:调用Runtime.exec方法将产生一个本地的进程,并返回一个Process子类的实例,该实例可用于控制进程或取得进程的相关信息。

由于调用Runtime.exec方法所创建的子进程没有自己的终端或控制台,因此该子进程的标准IO(如stdin,stdou,stderr)都通过Process.getOutputStream(),Process.getInputStream(),Process.getErrorStream()方法重定向给它的父进程了。用户需要用这些stream来向子进程输入数据或获取子进程的输出。所以正确执行Runtime.exec("ls")的例程如下:

try {  
    Process process = Runtime.getRuntime().exec(command);  
    InputStreamReader ir = new InputStreamReader(process.getInputStream());  
    LineNumberReader input = new LineNumberReader(ir);  
    String line;  
    while ((line = input.readLine()) != null)  
        System.out.println(line);  
} catch (java.io.IOException e) {  
    System.err.println("IOException " + e.getMessage());