¿Cómo puedo reiniciar una aplicación Java?

94

¿Cómo puedo reiniciar una aplicación Java AWT? Tengo un botón al que he adjuntado un controlador de eventos. ¿Qué código debo usar para reiniciar la aplicación?

Quiero hacer lo mismo que Application.Restart()en una aplicación C #.

Azfar Niaz
fuente
2
Quizás no entiendo tu pregunta. ¿Quieres que tu aplicación tenga un botón que reinicie la aplicación? Entonces, una vez que la aplicación ya no se esté ejecutando, ¿debería poder reiniciarse sola? Eso me suena imposible.
Jay
No estoy preguntando eso después de que se detenga la JVM, estoy preguntando cómo puedo reaparecer mi marco java principal.
Azfar Niaz
2
No imposible. Veo que el banco de trabajo de eclipse se reinicia con frecuencia, incluso Windows hace este truco después de las actualizaciones. La suposición falsa es que la aplicación es lo único que se ejecuta sin nada debajo. Necesitaremos un lanzador capaz de reiniciar, tortugas hasta el fondo.
Whatnick
de la misma manera que en la aplicación C #, ¿dónde puede escribir System.restart () para hacerlo?
Azfar Niaz
@aniaz, entonces debe actualizar la pregunta para señalar que desea mostrar / ocultar el marco. La aplicación NO es el marco.
whatnick

Respuestas:

105

Por supuesto, es posible reiniciar una aplicación Java.

El siguiente método muestra una forma de reiniciar una aplicación Java:

public void restartApplication()
{
  final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
  final File currentJar = new File(MyClassInTheJar.class.getProtectionDomain().getCodeSource().getLocation().toURI());

  /* is it a jar file? */
  if(!currentJar.getName().endsWith(".jar"))
    return;

  /* Build command: java -jar application.jar */
  final ArrayList<String> command = new ArrayList<String>();
  command.add(javaBin);
  command.add("-jar");
  command.add(currentJar.getPath());

  final ProcessBuilder builder = new ProcessBuilder(command);
  builder.start();
  System.exit(0);
}

Básicamente hace lo siguiente:

  1. Busque el ejecutable de Java (utilicé el binario de Java aquí, pero eso depende de sus requisitos)
  2. Encuentra la aplicación (un jar en mi caso, usando la MyClassInTheJarclase para encontrar la ubicación del jar)
  3. Construya un comando para reiniciar el jar (usando el binario java en este caso)
  4. ¡Ejecutalo! (y así terminar la aplicación actual e iniciarla de nuevo)
Veger
fuente
5
¿No hay un pequeño período de tiempo en el que se ejecutan dos versiones de la misma aplicación al mismo tiempo?
Monir
5
¿System.exit (0) no terminará el proceso hijo?
Horcrux7
16
@Veger Pregunta si System.exit(0)termina el proceso hijo tiene la misma respuesta que si esta respuesta realmente funciona y por qué. Si no puede proporcionar una explicación sensata junto con su respuesta, hizo un mal trabajo. La respuesta que proporciona más preguntas de las que responde no es un ejemplo de una respuesta completa. Las buenas respuestas no solo muestran el código, sino que también explican cómo y por qué funcionan, cuáles son los inconvenientes y cuáles son las alternativas. Ni siquiera trataste de cubrir estas cosas.
Tomáš Zato - Reincorpora a Monica
8
Tantos comentarios debatiendo si responder o no a la pregunta de @ Horcrux7. Ustedes podrían haberle dicho la respuesta desde el principio jajaja. Bueno, seguiré adelante y lo haré (un poco tarde, lo sé): no, no es así. Ahí.
Voldemort
10
Para responder a mis preguntas yo mismo. ¡La muestra no funciona! El System.exit (0) termina el proceso del cliente de inmediato.
Horcrux7 de
35
import java.io.File;
import java.io.IOException;
import java.lang.management.ManagementFactory;

public class Main {
    public static void main(String[] args) throws IOException, InterruptedException {
        StringBuilder cmd = new StringBuilder();
        cmd.append(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java ");
        for (String jvmArg : ManagementFactory.getRuntimeMXBean().getInputArguments()) {
            cmd.append(jvmArg + " ");
        }
        cmd.append("-cp ").append(ManagementFactory.getRuntimeMXBean().getClassPath()).append(" ");
        cmd.append(Main.class.getName()).append(" ");
        for (String arg : args) {
            cmd.append(arg).append(" ");
        }
        Runtime.getRuntime().exec(cmd.toString());
        System.exit(0);
    }
}

Dedicado a todos los que dicen que es imposible.

Este programa recopila toda la información disponible para reconstruir la línea de comandos original. Luego, lo lanza y dado que es el mismo comando, tu aplicación se inicia por segunda vez. Luego salimos del programa original, el programa hijo permanece ejecutándose (incluso bajo Linux) y hace exactamente lo mismo.

ADVERTENCIA : Si ejecuta esto, tenga en cuenta que nunca termina creando nuevos procesos, similar a una bomba de horquilla .

Meinersbur
fuente
La posible mejora ManagementFactory.getRuntimeMXBean().getInputArguments() solo le dará los argumentos de entrada pasados ​​a la JVM. Falta los parámetros pasados ​​a su aplicación. ej java -jar start.jar -MISSED_PARAM=true. En un jvm de Oracle, puede recuperar esos parámetros usando System.getProperty("sun.java.command").
Chris2M
1
La VM principal podría terminar si la VM secundaria y la VM principal no estuvieran conectadas entre sí con conductos, que es lo que sucede cuando se inicia la VM secundaria. Al usar ProcessBuildery inheritIO(), la VM secundaria se puede iniciar de una manera que la VM principal terminaría.
Christian Hujer
1
Tengo una versión de esto. Este comentario es para decirle cómo detenerlo: cambie el nombre de algo en la ruta que contiene java.exe.
Dale
Estrictamente hablando, no se trata de reiniciar sino de lanzar una nueva JVM con los mismos argumentos que esta.
Thorbjørn Ravn Andersen
4
¿Cual es la diferencia? ¿Existe alguna diferencia entre reiniciar una PC y apagar el sistema operativo + reiniciarlo?
Meinersbur
27

Básicamente, no puedes. Al menos no de forma fiable. Sin embargo, no debería ser necesario.

El no puede separarse

Para reiniciar un programa Java, debe reiniciar la JVM. Para reiniciar la JVM necesita

  1. Localice el javalanzador que se utilizó. Puede intentarlo, System.getProperty("java.home")pero no hay garantía de que apunte al lanzador que se utilizó para iniciar su aplicación. (Es posible que el valor devuelto no apunte al JRE utilizado para iniciar la aplicación o podría haber sido anulado -Djava.home).

  2. Se podría suponer que quieren honrar la memoria original configuración etc ( -Xmx, -Xms, ...) por lo que necesita para averiguar qué entornos en los que se utilizan para iniciar la primera JVM. Puede intentar usarlo, ManagementFactory.getRuntimeMXBean().getInputArguments()pero no hay garantía de que refleje la configuración utilizada. Esto incluso se detalla en la documentación de ese método:

    Por lo general, no todas las opciones de la línea de comandos para el comando 'java' se pasan a la máquina virtual Java. Por lo tanto, los argumentos de entrada devueltos pueden no incluir todas las opciones de la línea de comandos.

  3. Si su programa lee la entrada del Standard.instdin original, se perderá en el reinicio.

  4. Muchos de estos trucos y trucos fallarán en presencia de un SecurityManager.

No debería necesitar parte

Le recomiendo que diseñe su aplicación para que sea fácil de limpiar todo y luego cree una nueva instancia de su clase "principal".

Muchas aplicaciones están diseñadas para no hacer nada más que crear una instancia en el método principal:

public class MainClass {
    ...
    public static void main(String[] args) {
        new MainClass().launch();
    }
    ...
}

Al usar este patrón, debería ser bastante fácil hacer algo como:

public class MainClass {
    ...
    public static void main(String[] args) {
        boolean restart;
        do {
            restart = new MainClass().launch();
        } while (restart);
    }
    ...
}

y dejar que launch()devolver verdadero si y sólo si la aplicación se cierra de una manera que tiene que ser reiniciado.

aioobe
fuente
3
+1 para mejores consejos de diseño; aunque, a veces, simplemente no es posible, especialmente si se usa JNI, por ejemplo.
maerics
Bueno, una biblioteca nativa podría modificar el estado global que no se puede modificar desde la interfaz JNI, por lo que no habría forma de "reiniciar" el estado del programa más que reiniciando el proceso. Por supuesto, la biblioteca nativa debería estar mejor diseñada, pero a veces dependes de cosas que no puedes controlar.
maerics
Bien, pero con ese razonamiento, también puede tener una biblioteca Java pura modificando algunas variables estáticas internas. Sin embargo, esto sería un defecto de diseño y no debería ocurrir en bibliotecas bien escritas.
aioobe
1
Su respuesta es incorrecta, ya que es perfectamente posible incluso sin aplicaciones / demonios externos como lo muestran Meinersbur y mi propia respuesta. Y para fines de autoactualización, reiniciar una aplicación es una buena solución, por lo que en realidad también es necesario reiniciar las aplicaciones.
Veger
1
Pero se hace uso de una aplicación externa: java! Olvidas que Java es una especificación de lenguaje, no un programa. ¿Qué pasa si ejecuto su programa usando algún otro jvm, como kaffe, por ejemplo?
Actualicé
9

Estrictamente hablando, un programa Java no puede reiniciarse por sí mismo, ya que para hacerlo debe eliminar la JVM en la que se está ejecutando y luego iniciarla de nuevo, pero una vez que la JVM ya no se ejecuta (se elimina), no se puede realizar ninguna acción.

Puede hacer algunos trucos con cargadores de clases personalizados para cargar, empaquetar e iniciar los componentes AWT nuevamente, pero esto probablemente causará muchos dolores de cabeza con respecto al bucle de eventos de la GUI.

Dependiendo de cómo se inicie la aplicación, puede iniciar la JVM en un script de envoltura que contiene un bucle do / while, que continúa mientras la JVM sale con un código en particular, entonces la aplicación AWT tendría que llamar System.exit(RESTART_CODE). Por ejemplo, en la secuencia de comandos de pseudocódigo:

DO
  # Launch the awt program
  EXIT_CODE = # Get the exit code of the last process
WHILE (EXIT_CODE == RESTART_CODE)

La aplicación AWT debe salir de la JVM con algo distinto al RESTART_CODE en la terminación "normal" que no requiere reinicio.

maerics
fuente
solución muy interesante. El problema en OSX es que, por lo general, las aplicaciones Java se ejecutan desde una compilación JavaApplicationStub... No estoy seguro de si hay una manera fácil de evitar eso.
Dan Rosenstark
7

Eclipse normalmente se reinicia después de que se instala un complemento. Lo hacen utilizando un contenedor eclipse.exe (aplicación de inicio) para Windows. Esta aplicación ejecuta el jar del corredor principal de eclipse y si la aplicación java de eclipse termina con un código de relanzamiento, eclipse.exe reinicia el banco de trabajo. Puede crear un código nativo similar, un script de shell u otro contenedor de código Java para lograr el reinicio.

¿Qué?
fuente
5

Ventanas

public void restartApp(){

    // This launches a new instance of application dirctly, 
    // remember to add some sleep to the start of the cmd file to make sure current instance is
    // completely terminated, otherwise 2 instances of the application can overlap causing strange
    // things:)

    new ProcessBuilder("cmd","/c start /min c:/path/to/script/that/launches/my/application.cmd ^& exit").start();
    System.exit(0);
}

/ min para iniciar el script en la ventana minimizada

^ & salir para cerrar la ventana de cmd después de terminar

un script cmd de muestra podría ser

@echo off
rem add some sleep (e.g. 10 seconds) to allow the preceding application instance to release any open resources (like ports) and exit gracefully, otherwise the new instance could fail to start
sleep 10   
set path=C:\someFolder\application_lib\libs;%path%
java -jar application.jar

dormir 10 dormir durante 10 segundos

Amr Lotfy
fuente
4

Aunque esta pregunta es antigua y está respondida, me encontré con un problema con algunas de las soluciones y decidí agregar mi sugerencia a la mezcla.

El problema con algunas de las soluciones es que crean una única cadena de comandos. Esto crea problemas cuando algunos parámetros contienen espacios, especialmente java.home .

Por ejemplo, en Windows, la línea

final String javaBin = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";

Podría devolver algo como esto:C:\Program Files\Java\jre7\bin\java

Esta cadena debe estar entre comillas o escaparse debido al espacio en Program Files. No es un gran problema, pero algo molesto y propenso a errores, especialmente en aplicaciones multiplataforma.

Por lo tanto, mi solución crea el comando como una matriz de comandos:

public static void restart(String[] args) {

        ArrayList<String> commands = new ArrayList<String>(4 + jvmArgs.size() + args.length);
        List<String> jvmArgs = ManagementFactory.getRuntimeMXBean().getInputArguments();

        // Java
        commands.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");

        // Jvm arguments
        for (String jvmArg : jvmArgs) {
            commands.add(jvmArg);
        }

        // Classpath
        commands.add("-cp");
        commands.add(ManagementFactory.getRuntimeMXBean().getClassPath());

        // Class to be executed
        commands.add(BGAgent.class.getName());

        // Command line arguments
        for (String arg : args) {
            commands.add(arg);
        }

        File workingDir = null; // Null working dir means that the child uses the same working directory

        String[] env = null; // Null env means that the child uses the same environment

        String[] commandArray = new String[commands.size()];
        commandArray = commands.toArray(commandArray);

        try {
            Runtime.getRuntime().exec(commandArray, env, workingDir);
            System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
Malta
fuente
3

Yo mismo estaba investigando el tema cuando me encontré con esta pregunta.

Independientemente del hecho de que la respuesta ya está aceptada, me gustaría ofrecer un enfoque alternativo para completar. Específicamente, Apache Ant sirvió como una solución muy flexible.

Básicamente, todo se reduce a un archivo de script Ant con una única tarea de ejecución de Java (consulte aquí y aquí ) invocada desde un código Java (consulte aquí ). Este código Java, que puede ser el inicio de un método , podría ser parte de la aplicación que debe reiniciarse. La aplicación debe tener una dependencia de la biblioteca Apache Ant (jar).

Siempre que la aplicación deba reiniciarse, debe llamar al lanzamiento del método y salir de la VM. La tarea Ant java debe tener las opciones fork y spawn configuradas en true.

Aquí hay un ejemplo de un script Ant:

<project name="applaucher" default="launch" basedir=".">
<target name="launch">
    <java classname="package.MasinClass" fork="true" spawn="true">
        <jvmarg value="-splash:splash.jpg"/>
        <jvmarg value="-D other VM params"/>
        <classpath>
            <pathelement location="lib-1.jar" />
            ...
            <pathelement location="lib-n.jar" />
        </classpath>
    </java>
</target>
</project>

El código para el método de lanzamiento puede verse así:

public final void launch(final String antScriptFile) {
 /* configure Ant and execute the task */
   final File buildFile = new File(antScriptFile);
   final Project p = new Project();
   p.setUserProperty("ant.file", buildFile.getAbsolutePath());

   final DefaultLogger consoleLogger = new DefaultLogger();
   consoleLogger.setErrorPrintStream(System.err);
   consoleLogger.setOutputPrintStream(System.out);
   consoleLogger.setMessageOutputLevel(Project.MSG_INFO);
   p.addBuildListener(consoleLogger);

   try {
       p.fireBuildStarted();
       p.init();
       final ProjectHelper helper = ProjectHelper.getProjectHelper();
       p.addReference("ant.projectHelper", helper);
       helper.parse(p, buildFile);
       p.executeTarget(p.getDefaultTarget());
       p.fireBuildFinished(null);
   } catch (final BuildException e) {
       p.fireBuildFinished(e);
   }

   /* exit the current VM */
   System.exit(0);

}

Una cosa muy conveniente aquí es que se usa el mismo script para el inicio inicial de la aplicación así como para los reinicios.

01es
fuente
3

Simplemente agregando información que no está presente en otras respuestas.

Si procfs /proc/self/cmdline está disponible

Si está ejecutando en un entorno que proporciona procfs y, por lo tanto, tiene el /procsistema de archivos disponible (lo que significa que esta no es una solución portátil), puede hacer que Java lea /proc/self/cmdlinepara reiniciarse, así:

public static void restart() throws IOException {
    new ProcessBuilder(getMyOwnCmdLine()).inheritIO().start();
}
public static String[] getMyOwnCmdLine() throws IOException {
    return readFirstLine("/proc/self/cmdline").split("\u0000");
}
public static String readFirstLine(final String filename) throws IOException {
    try (final BufferedReader in = new BufferedReader(new FileReader(filename))) {
        return in.readLine();
    }
}

En sistemas con /proc/self/cmdlinedisponibilidad, esta es probablemente la forma más elegante de cómo "reiniciar" el proceso actual de Java desde Java. No hay JNI involucrado, y no se requiere adivinar caminos y cosas. Esto también se encargará de todas las opciones de JVM pasadas al javabinario. La línea de comando será exactamente idéntica a la del proceso actual de JVM.

Muchos sistemas UNIX, incluido GNU / Linux (incluido Android) actualmente tienen procfs. Sin embargo, en algunos como FreeBSD, está obsoleto y se está eliminando gradualmente. Mac OS X es una excepción en el sentido de que no tiene procfs . Windows tampoco tiene procfs . Cygwin tiene procfs pero es invisible para Java porque solo es visible para las aplicaciones que usan las DLL de Cygwin en lugar de las llamadas al sistema de Windows, y Java no conoce Cygwin.

No olvides usar ProcessBuilder.inheritIO()

El valor predeterminado es que stdin/ stdout/ stderr(en Java llamado System.in/ System.out/ System.err) del proceso iniciado se configuran en conductos que permiten que el proceso en ejecución se comunique con el proceso recién iniciado. Si desea reiniciar el proceso actual, lo más probable es que esto no sea lo que desea . En su lugar, querrá que stdin/ stdout/ stderrsean los mismos que los de la VM actual. Esto se llama heredado . Puede hacerlo llamando inheritIO()a su ProcessBuilderinstancia.

Escollo en Windows

Un caso de uso frecuente de una restart()función es reiniciar la aplicación después de una actualización. La última vez que probé esto en Windows fue problemático. Cuando sobrescribió el .jararchivo de la aplicación con la nueva versión, la aplicación comenzó a comportarse mal y a dar excepciones sobre el .jararchivo. Solo digo, en caso de que este sea su caso de uso. En ese entonces, resolví el problema envolviendo la aplicación en un archivo por lotes y usando un valor de retorno mágico del System.exit()que consulté en el archivo por lotes e hice que el archivo por lotes reiniciara la aplicación en su lugar.

Christian Hujer
fuente
2

Vieja pregunta y todo eso. Pero esta es otra forma que ofrece algunas ventajas.

En Windows, puede pedirle al programador de tareas que vuelva a iniciar su aplicación. Esto tiene la ventaja de esperar un tiempo específico antes de reiniciar la aplicación. Puede ir al administrador de tareas y eliminar la tarea y deja de repetirse.

SimpleDateFormat hhmm = new SimpleDateFormat("kk:mm");    
Calendar aCal = Calendar.getInstance(); 
aCal.add(Calendar.SECOND, 65);
String nextMinute = hhmm.format(aCal.getTime()); //Task Scheduler Doesn't accept seconds and won't do current minute.
String[] create = {"c:\\windows\\system32\\schtasks.exe", "/CREATE", "/F", "/TN", "RestartMyProg", "/SC", "ONCE", "/ST", nextMinute, "/TR", "java -jar c:\\my\\dev\\RestartTest.jar"};  
Process proc = Runtime.getRuntime().exec(create, null, null);
System.out.println("Exit Now");
try {Thread.sleep(1000);} catch (Exception e){} // just so you can see it better
System.exit(0);
Valle
fuente
2

Similar a la respuesta ' mejorada ' de Yoda , pero con más mejoras (tanto funcionales como de legibilidad y de prueba). Ahora es seguro de ejecutar y se reinicia tantas veces como la cantidad de argumentos de programa proporcionados.

  • Sin acumulación de JAVA_TOOL_OPTIONSopciones.
  • Encuentra automáticamente la clase principal.
  • Hereda stdout / stderr actual.

public static void main(String[] args) throws Exception {
    if (args.length == 0)
        return;
    else
        args = Arrays.copyOf(args, args.length - 1);

    List<String> command = new ArrayList<>(32);
    appendJavaExecutable(command);
    appendVMArgs(command);
    appendClassPath(command);
    appendEntryPoint(command);
    appendArgs(command, args);

    System.out.println(command);
    try {
        new ProcessBuilder(command).inheritIO().start();
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}

private static void appendJavaExecutable(List<String> cmd) {
    cmd.add(System.getProperty("java.home") + File.separator + "bin" + File.separator + "java");
}

private static void appendVMArgs(Collection<String> cmd) {
    Collection<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();

    String javaToolOptions = System.getenv("JAVA_TOOL_OPTIONS");
    if (javaToolOptions != null) {
        Collection<String> javaToolOptionsList = Arrays.asList(javaToolOptions.split(" "));
        vmArguments = new ArrayList<>(vmArguments);
        vmArguments.removeAll(javaToolOptionsList);
    }

    cmd.addAll(vmArguments);
}

private static void appendClassPath(List<String> cmd) {
    cmd.add("-cp");
    cmd.add(ManagementFactory.getRuntimeMXBean().getClassPath());
}

    private static void appendEntryPoint(List<String> cmd) {
    StackTraceElement[] stackTrace          = new Throwable().getStackTrace();
    StackTraceElement   stackTraceElement   = stackTrace[stackTrace.length - 1];
    String              fullyQualifiedClass = stackTraceElement.getClassName();
    String              entryMethod         = stackTraceElement.getMethodName();
    if (!entryMethod.equals("main"))
        throw new AssertionError("Entry point is not a 'main()': " + fullyQualifiedClass + '.' + entryMethod);

    cmd.add(fullyQualifiedClass);
}

private static void appendArgs(List<String> cmd, String[] args) {
    cmd.addAll(Arrays.asList(args));
}

V1.1 Bugfix: puntero nulo si JAVA_TOOL_OPTIONS no está configurado


Ejemplo:

$ java -cp Temp.jar Temp a b c d e
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b, c, d]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b, c]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a, b]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp, a]
[/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java, -cp, Temp.jar, Temp]
$
Mark Jeronimus
fuente
-13
System.err.println("Someone is Restarting me...");
setVisible(false);
try {
    Thread.sleep(600);
} catch (InterruptedException e1) {
    e1.printStackTrace();
}
setVisible(true);

Supongo que no quieres detener la aplicación, sino "Reiniciarla". Para eso, puede usar esto y agregar su "Reset" antes del sueño y después de la ventana invisible.

NBStudios
fuente
4
El usuario pidió reiniciar la aplicación, no solo ocultar y mostrar una ventana.
Amr Lotfy