Como posso reiniciar um aplicativo Java?

94

Como posso reiniciar um aplicativo Java AWT? Tenho um botão ao qual anexei um manipulador de eventos. Qual código devo usar para reiniciar o aplicativo?

Quero fazer a mesma coisa que Application.Restart()faço em um aplicativo C #.

Azfar Niaz
fonte
2
Talvez eu não entenda sua pergunta. Você deseja que seu aplicativo tenha um botão que o reinicia? Então, depois que o aplicativo não estiver mais em execução, ele deve ser capaz de reiniciar sozinho? Isso parece impossível para mim.
Jay
Não estou perguntando isso depois que a JVM parar, estou perguntando como posso reaparecer meu quadro Java principal?
Azfar Niaz
2
Não é impossível. Eu vejo o eclipse workbench reiniciar com frequência, até mesmo o Windows faz esse truque após as atualizações. A falsa suposição é que o aplicativo é a única coisa em execução sem nada por baixo. Precisaremos de um lançador com capacidade de reinicialização, tartarugas até o fim.
whatnick
da mesma forma que no aplicativo C #, onde você pode escrever System.restart () para fazer isso?
Azfar Niaz
@aniaz então você deve atualizar a pergunta para apontar que você deseja mostrar / ocultar o quadro. O aplicativo NÃO é o Frame.
whatnick

Respostas:

105

Claro, é possível reiniciar um aplicativo Java.

O método a seguir mostra uma maneira de reiniciar um aplicativo 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);
}

Basicamente, ele faz o seguinte:

  1. Encontre o executável java (usei o binário java aqui, mas isso depende de seus requisitos)
  2. Encontre o aplicativo (um jar no meu caso, usando a MyClassInTheJarclasse para encontrar o próprio local do jar)
  3. Construa um comando para reiniciar o jar (usando o binário java neste caso)
  4. Execute! (e, assim, encerrar o aplicativo atual e reiniciá-lo)
Veger
fonte
5
Não existe um pequeno intervalo de tempo em que duas versões do mesmo aplicativo são executadas ao mesmo tempo?
Monir,
5
System.exit (0) não encerrará o processo filho?
Horcrux7,
16
@Veger A pergunta se System.exit(0)termina o processo filho tem a mesma resposta que se essa resposta realmente funciona e por quê. Se você não puder fornecer uma explicação sensata junto com sua resposta, você fez um péssimo trabalho. Uma resposta que fornece mais perguntas do que respostas não é um exemplo de resposta completa. Boas respostas não apenas mostram o código, mas também explicam como e por que funcionam, quais são as desvantagens e quais são as alternativas. Você nem tentou cobrir essas coisas.
Tomáš Zato - Reintegrar Monica
8
Tantos comentários debatendo se devemos responder à pergunta de @ Horcrux7 ou não. Vocês poderiam apenas ter dito a ele a resposta desde o início lol. Bem, eu vou em frente e faço (meio tarde eu sei): não, não faz. Lá.
Voldemort
10
Para responder às minhas próprias perguntas. A amostra não funciona !!! O System.exit (0) finaliza o processo do cliente imediatamente.
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 aqueles que dizem que é impossível.

Este programa coleta todas as informações disponíveis para reconstruir a linha de comando original. Em seguida, ele o inicia e, como é o mesmo comando, seu aplicativo inicia uma segunda vez. Então, saímos do programa original, o programa filho continua em execução (mesmo no Linux) e faz exatamente a mesma coisa.

AVISO : Se você executar isso, lembre-se de que nunca termina criando novos processos, semelhante a uma bomba fork .

Meinersbur
fonte
Uma possível melhoria ManagementFactory.getRuntimeMXBean().getInputArguments() fornecerá apenas os argumentos de entrada passados ​​para a JVM. Ele perde parâmetros passados ​​para seu aplicativo. por exemplo, java -jar start.jar -MISSED_PARAM=true. Em um oracle jvm, você pode recuperar esses parâmetros usando System.getProperty("sun.java.command").
Chris2M
1
A VM pai poderia terminar se a VM filha e a VM pai não estivessem conectadas entre si por tubos, que é o que acontece na maneira como a VM filha é iniciada. Usando ProcessBuildere inheritIO(), a VM filha pode ser iniciada de forma que a VM pai seja encerrada.
Christian Hujer
1
Eu tenho uma versão disso. Este comentário ensina como pará-lo: renomeie algo no caminho que contém o java.exe.
Dale
Estritamente falando, não é reiniciar, mas sim lançar uma nova JVM com os mesmos argumentos que esta.
Thorbjørn Ravn Andersen de
4
Qual é a diferença? Existe alguma diferença entre reiniciar um PC e desligar o OS + e inicializá-lo novamente?
Meinersbur de
27

Basicamente, você não pode. Pelo menos não de maneira confiável. No entanto, você não deve precisar.

A não pode parte

Para reiniciar um programa Java, você precisa reiniciar o JVM. Para reiniciar o JVM você precisa

  1. Localize o javalançador que foi usado. Você pode tentar, System.getProperty("java.home")mas não há garantia de que isso realmente apontará para o inicializador que foi usado para iniciar seu aplicativo. (O valor retornado pode não apontar para o JRE usado para iniciar o aplicativo ou pode ter sido substituído por -Djava.home.)

  2. Você presumivelmente quer honrar a memória original configurações etc ( -Xmx, -Xms...) então você precisa descobrir quais configurações onde usado para iniciar a primeira JVM. Você pode tentar usar, ManagementFactory.getRuntimeMXBean().getInputArguments()mas não há garantia de que isso refletirá as configurações usadas. Isso está até mesmo explicitado na documentação desse método:

    Normalmente, nem todas as opções de linha de comando para o comando 'java' são passadas para a máquina virtual Java. Portanto, os argumentos de entrada retornados podem não incluir todas as opções de linha de comando.

  3. Se o seu programa lê a entrada do Standard.instdin original, ele será perdido na reinicialização.

  4. Muitos desses truques e hacks falharão na presença de um SecurityManager.

A parte não deve precisar

Eu recomendo que você projete seu aplicativo de forma que seja fácil limpar tudo e depois crie uma nova instância de sua classe "principal".

Muitos aplicativos são projetados para fazer nada além de criar uma instância no método principal:

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

Usando esse padrão, deve ser fácil fazer algo como:

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

e deixe launch()retornar verdadeiro se e somente se o aplicativo foi encerrado de uma forma que precisa ser reiniciado.

aioobe
fonte
3
1 para melhores conselhos de design; embora, às vezes, simplesmente não seja possível, especialmente se estiver usando JNI, por exemplo.
maerics
Bem, uma biblioteca nativa poderia modificar o estado global que não pode ser modificado na interface JNI, portanto, não haveria maneira de "reiniciar" o estado do programa a não ser reiniciando o processo. Claro, a biblioteca nativa deve ser melhor projetada, mas às vezes você depende de coisas que não pode controlar.
maerics
Ok, mas com esse raciocínio, você também pode ter uma biblioteca Java pura modificando algumas variáveis ​​estáticas internas. No entanto, isso seria uma falha de design e não deveria ocorrer em bibliotecas bem escritas.
aioobe
1
Sua resposta está incorreta, pois é perfeitamente possível mesmo sem aplicativos / daemons externos, como mostrado por Meinersbur e minha própria resposta. E para fins de autoatualização, reiniciar um aplicativo é uma boa solução, então, na verdade, também é necessário reiniciar os aplicativos.
Veger
1
Mas você fazer usar um aplicativo externo: java! Você está esquecendo que Java é uma especificação de linguagem, não um programa. E se eu executar seu programa usando algum outro jvm, como o kaffe, por exemplo?
Atualizei
9

Estritamente falando, um programa Java não pode reiniciar a si mesmo, pois para isso ele deve matar a JVM na qual está sendo executado e reiniciá-la, mas uma vez que a JVM não está mais em execução (morta), nenhuma ação pode ser realizada.

Você poderia fazer alguns truques com carregadores de classe personalizados para carregar, empacotar e iniciar os componentes AWT novamente, mas isso provavelmente causará muitas dores de cabeça em relação ao loop de eventos da GUI.

Dependendo de como o aplicativo é iniciado, você pode iniciar a JVM em um script de wrapper que contém um loop do / while, que continua enquanto a JVM sai com um código específico, então o aplicativo AWT teria que chamar System.exit(RESTART_CODE). Por exemplo, em pseudocódigo de script:

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

O aplicativo AWT deve sair da JVM com algo diferente de RESTART_CODE na terminação "normal" que não requer reinicialização.

matemática
fonte
solução muito interessante. O problema no OSX é que, normalmente, os aplicativos Java são executados a partir de um compilado JavaApplicationStub... Não tenho certeza se existe uma maneira fácil de contornar isso.
Dan Rosenstark
7

O Eclipse normalmente reinicia após a instalação de um plug-in. Eles fazem isso usando um wrapper eclipse.exe (aplicativo inicializador) para janelas. Este aplicativo executa o jar do executor eclipse principal e se o aplicativo eclipse java terminar com um código de reinicialização, eclipse.exe reinicia o ambiente de trabalho. Você pode construir um bit semelhante de código nativo, script de shell ou outro wrapper de código Java para conseguir a reinicialização.

whatnick
fonte
5

janelas

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 o script na janela minimizada

^ & saia para fechar a janela cmd após terminar

um script cmd de amostra pode 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 por 10 segundos

Amr Lotfy
fonte
4

Embora essa pergunta seja antiga e respondida, me deparei com um problema com algumas das soluções e decidi adicionar minha sugestão à mistura.

O problema com algumas das soluções é que elas criam uma única string de comando. Isso cria problemas quando alguns parâmetros contêm espaços, especialmente java.home .

Por exemplo, no Windows, a linha

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

Pode retornar algo assim:C:\Program Files\Java\jre7\bin\java

Esta string deve ser colocada entre aspas ou escapada devido ao espaço em Program Files. Não é um grande problema, mas um tanto chato e sujeito a erros, especialmente em aplicativos de plataforma cruzada.

Portanto, minha solução cria o comando como uma 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();
        }
    }
Malte
fonte
3

Eu mesmo estava pesquisando o assunto quando me deparei com essa pergunta.

Apesar do fato de que a resposta já foi aceita, eu ainda gostaria de oferecer uma abordagem alternativa para completude. Especificamente, o Apache Ant serviu como uma solução muito flexível.

Basicamente, tudo se resume a um arquivo de script Ant com uma única tarefa de execução Java (consulte aqui e aqui ) invocada a partir de um código Java (consulte aqui ). Este código Java, que pode ser um lançamento de método , pode ser uma parte do aplicativo que precisa ser reiniciado. O aplicativo precisa ter uma dependência da biblioteca Apache Ant (jar).

Sempre que o aplicativo precisa ser reiniciado, ele deve chamar o lançamento do método e sair da VM. A tarefa Ant java deve ter as opções fork e spawn definidas como true.

Aqui está um exemplo de um 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>

O código para o método de inicialização pode ser parecido com este:

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

}

Uma coisa muito conveniente aqui é que o mesmo script é usado para a inicialização do aplicativo inicial, bem como para reinicializações.

01es
fonte
3

Apenas adicionando informações que não estão presentes em outras respostas.

Se procfs /proc/self/cmdline estiver disponível

Se você estiver executando em um ambiente que fornece procfs e, portanto, tem o /procsistema de arquivos disponível (o que significa que esta não é uma solução portátil), você pode fazer a leitura do Java /proc/self/cmdlinepara reiniciar a si mesmo, assim:

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

Em sistemas com /proc/self/cmdlinedisponível, esta provavelmente é a maneira mais elegante de como "reiniciar" o processo Java atual do Java. Nenhum JNI envolvido e nenhuma suposição de caminhos e outras coisas necessárias. Isso também cuidará de todas as opções JVM passadas para o javabinário. A linha de comando será exatamente idêntica àquela do processo JVM atual.

Muitos sistemas UNIX incluindo GNU / Linux (incluindo Android) hoje em dia têm procfs. No entanto, em alguns como o FreeBSD, ele está obsoleto e está sendo descontinuado. O Mac OS X é uma exceção no sentido de que não possui procfs . O Windows também não tem procfs . O Cygwin tem procfs, mas é invisível para Java porque só é visível para aplicativos que usam DLLs do Cygwin em vez de chamadas de sistema do Windows, e o Java não conhece o Cygwin.

Não se esqueça de usar ProcessBuilder.inheritIO()

O padrão é que stdin/ stdout/ stderr(em Java chamado System.in/ System.out/ System.err) do processo iniciado seja definido como canais que permitem que o processo atualmente em execução se comunique com o processo recém-iniciado. Se você deseja reiniciar o processo atual, provavelmente não é isso que você deseja . Em vez disso, você deseja que stdin/ stdout/ stderrsejam iguais aos da VM atual. Isso é chamado de herdado . Você pode fazer isso chamando inheritIO()sua ProcessBuilderinstância.

Pitfall no Windows

Um caso de uso frequente de uma restart()função é reiniciar o aplicativo após uma atualização. A última vez que tentei isso no Windows, isso foi problemático. Ao sobrescrever o .jararquivo do aplicativo com a nova versão, o aplicativo passou a apresentar mau funcionamento e .jarabrir exceções sobre o arquivo. Estou apenas dizendo, caso este seja o seu caso de uso. Naquela época, resolvi o problema envolvendo o aplicativo em um arquivo em lote e usando um valor de retorno mágico System.exit()daquele que consultei no arquivo em lote e fiz com que o arquivo em lote reiniciasse o aplicativo.

Christian Hujer
fonte
2

Pergunta antiga e tudo isso. Mas esta é mais uma forma que oferece algumas vantagens.

No Windows, você pode pedir ao agendador de tarefas para iniciar seu aplicativo novamente para você. Isso tem a vantagem de esperar um determinado período de tempo antes que o aplicativo seja reiniciado. Você pode ir para o gerenciador de tarefas e excluir a tarefa e ela para de se repetir.

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);
Dale
fonte
2

Semelhante à resposta ' melhorada ' de Yoda , mas com outras melhorias (tanto funcional, legibilidade e testabilidade). Agora é seguro executar e reiniciar tantas vezes quanto a quantidade de argumentos de programa fornecidos.

  • Sem acúmulo de JAVA_TOOL_OPTIONSopções.
  • Encontra automaticamente a classe principal.
  • Herda o stdout / stderr atual.

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: ponteiro nulo se JAVA_TOOL_OPTIONS não estiver definido


Exemplo:

$ 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
fonte
-13
System.err.println("Someone is Restarting me...");
setVisible(false);
try {
    Thread.sleep(600);
} catch (InterruptedException e1) {
    e1.printStackTrace();
}
setVisible(true);

Eu acho que você realmente não quer parar o aplicativo, mas sim "Reiniciá-lo". Para isso, você pode usar isso e adicionar o seu "Reset" antes de dormir e depois da janela invisível.

NBStudios
fonte
4
O usuário pediu para reiniciar o aplicativo não apenas para ocultar e mostrar uma janela.
Amr Lotfy de