【问题标题】:How can I restart a Java application?如何重新启动 Java 应用程序?
【发布时间】:2010-11-11 22:15:33
【问题描述】:

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

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

【问题讨论】:

  • 也许我不明白你的问题。您希望您的应用程序有一个重新启动应用程序的按钮吗?那么,应用程序不再运行后,它应该能够自行重启吗?这对我来说听起来是不可能的。
  • 我不是问JVM停止后,我问的是如何重新生成我的主java框架?
  • 并非不可能。我看到 Eclipse 工作台经常重新启动,甚至 Windows 在更新后也会这样做。错误的假设是应用程序是唯一运行的东西,它下面没有任何东西。我们需要一个能够重启的启动器,海龟一直向下。
  • 和 C# 应用程序一样,你可以编写 System.restart() 来这样做?
  • @aniaz 那么您应该更新问题以指出您想要显示/隐藏框架。应用程序不是框架。

标签: java restart application-restart


【解决方案1】:

当然可以重新启动 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. 执行它! (从而终止当前应用程序并重新启动它)

【讨论】:

  • 同一个应用的两个版本同时运行的时间框架不是很小吗?
  • @Veger 所有问题都与答案非常相关。
  • @Veger 问题System.exit(0) 是否终止子进程与这个答案是否真的有效以及为什么有相同的答案。如果您不能提供合理的解释以及您的答案,那么您做得不好。提供的问题多于答案的答案并不是彻底答案的示例。好的答案不仅显示代码,还解释它们如何工作以及为什么工作,缺点是什么以及替代方案是什么。你甚至没有试图掩盖这些事情。
  • 这么多 cmets 争论是否回答 @Horcrux7 的问题。你们可以从一开始就告诉他答案,哈哈。好吧,我会继续做(我知道有点晚了):不,它没有。在那里。
  • 自己回答我的问题。样品不起作用! System.exit(0) 立即终止客户端进程。
【解决方案2】:
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

【讨论】:

  • 可能的改进 ManagementFactory.getRuntimeMXBean().getInputArguments() 只会为您提供传递给 JVM 的输入参数。它错过了传递给您的应用程序的参数。例如,java -jar start.jar -MISSED_PARAM=true。在 oracle jvm 上,您可以使用 System.getProperty("sun.java.command") 检索这些参数。
  • 如果子虚拟机和父虚拟机不能通过管道相互连接,父虚拟机可能会结束,这就是子虚拟机启动的方式。通过使用ProcessBuilderinheritIO(),子虚拟机可以以父虚拟机结束的方式启动。
  • 我得到了这个版本。此注释是告诉您如何停止它:重命名包含 java.exe 的路径中的某些内容。
  • 这严格来说不是重新启动,而是启动一个新的 JVM,参数和这个相同。
  • 有什么区别?重启电脑和关闭操作系统+重新启动有区别吗?
【解决方案3】:

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

不能部分

要重新启动 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 读取输入,原始标准输入将在重新启动时丢失。

  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。

【讨论】:

  • +1 以获得更好的设计建议;不过,有时这是不可能的,尤其是在使用 JNI 时。
  • 好吧,原生库可以修改无法从 JNI 接口修改的全局状态,因此除了重新启动进程之外,没有办法“重新启动”程序的状态。当然,原生库应该设计得更好,但有时你依赖于你无法控制的东西。
  • 您的答案不正确,因为即使没有 Meinersbur 和我自己的答案所示的外部应用程序/守护程序,也完全有可能。并且出于自我更新的目的,重新启动应用程序是一个很好的解决方案,因此实际上也需要重新启动应用程序。
  • 但是您确实使用外部应用程序:java!您忘记了 Java 是一种语言规范,而不是程序。如果我使用其他一些 jvm 运行您的程序,例如 kaffe,会怎样?无论如何更新了我的答案:-)
  • @Lorenzo,这是正确的。这里的原始问题基本上是应用程序是否可以自行重启。答案基本上是,您需要其他人(如操作系统、包装脚本或类似的东西)的合作来生成新实例。
【解决方案4】:

严格来说,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。

【讨论】:

  • 非常有趣的解决方案。 OSX 上的问题是,通常情况下,Java 应用程序是从已编译的 JavaApplicationStub 运行的...不确定是否有简单的方法。
  • 我在windows中做了这个并且效果很好,我有一个批处理脚本,它最初通过java my-program.java运行我的程序,批处理文件的最后一行是if "%ERRORLEVEL%" EQU "1" java my-program.java所以如果我的程序以System.exit(1) 退出,它将重新启动,否则System.exit(0) 使其关闭。虽然我之前能够使用纯 java 来做到这一点(添加关闭挂钩 + 使用 processbuilder.inheritIO() 到 stdin/out 被传递给新进程);这种方法更容易
【解决方案5】:

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

【讨论】:

    【解决方案6】:

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

    /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 秒

    【讨论】:

      【解决方案7】:

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

      如果 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/stdout/stderr(在Java中称为System.in/System.out/System.err)设置为管道允许当前运行的进程与新启动的进程通信。如果您想重新启动当前进程,这很可能不是您想要的。相反,您希望 stdin / stdout / stderr 与当前 VM 的相同。这称为继承。您可以通过调用 ProcessBuilder 实例的 inheritIO() 来实现。

      Windows 上的陷阱

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

      【讨论】:

      • 我在 windows 上的解决方案是将新生成的 .jar 文件从构建目录 build\libs\my.jar 复制到当前工作目录,然后运行该文件。这避免了我覆盖当前正在运行的 jar(因为我在重新启动之间自动重新编译了我的 jar);而且这种方法还有助于在更新/编译失败的情况下始终拥有一个可运行的 jar;重启只会运行之前编译的jar。
      • 我自己(还没有)尝试过,但我相信这种 procfs 技术也可以利用 /proc/self/environ(重新创建完全相同的环境,包括非导出变量)和 /proc/self /exe也是
      【解决方案8】:

      尽管这个问题已经过时且已得到解答,但我偶然发现了一些解决方案的问题,并决定将我的建议添加到组合中。

      一些解决方案的问题在于它们构建了一个命令字符串。当某些参数包含空格时,这会产生问题,尤其是 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 中的空格,此字符串必须用引号括起来或转义。不是一个大问题,但有些烦人且容易出错,尤其是在跨平台应用程序中。

      因此,我的解决方案将命令构建为 array 命令:

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

      【讨论】:

        【解决方案9】:

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

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

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

        【讨论】:

          【解决方案10】:

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

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

          【讨论】:

            【解决方案11】:

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

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

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

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

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

            }

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

            【讨论】:

              【解决方案12】:

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

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

              【讨论】:

                【解决方案13】:
                System.err.println("Someone is Restarting me...");
                setVisible(false);
                try {
                    Thread.sleep(600);
                } catch (InterruptedException e1) {
                    e1.printStackTrace();
                }
                setVisible(true);
                

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

                【讨论】:

                • 用户要求重新启动应用程序,而不仅仅是隐藏和显示一个窗口。
                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多