如何重新启动Java应用程序?

时间:2010-11-11 22:15:34

标签: java restart application-restart

如何重新启动Java AWT应用程序?我有一个按钮,我附加了一个事件处理程序。我应该用什么代码来重启应用程序?

我想做Application.Restart()在C#应用程序中做的事情。

13 个答案:

答案 0 :(得分:100)

当然可以重新启动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. 找到应用程序(在我的情况下是一个jar,使用MyClassInTheJar类来查找jar位置本身)
  3. 构建一个重启jar的命令(在本例中使用java二进制文件)
  4. 执行它! (从而终止当前的申请并重新开始)

答案 1 :(得分:33)

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下)也做同样的事情。

警告:如果您运行此操作,请注意它永远不会结束创建新进程,类似于fork bomb

答案 2 :(得分:22)

基本上,你不能。至少不是以可靠的方式。

要重新启动Java程序,需要重新启动JVM。要重新启动JVM,您需要

  1. 找到已使用的java启动器。您可以尝试使用System.getProperty("java.home"),但无法保证这实际上会指向用于启动应用程序的启动器。 (返回的值为may not point to the JRE used to launch the application,或者-Djava.home可能会覆盖该值。)

  2. 您可能希望遵守原始内存设置等(-Xmx-Xms,...),因此您需要确定用于启动第一个JVM的设置。您可以尝试使用ManagementFactory.getRuntimeMXBean().getInputArguments(),但无法保证这将反映所使用的设置。这甚至在该方法的the documentation中详细说明:

      

    通常,并非'java'命令的所有命令行选项都传递给Java虚拟机。因此,返回的输入参数可能不包括所有命令行选项。

  3. 如果你的程序从Standard.in读取输入,原始stdin将在重启时丢失。

  4. SecurityManager出现时,很多这些技巧和黑客都会失败。


  5. 另一方面:你不应该这样做。

    我建议你设计你的应用程序,这样就可以很容易地清理每一件事,然后创建一个新的“主”类实例。

    许多应用程序只是在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。

答案 3 :(得分:8)

严格来说,Java程序无法自行重启,因为它必须终止运行它的JVM然后再次启动它,但是一旦JVM不再运行(被杀死),就不能采取任何行动。

你可以用自定义类加载器做一些技巧来再次加载,打包和启动AWT组件,但这可能会引起很多关于GUI事件循环的麻烦。

根据应用程序的启动方式,您可以在包含do / while循环的包装器脚本中启动JVM,该循环在JVM以特定代码退出时继续,然后AWT应用程序必须调用{{1 }}。例如,在编写伪代码的脚本中:

System.exit(RESTART_CODE)

AWT应用程序应该在“正常”终止时使用RESTART_CODE之外的其他东西退出JVM,这不需要重新启动。

答案 4 :(得分:7)

Eclipse通常在安装插件后重新启动。他们使用包装器eclipse.exe(启动器应用程序)为Windows执行此操作。这个应用程序执行核心eclipse运行程序jar,如果eclipse java应用程序以重新启动代码终止,eclipse.exe将重新启动工作台。您可以构建类似的本机代码,shell脚本或其他Java代码包装器来实现重启。

答案 5 :(得分:4)

如果你真的需要重启你的应用程序,你可以编写一个单独的应用程序启动它......

此页面提供了许多不同场景的不同示例:

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

答案 6 :(得分:4)

<强>窗

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 在最小化窗口中启动脚本

完成后

^&amp; 退出以关闭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秒

答案 7 :(得分:4)

虽然这个问题已经陈旧并且已经回答了,但我偶然发现了一些解决方案的问题,并决定将我的建议加入到混合中。

一些解决方案的问题是它们构建了一个命令字符串。当某些参数包含空格时,这会产生问题,尤其是 java.home

例如,在Windows上,行

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

答案 8 :(得分:3)

当我遇到这个问题时,我正在研究这个主题。

尽管答案已经被接受,但我仍然希望提供一种完整性的替代方法。具体来说,Apache Ant是一个非常灵活的解决方案。

基本上,一切都归结为Ant脚本文件,其中包含从Java代码调用的单个Java执行任务(请参阅herehere)(请参阅here)。此Java代码(可以是 launch 方法)可能是需要重新启动的应用程序的一部分。应用程序需要依赖Apache Ant库(jar)。

每当需要重新启动应用程序时,它应该调用方法启动并退出VM。 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>

launch 方法的代码可能如下所示:

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

}

这里非常方便的是,相同的脚本用于初始应用程序启动和重启。

答案 9 :(得分:3)

只需添加其他答案中没有的信息。

如果 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,也没有猜测所需的路径和东西。这还将处理传递给java二进制文件的所有JVM选项。命令行将与当前JVM进程完全相同。

现在许多UNIX系统(包括GNU / Linux(包括Android))都有procfs但是对于像FreeBSD这样的一些系统,它已被弃用并被逐步淘汰。 Mac OS X 没有procfs 的意义上的例外。 Windows 也没有 procfs 。 Cygwin有 procfs 但它对Java是不可见的,因为它只对使用Cygwin DLL而不是Windows系统调用的应用程序可见,而Java不知道Cygwin。

不要忘记使用ProcessBuilder.inheritIO()

默认情况下,已启动进程的stdin / stdout / stderr(在Java中称为System.in / System.out / System.err)设置为管道,允许当前正在运行的进程与新启动的进程通信。如果要重新启动当前进程,则很可能不是您想要的。相反,您希望stdin / stdout / stderr与当前VM的相同。这称为继承。您可以致电inheritIO()个实例的ProcessBuilder来完成此操作。

Windows上的陷阱

restart()函数的常见用例是在更新后重新启动应用程序。我最后一次在Windows上尝试这个是有问题的。当使用新版本覆盖应用程序的.jar文件时,应用程序开始行为异常并提供有关.jar文件的例外情况。我只是告诉,如果这是你的用例。当时我通过将应用程序包装在批处理文件中并使用我在批处理文件中查询的System.exit()的魔术返回值并让批处理文件重新启动应用程序来解决了这个问题。

答案 10 :(得分:2)

老问题和所有这些。但这是另一种提供一些优势的方式。

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

答案 11 :(得分:2)

与Yoda的“improved”答案类似,但进一步改进(功能,可读性和可测试性)。它现在可以安全运行,并且重新启动的次数与给定的程序参数数量一样多。

  • 没有积累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]
$

答案 12 :(得分:-10)

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

我猜你真的不想停止应用程序,而是“重启”它。为此,您可以使用此功能并在睡眠前和隐藏窗口之后添加“重置”。