104

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

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

4

13 回答 13

112

当然,可以重新启动 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. 执行它!(从而终止当前应用程序并重新启动它)
于 2010-11-16T12:42:56.577 回答
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);
    }
}

献给所有说这是不可能的人。

该程序收集所有可用于重建原始命令行的信息。然后,它会启动它,因为它是相同的命令,所以您的应用程序会再次启动。然后我们退出原始程序,子程序仍然运行(即使在 Linux 下)并且做同样的事情。

警告:如果你运行它,请注意它永远不会结束创建新进程,类似于叉子炸弹

于 2010-11-12T00:19:11.723 回答
32

基本上,你不能。至少不是以可靠的方式。但是,您不应该这样做。

不能分开_

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

  1. 找到使用的java启动器。您可以尝试使用,System.getProperty("java.home")但不能保证这实际上会指向用于启动您的应用程序的启动器。(返回的值可能不指向用于启动应用程序的 JRE,或者它可能已被 覆盖-Djava.home。)

  2. 您可能想要尊重原始内存设置等(、、、-Xmx... -Xms),因此您需要弄清楚哪些设置用于启动第一个 JVM。您可以尝试使用ManagementFactory.getRuntimeMXBean().getInputArguments(),但不能保证这将反映所使用的设置。这甚至在该方法的文档中都有说明:

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

  3. 如果您的程序从原始标准输入读取输入,Standard.in将在重新启动时丢失。

  4. 许多这些技巧和技巧在SecurityManager.

不需要的部分

我建议您设计您的应用程序,以便轻松清理所有内容,然后创建“主”类的新实例。

许多应用程序被设计成除了在主方法中创建一个实例之外什么都不做:

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。

于 2010-11-11T22:20:24.900 回答
14

严格来说,Java 程序不能重新启动自己,因为这样做它必须杀死正在其中运行的 JVM,然后重新启动它,但是一旦 JVM 不再运行(被杀死),就不能采取任何行动。

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

根据应用程序的启动方式,您可以在包含 do/while 循环的包装脚本中启动 JVM,该循环在 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 应用程序应该在不需要重新启动的“正常”终止时使用 RESTART_CODE 以外的其他内容退出 JVM。

于 2010-11-11T22:23:06.610 回答
7

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

于 2010-11-11T22:29:01.947 回答
6

视窗

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

^&完成后退出关闭 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 秒

于 2014-10-01T13:35:32.607 回答
6

只需添加其他答案中不存在的信息。

如果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 进程之一完全相同。

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

不要忘记使用ProcessBuilder.inheritIO()

默认情况下,已启动进程的stdin// (在 Java 中称为stdout// )设置为管道,允许当前运行的进程与新启动的进程进行通信。如果您想重新启动当前进程,这很可能不是您想要的。相反,您希望//与当前 VM 的相同。这称为继承。您可以通过调用您的实例来做到这一点。stderrSystem.inSystem.outSystem.errstdinstdoutstderrinheritIO()ProcessBuilder

Windows 上的陷阱

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

于 2015-03-11T22:59:45.780 回答
5

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

一些解决方案的问题在于它们构建了一个命令字符串。当某些参数包含空格时,这会产生问题,尤其是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();
        }
    }
于 2015-01-11T12:24:34.903 回答
4

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

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

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

于 2010-11-11T22:19:18.680 回答
4

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

  • 没有选择的积累JAVA_TOOL_OPTIONS
  • 自动查找主类。
  • 继承当前的标准输出/标准错误。

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]
$
于 2018-02-26T16:28:11.087 回答
3

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

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

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

每当应用程序需要重新启动时,它应该调用方法启动并退出虚拟机。Ant java 任务应该将选项forkspawn设置为 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);

}

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

于 2010-12-12T20:09:59.463 回答
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);
于 2016-05-16T03:44:21.790 回答
-14
System.err.println("Someone is Restarting me...");
setVisible(false);
try {
    Thread.sleep(600);
} catch (InterruptedException e1) {
    e1.printStackTrace();
}
setVisible(true);

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

于 2013-11-03T19:17:20.947 回答