Как я могу перезапустить приложение Java?

Как я могу перезапустить приложение Java AWT? У меня есть кнопка, к которой я прикрепил обработчик событий. Какой код я должен использовать, чтобы перезапустить приложение?

Я хочу сделать то же самое, что Application.Restart() сделать в приложении C#.

13 ответов

Решение

Конечно, можно перезапустить приложение Java.

Следующий метод показывает способ перезапустить приложение 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);
}

В основном это делает следующее:

  1. Найдите исполняемый файл Java (я использовал двоичный файл Java здесь, но это зависит от ваших требований)
  2. Найти приложение (баночка в моем случае, используя MyClassInTheJar класс, чтобы найти само место банку)
  3. Создайте команду для перезапуска jar (в этом случае используйте двоичный файл java)
  4. Выполните это! (и, таким образом, завершение текущего приложения и запуск его снова)

По сути, вы не можете. По крайней мере, не надежным способом.

Чтобы перезапустить программу Java, вам нужно перезапустить JVM. Чтобы перезапустить JVM, вам нужно

  1. Найдите java пусковая установка, которая была использована. Вы можете попробовать с System.getProperty("java.home") но нет гарантии, что это на самом деле будет указывать на панель запуска, которая использовалась для запуска вашего приложения. (Возвращаемое значение может не указывать на JRE, использованную для запуска приложения, или оно могло быть переопределено -Djava.home.)

  2. Вы, вероятно, хотели бы соблюдать оригинальные настройки памяти и т. Д. (-Xmx, -Xms… Так что вам нужно выяснить, какие настройки использовались для запуска первой JVM. Вы можете попробовать использовать ManagementFactory.getRuntimeMXBean().getInputArguments() но нет гарантии, что это будет отражать используемые настройки. Это даже прописано в документации этого метода:

    Как правило, не все параметры командной строки команды java передаются на виртуальную машину Java. Таким образом, возвращаемые входные аргументы могут не включать все параметры командной строки.

  3. Если ваша программа читает входные данные из Standard.in исходный stdin будет потерян при перезапуске.

  4. Многие из этих трюков и хаков потерпят неудачу в присутствии SecurityManager,


С другой стороны: вам не нужно.

Я рекомендую вам спроектировать ваше приложение так, чтобы было легко очистить каждую вещь, а после этого создать новый экземпляр вашего "основного" класса.

Многие приложения предназначены только для создания экземпляра в методе main:

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

Используя этот шаблон, должно быть достаточно легко сделать что-то вроде:

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

и разреши launch() верните true тогда и только тогда, когда приложение было закрыто так, что его необходимо перезапустить.

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);
    }
}

Посвящается всем тем, кто говорит, что это невозможно.

Эта программа собирает всю информацию, доступную для восстановления оригинальной командной строки. Затем он запускает его, и поскольку это та же команда, ваше приложение запускается во второй раз. Затем мы выходим из исходной программы, дочерняя программа остается запущенной (даже под Linux) и делает то же самое.

ВНИМАНИЕ: Если вы запустите это, имейте в виду, что это никогда не заканчивается созданием новых процессов, подобных бомбе-вилке.

Строго говоря, Java-программа не может перезапустить себя, поскольку для этого она должна завершить работу JVM, в которой она выполняется, а затем запустить ее снова, но как только JVM больше не работает (не завершается), никакие действия не могут быть предприняты.

Вы могли бы сделать некоторые трюки с пользовательскими загрузчиками классов, чтобы загрузить, упаковать и запустить компоненты AWT снова, но это, вероятно, вызовет много головной боли в отношении цикла событий GUI.

В зависимости от того, как запущено приложение, вы можете запустить JVM в сценарии-оболочке, который содержит цикл do/while, который продолжается, пока JVM завершает работу с определенным кодом, тогда приложение AWT должно будет вызвать System.exit(RESTART_CODE), Например, в сценарии псевдокод:

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

Приложение AWT должно выйти из JVM с чем-то отличным от RESTART_CODE при "нормальном" завершении, которое не требует перезапуска.

Eclipse обычно перезапускается после установки плагина. Они делают это, используя оболочку eclipse.exe (приложение запуска) для Windows. Это приложение исполняет основной jar-файл eclipse runner, и если java-приложение eclipse завершает работу с кодом перезапуска, eclipse.exe перезапускает рабочую среду. Вы можете создать подобный фрагмент собственного кода, сценария оболочки или другой оболочки Java-кода для достижения перезапуска.

Windows

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);
}

/ мин, чтобы запустить скрипт в свернутом окне

^& выход, чтобы закрыть окно cmd после окончания

образец сценария cmd может быть

@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

спать 10 спать в течение 10 секунд

Просто добавив информацию, которой нет в других ответах.

Если procfs /proc/self/cmdline доступен

Если вы работаете в среде, которая предоставляет procfs и, следовательно, имеет /proc файловая система доступна (что означает, что это не переносимое решение), вы можете читать Java /proc/self/cmdline чтобы перезапустить себя, вот так:

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();
    }
}

На системах с /proc/self/cmdline доступно, это, вероятно, самый элегантный способ "перезапустить" текущий процесс Java из Java. Никакой JNI не задействован, и не нужно угадывать пути и прочее.

Многие UNIX-системы, включая GNU/Linux (включая Android), в настоящее время имеют procfs. Однако в некоторых, таких как FreeBSD, это устарело и постепенно прекращается. Mac OS X является исключением в том смысле, что у него нет procfs. Windows также не имеет procfs. У Cygwin есть procfs, но он невидим для Java, потому что он виден только приложениям, использующим библиотеки Cygwin вместо системных вызовов Windows, а Java не знает о Cygwin.

Не забудьте использовать ProcessBuilder.inheritIO()

По умолчанию это stdin / stdout / stderr (в Java называется System.in / System.out / System.err) запущенного Процесса устанавливаются в каналы, которые позволяют текущему запущенному процессу взаимодействовать с вновь запущенным процессом. Если вы хотите перезапустить текущий процесс, скорее всего, это не то, что вам нужно. Вместо этого вы хотели бы этого stdin / stdout / stderr такие же, как у текущей виртуальной машины. Это называется наследственным. Вы можете сделать это, позвонив inheritIO() вашей ProcessBuilder пример.

Ловушка на Windows

Частый случай использования restart() Функция состоит в том, чтобы перезапустить приложение после обновления. В прошлый раз, когда я попробовал это на Windows, это было проблематично. Когда переписали приложения .jar файл с новой версией, приложение начало плохо себя вести и дает исключения о .jar файл. Я просто говорю, на случай, если это ваш вариант использования. Тогда я решил проблему, поместив приложение в командный файл и использовав магическое возвращаемое значение из System.exit() что я запросил в командном файле, и вместо этого командный файл перезапустил приложение.

Хотя этот вопрос старый и на него дан ответ, я наткнулся на проблему с некоторыми решениями и решил добавить свое предложение в этот микс.

Проблема некоторых решений заключается в том, что они создают единую командную строку. Это создает проблемы, когда некоторые параметры содержат пробелы, особенно java.home.

Например, на окнах строка

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

Может вернуть что-то вроде этого:C:\Program Files\Java\jre7\bin\java

Эта строка должна быть заключена в кавычки или экранирована из-за пробела в Program Files, Не большая проблема, но несколько раздражающая и подверженная ошибкам, особенно в кроссплатформенных приложениях.

Поэтому мое решение строит команду как массив команд:

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();
        }
    }

Если вам действительно нужно перезапустить приложение, вы можете написать отдельное приложение, чтобы запустить его...

На этой странице представлено много разных примеров для разных сценариев:

http://www.rgagnon.com/javadetails/java-0014.html

Похож на " улучшенный" ответ Йоды, но с дальнейшими улучшениями (функциональность, удобочитаемость и тестируемость). Теперь он безопасен для запуска и перезапускается столько раз, сколько задано аргументов программы.

  • Нет накопления JAVA_TOOL_OPTIONS опции.
  • Автоматически находит основной класс.
  • Наследует текущий stdout / stderr.

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 Исправление: нулевой указатель, если JAVA_TOOL_OPTIONS не установлен


Пример:

$ 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]
$

Старый вопрос и все такое. Но это еще один способ, который предлагает некоторые преимущества.

В Windows вы можете попросить планировщик задач снова запустить ваше приложение. Это имеет преимущество в ожидании определенного количества времени, прежде чем приложение будет перезапущено. Вы можете перейти в диспетчер задач и удалить задачу, и она перестает повторяться.

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);

Я сам исследовал эту тему, когда столкнулся с этим вопросом.

Независимо от того, что ответ уже принят, я все же хотел бы предложить альтернативный подход для полноты. В частности, Apache Ant послужил очень гибким решением.

По сути, все сводится к файлу сценария Ant с одной задачей выполнения Java (см. Здесь и здесь), вызываемой из кода Java (см. Здесь). Этот код Java, который может быть запуском метода, может быть частью приложения, которое необходимо перезапустить. Приложение должно иметь зависимость от библиотеки Apache Ant (jar).

Всякий раз, когда необходимо перезапустить приложение, оно должно вызывать запуск метода и выход из виртуальной машины. Задача Ant java должна иметь для параметров fork и spawn значение true.

Вот пример сценария 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>

Код для метода запуска может выглядеть примерно так:

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);

}

Здесь очень удобно то, что один и тот же скрипт используется как для первоначального запуска приложения, так и для перезапуска.

System.err.println("Someone is Restarting me...");
setVisible(false);
try {
    Thread.sleep(600);
} catch (InterruptedException e1) {
    e1.printStackTrace();
}
setVisible(true);

Я полагаю, вы не хотите останавливать приложение, а просто "перезапустите" его. Для этого вы можете использовать это и добавить свой "Сброс" перед сном и после невидимого окна.

Другие вопросы по тегам