diff --git a/src/java.base/share/classes/java/lang/Process.java b/src/java.base/share/classes/java/lang/Process.java index 3e4837d2e0253..3b67c5bb5cb7c 100644 --- a/src/java.base/share/classes/java/lang/Process.java +++ b/src/java.base/share/classes/java/lang/Process.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2025, 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 @@ -78,10 +78,6 @@ * process I/O can also be redirected * using methods of the {@link ProcessBuilder} class. * - *

The process is not killed when there are no more references to - * the {@code Process} object, but rather the process - * continues executing asynchronously. - * *

There is no requirement that the process represented by a {@code * Process} object execute asynchronously or concurrently with respect * to the Java process that owns the {@code Process} object. @@ -98,6 +94,49 @@ * Delegating to the underlying Process or ProcessHandle is typically * easiest and most efficient. * + *

Resource Usage

+ * {@linkplain ProcessBuilder#start() Starting a process} uses resources in both the invoking process and the invoked + * process and for the communication streams between them. + * The resources to control the process and for communication between the processes are retained + * until there are no longer any references to the Process or the input, error, and output streams + * or readers, or they have been closed. + * + *

The process is not killed when there are no more references to the {@code Process} object, + * but rather the process continues executing asynchronously. + * The process implementation closes file descriptors and handles for streams + * that are no longer referenced to prevent leaking operating system resources. + * Processes that have terminated or been terminated are monitored and their resources released. + * + *

Streams should be {@code closed} when they are no longer needed, to avoid delaying + * releasing the operating system resources. + * {@code Try-with-resources} can be used to open and close the streams. + *

For example, to capture the output of a program known to produce some output and then exit: + * {@snippet lang = "java" : + * List capture(List args) throws Exception { + * ProcessBuilder pb = new ProcessBuilder(args); + * Process process = pb.start(); + * try (BufferedReader in = process.inputReader()) { + * List captured = in.readAllLines(); + * int status = process.waitFor(); + * if (status != 0) { + * throw new RuntimeException("Process %d: %s failed with %d" + * .formatted(process.pid(), args, status)); + * } + * return captured; + * } + * } + * } + *

Stream resources (file descriptord or handled) are always paired; one in the invoking process + * and the other end of that connection in the invoked process. + * Closing a stream at either end terminates communication but does not have any direct effect + * on the other Process. The closing of the stream typically results in the other process exiting. + * + *

{@linkplain #destroy Destroying a process} signals the operating system to terminate the process. + * It is up to the operating system to cleanup and release the resources of that process. + * Typically, file descriptors and handles are closed. When they are closed, any connections to + * other processes are terminated and file descriptors and handles in the invoking process signal + * end-of-file or closed. Usually, that is seen as an end-of-file or an exception. + * * @since 1.0 */ public abstract class Process { @@ -127,6 +166,9 @@ public Process() {} * then this method will return a * null output stream. * + *

The output stream should be {@linkplain OutputStream#close closed} + * when it is no longer needed. + * * @apiNote * When writing to both {@link #getOutputStream()} and either {@link #outputWriter()} * or {@link #outputWriter(Charset)}, {@link BufferedWriter#flush BufferedWriter.flush} @@ -159,9 +201,15 @@ public Process() {} * then the input stream returned by this method will receive the * merged standard output and the standard error of the process. * + *

The input stream should be {@linkplain InputStream#close closed} + * when it is no longer needed. + * * @apiNote - * Use {@link #getInputStream()} and {@link #inputReader()} with extreme care. - * The {@code BufferedReader} may have buffered input from the input stream. + * Use either this method or an {@linkplain #inputReader(Charset) input reader} + * but not both on the same {@code Process}. + * The input reader consumes and buffers bytes from the input stream. + * Bytes read from the input stream would not be seen by the reader and + * buffer contents are unpredictable. * * @implNote * Implementation note: It is a good idea for the returned @@ -185,9 +233,15 @@ public Process() {} * then this method will return a * null input stream. * + *

The error stream should be {@linkplain InputStream#close closed} + * when it is no longer needed. + * * @apiNote - * Use {@link #getErrorStream()} and {@link #errorReader()} with extreme care. - * The {@code BufferedReader} may have buffered input from the error stream. + * Use either this method or an {@linkplain #errorReader(Charset) error reader} + * but not both on the same {@code Process}. + * The error reader consumes and buffers bytes from the error stream. + * Bytes read from the error stream would not be seen by the reader and the + * buffer contents are unpredictable. * * @implNote * Implementation note: It is a good idea for the returned @@ -208,6 +262,16 @@ public Process() {} * If the {@code native.encoding} is not a valid charset name or not supported * the {@link Charset#defaultCharset()} is used. * + *

The reader should be {@linkplain BufferedReader#close closed} + * when it is no longer needed. + * + * @apiNote + * Use either this method or the {@linkplain #getInputStream input stream} + * but not both on the same {@code Process}. + * The input reader consumes and buffers bytes from the input stream. + * Bytes read from the input stream would not be seen by the reader and the + * buffer contents are unpredictable. + * * @return a {@link BufferedReader BufferedReader} using the * {@code native.encoding} if supported, otherwise, the * {@link Charset#defaultCharset()} @@ -238,6 +302,9 @@ public final BufferedReader inputReader() { * then the {@code InputStreamReader} will be reading from a * null input stream. * + *

The reader should be {@linkplain BufferedReader#close closed} + * when it is no longer needed. + * *

Otherwise, if the standard error of the process has been redirected using * {@link ProcessBuilder#redirectErrorStream(boolean) * ProcessBuilder.redirectErrorStream} then the input reader returned by @@ -245,9 +312,11 @@ public final BufferedReader inputReader() { * of the process. * * @apiNote - * Using both {@link #getInputStream} and {@link #inputReader(Charset)} has - * unpredictable behavior since the buffered reader reads ahead from the - * input stream. + * Use either this method or the {@linkplain #getInputStream input stream} + * but not both on the same {@code Process}. + * The input reader consumes and buffers bytes from the input stream. + * Bytes read from the input stream would not be seen by the reader and the + * buffer contents are unpredictable. * *

When the process has terminated, and the standard input has not been redirected, * reading of the bytes available from the underlying stream is on a best effort basis and @@ -283,6 +352,16 @@ public final BufferedReader inputReader(Charset charset) { * If the {@code native.encoding} is not a valid charset name or not supported * the {@link Charset#defaultCharset()} is used. * + *

The error reader should be {@linkplain BufferedReader#close closed} + * when it is no longer needed. + * + * @apiNote + * Use either this method or the {@linkplain #getErrorStream error stream} + * but not both on the same {@code Process}. + * The error reader consumes and buffers bytes from the error stream. + * Bytes read from the error stream would not be seen by the reader and the + * buffer contents are unpredictable. + * * @return a {@link BufferedReader BufferedReader} using the * {@code native.encoding} if supported, otherwise, the * {@link Charset#defaultCharset()} @@ -314,10 +393,15 @@ public final BufferedReader errorReader() { * then the {@code InputStreamReader} will be reading from a * null input stream. * + *

The error reader should be {@linkplain BufferedReader#close closed} + * when it is no longer needed. + * * @apiNote - * Using both {@link #getErrorStream} and {@link #errorReader(Charset)} has - * unpredictable behavior since the buffered reader reads ahead from the - * error stream. + * Use either this method or the {@linkplain #getErrorStream error stream} + * but not both on the same {@code Process}. + * The error reader consumes and buffers bytes from the error stream. + * Bytes read from the error stream would not be seen by the reader and the + * buffer contents are unpredictable. * *

When the process has terminated, and the standard error has not been redirected, * reading of the bytes available from the underlying stream is on a best effort basis and @@ -354,6 +438,9 @@ public final BufferedReader errorReader(Charset charset) { * If the {@code native.encoding} is not a valid charset name or not supported * the {@link Charset#defaultCharset()} is used. * + *

The output writer should be {@linkplain BufferedWriter#close closed} + * when it is no longer needed. + * * @return a {@code BufferedWriter} to the standard input of the process using the charset * for the {@code native.encoding} system property * @since 17 @@ -383,6 +470,9 @@ public final BufferedWriter outputWriter() { * ProcessBuilder.redirectInput} then the {@code OutputStreamWriter} writes to a * null output stream. * + *

The output writer should be {@linkplain BufferedWriter#close closed} + * when it is no longer needed. + * * @apiNote * A {@linkplain BufferedWriter} writes characters, arrays of characters, and strings. * Wrapping the {@link BufferedWriter} with a {@link PrintWriter} provides @@ -674,11 +764,12 @@ public long pid() { * free the current thread and block only if and when the value is needed. *
* For example, launching a process to compare two files and get a boolean if they are identical: - *

 {@code   Process p = new ProcessBuilder("cmp", "f1", "f2").start();
-     *    Future identical = p.onExit().thenApply(p1 -> p1.exitValue() == 0);
-     *    ...
-     *    if (identical.get()) { ... }
-     * }
+ * {@snippet lang = "java" : + * Process p = new ProcessBuilder("cmp", "f1", "f2").start(); + * Future identical = p.onExit().thenApply(p1 -> p1.exitValue() == 0); + * ... + * if (identical.get()) { ... } + * } * * @implSpec * This implementation executes {@link #waitFor()} in a separate thread @@ -695,11 +786,11 @@ public long pid() { * External implementations should override this method and provide * a more efficient implementation. For example, to delegate to the underlying * process, it can do the following: - *
{@code
+     * {@snippet lang = "java" :
      *    public CompletableFuture onExit() {
      *       return delegate.onExit().thenApply(p -> this);
      *    }
-     * }
+ * } * @apiNote * The process may be observed to have terminated with {@link #isAlive} * before the ComputableFuture is completed and dependent actions are invoked. diff --git a/src/java.base/share/classes/java/lang/ProcessBuilder.java b/src/java.base/share/classes/java/lang/ProcessBuilder.java index 9cb5848bdff55..f342b49bc0224 100644 --- a/src/java.base/share/classes/java/lang/ProcessBuilder.java +++ b/src/java.base/share/classes/java/lang/ProcessBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2025, 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 @@ -150,30 +150,33 @@ *

Starting a new process which uses the default working directory * and environment is easy: * - *

 {@code
+ * {@snippet lang = "java" :
+
  * Process p = new ProcessBuilder("myCommand", "myArg").start();
- * }
+ * } + * *

Here is an example that starts a process with a modified working * directory and environment, and redirects standard output and error * to be appended to a log file: * - *

 {@code
- * ProcessBuilder pb =
- *   new ProcessBuilder("myCommand", "myArg1", "myArg2");
- * Map env = pb.environment();
- * env.put("VAR1", "myValue");
- * env.remove("OTHERVAR");
- * env.put("VAR2", env.get("VAR1") + "suffix");
- * pb.directory(new File("myDir"));
- * File log = new File("log");
- * pb.redirectErrorStream(true);
- * pb.redirectOutput(Redirect.appendTo(log));
- * Process p = pb.start();
- * assert pb.redirectInput() == Redirect.PIPE;
- * assert pb.redirectOutput().file() == log;
- * assert p.getInputStream().read() == -1;
- * }
+ * {@snippet lang = "java": + * ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2"); + * Map env = pb.environment(); + * env.put("VAR1", "myValue"); + * env.remove("OTHERVAR"); + * env.put("VAR2", env.get("VAR1") + "suffix"); + * + * pb.directory(new File("myDir")); + * File log = new File("log"); + * pb.redirectErrorStream(true); + * pb.redirectOutput(Redirect.appendTo(log)); + * + * Process p = pb.start(); + * assert pb.redirectInput() == Redirect.PIPE; + * assert pb.redirectOutput().file() == log; + * assert p.getInputStream().read() == -1; + * } * *

To start a process with an explicit set of environment * variables, first call {@link java.util.Map#clear() Map.clear()} @@ -506,10 +509,10 @@ public enum Type { * This is the default handling of subprocess standard I/O. * *

It will always be true that - *

 {@code
-         * Redirect.PIPE.file() == null &&
-         * Redirect.PIPE.type() == Redirect.Type.PIPE
-         * }
+ * {@snippet lang = "java" : + * Redirect.PIPE.file() == null && + * Redirect.PIPE.type() == Redirect.Type.PIPE + * } */ public static final Redirect PIPE = new Redirect() { public Type type() { return Type.PIPE; } @@ -521,10 +524,10 @@ public enum Type { * behavior of most operating system command interpreters (shells). * *

It will always be true that - *

 {@code
-         * Redirect.INHERIT.file() == null &&
-         * Redirect.INHERIT.type() == Redirect.Type.INHERIT
-         * }
+ * {@snippet lang = "java" : + * Redirect.INHERIT.file() == null && + * Redirect.INHERIT.type() == Redirect.Type.INHERIT + * } */ public static final Redirect INHERIT = new Redirect() { public Type type() { return Type.INHERIT; } @@ -537,11 +540,10 @@ public enum Type { * an operating system specific "null file". * *

It will always be true that - *

 {@code
-         * Redirect.DISCARD.file() is the filename appropriate for the operating system
-         * and may be null &&
-         * Redirect.DISCARD.type() == Redirect.Type.WRITE
-         * }
+ * {@snippet lang = "java" : + * Redirect.DISCARD.file(); // is the filename appropriate for the operating system + * Redirect.DISCARD.type() == Redirect.Type.WRITE; + * } * @since 9 */ public static final Redirect DISCARD = new Redirect() { @@ -572,10 +574,10 @@ boolean append() { * Returns a redirect to read from the specified file. * *

It will always be true that - *

 {@code
-         * Redirect.from(file).file() == file &&
-         * Redirect.from(file).type() == Redirect.Type.READ
-         * }
+ * {@snippet lang = "java" : + * Redirect.from(file).file() == file && + * Redirect.from(file).type() == Redirect.Type.READ + * } * * @param file The {@code File} for the {@code Redirect}. * @return a redirect to read from the specified file @@ -598,10 +600,10 @@ public String toString() { * its previous contents will be discarded. * *

It will always be true that - *

 {@code
-         * Redirect.to(file).file() == file &&
-         * Redirect.to(file).type() == Redirect.Type.WRITE
-         * }
+ * {@snippet lang = "java" : + * Redirect.to(file).file() == file && + * Redirect.to(file).type() == Redirect.Type.WRITE + * } * * @param file The {@code File} for the {@code Redirect}. * @return a redirect to write to the specified file @@ -628,10 +630,10 @@ public String toString() { * system-dependent and therefore unspecified. * *

It will always be true that - *

 {@code
-         * Redirect.appendTo(file).file() == file &&
-         * Redirect.appendTo(file).type() == Redirect.Type.APPEND
-         * }
+ * {@snippet lang = "java" : + * Redirect.appendTo(file).file() == file && + * Redirect.appendTo(file).type() == Redirect.Type.APPEND + * } * * @param file The {@code File} for the {@code Redirect}. * @return a redirect to append to the specified file @@ -914,15 +916,15 @@ public Redirect redirectError() { * to be the same as those of the current Java process. * *

This is a convenience method. An invocation of the form - *

 {@code
-     * pb.inheritIO()
-     * }
+ * {@snippet lang = "java" : + * pb.inheritIO() + * } * behaves in exactly the same way as the invocation - *
 {@code
-     * pb.redirectInput(Redirect.INHERIT)
-     *   .redirectOutput(Redirect.INHERIT)
-     *   .redirectError(Redirect.INHERIT)
-     * }
+ * {@snippet lang = "java" : + * pb.redirectInput(Redirect.INHERIT) + * .redirectOutput(Redirect.INHERIT) + * .redirectError(Redirect.INHERIT) + * } * * This gives behavior equivalent to most operating system * command interpreters, or the standard C library function @@ -1176,22 +1178,21 @@ private Process start(Redirect[] redirects) throws IOException { * @apiNote * For example to count the unique imports for all the files in a file hierarchy * on a Unix compatible platform: - *
{@code
-     * String directory = "/home/duke/src";
-     * ProcessBuilder[] builders = {
+     * {@snippet lang = "java" :
+     *     String directory = "/home/duke/src";
+     *     ProcessBuilder[] builders = {
      *              new ProcessBuilder("find", directory, "-type", "f"),
      *              new ProcessBuilder("xargs", "grep", "-h", "^import "),
      *              new ProcessBuilder("awk", "{print $2;}"),
      *              new ProcessBuilder("sort", "-u")};
-     * List processes = ProcessBuilder.startPipeline(
-     *         Arrays.asList(builders));
-     * Process last = processes.get(processes.size()-1);
-     * try (InputStream is = last.getInputStream();
+     *     List processes = ProcessBuilder.startPipeline( Arrays.asList(builders));
+     *     Process last = processes.get(processes.size() - 1);
+     *     try (InputStream is = last.getInputStream();
      *         Reader isr = new InputStreamReader(is);
      *         BufferedReader r = new BufferedReader(isr)) {
-     *     long count = r.lines().count();
+     *         long count = r.lines().count();
+     *     }
      * }
-     * }
* * @param builders a List of ProcessBuilders * @return a {@code List}es started from the corresponding