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


94

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

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


2
也许我不明白你的问题。您希望您的应用程序具有一个重新启动应用程序的按钮吗?因此,在应用程序不再运行后,它应该能够重新启动吗?这对我来说听起来是不可能的。
杰伊(Jay)

我不是问JVM停止后,我问我如何重新生成主要的Java框架?
Azfar Niaz 2010年

2
不是不可能。我看到Eclipse工作台频繁重启,即使Windows在更新后也能做到这一点。错误的假设是,应用程序是唯一在其下无内容运行的东西。我们将需要一个具有重启功能的启动器,将海龟一直降下来。
whatnick

就像在C#应用程序中一样,您可以在其中编写System.restart()来执行此操作?
Azfar Niaz 2010年

@aniaz,然后您应该更新问题以指出要显示/隐藏框架。该应用程序不是框架。
whatnick

Answers:


105

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

5
在同一应用程序的两个版本同时运行的时间不短吗?
Monir 2014年

5
System.exit(0)不会终止子进程吗?
Horcrux15年

16
@Veger问题是否System.exit(0)终止子进程的答案与该答案是否确实有效以及原因相同。如果您无法在回答中提供合理的解释,则说明您做得不好。提供的问题多于答案的答案不是彻底答案的示例。好的答案不仅会显示代码,而且还会解释它们的工作方式和原因,缺点以及替代方法是什么。您甚至都没有尝试涵盖这些内容。
托马什Zato -恢复莫妮卡

8
如此多的评论辩论是否要回答@ Horcrux7的问题。你们可以从一开始就告诉他答案。好吧,我会继续做下去(我知道有点晚了):不,不是。那里。
Voldemort 2015年

10
自我回答我的问题。该示例不起作用!System.exit(0)立即终止客户端进程。
Horcrux15年

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下),并且执行相同的操作。

警告:如果运行此命令,请注意它永远不会停止创建新进程,类似于fork炸弹


可能的改进 ManagementFactory.getRuntimeMXBean().getInputArguments()只会为您提供传递给JVM的输入参数。它错过了传递给您的应用程序的参数。例如java -jar start.jar -MISSED_PARAM=true。在oracle jvm上,您可以使用检索这些参数System.getProperty("sun.java.command")
Chris2M 2014年

1
如果子虚拟机和父虚拟机不会通过管道相互连接,则父虚拟机可能会终止,这就是子虚拟机启动的方式。通过使用ProcessBuilderinheritIO(),可以以终止父VM的方式启动子VM。
Christian Hujer,2015年

1
我有一个版本。此注释旨在告诉您如何停止它:在包含java.exe的路径中重命名某些内容。
戴尔

严格来说,这不是重新启动,而是使用与此参数相同的参数启动新的JVM。
托尔比约恩Ravn的安德森

4
有什么不同?重新启动PC和关闭OS再重新启动PC之间有区别吗?
Meinersbur

27

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

不能部分

要重新启动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. 如果存在此类错误,许多招数和hacks将会失败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。


3
+1以获得更好的设计建议;尽管有时是不可能的,尤其是在使用JNI的情况下。
maerics 2010年

好吧,本机库可以修改无法从JNI接口修改的全局状态,因此除了重新启动进程外,没有其他方法可以“重新启动”程序的状态。当然,应该更好地设计本机库,但是有时您依赖无法控制的内容。
maerics 2010年

好的,但是有了这种推理,您也可以拥有一个纯Java库来修改一些内部静态变量。但是,这将是设计缺陷,不应在编写良好的库中发生。
aioobe

1
您的答案是错误的,因为即使没有外部应用程序/守护程序(如Meinersbur和我自己的答案所示),也完全有可能。出于自我更新的目的,重新启动应用程序是一个很好的解决方案,因此实际上也需要重新启动应用程序。
Veger 2010年

1
但是你使用外部应用程序:java!您忘记了Java是语言规范,而不是程序。如果我使用其他jvm(例如kaffe)运行您的程序怎么办?无论如何更新了我的答案:-)
aioobe 2010年

9

严格来说,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。不确定是否有简便的方法可以解决此问题。
丹·罗森斯塔克

7

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


5

视窗

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秒



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

3

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

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

基本上,一切都归结为一个单一的Java执行任务的Ant脚本文件(参见这里这里)从Java代码中调用(参见这里)。此Java代码(可以作为方法启动)可能是需要重新启动的应用程序的一部分。该应用程序需要依赖于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>

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

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

}

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


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

如今,包括GNU / Linux(包括Android)在内的许多UNIX系统都具有procfs。但是在FreeBSD之类的系统上,它已被弃用并逐步淘汰。Mac OS X没有procfs的意义上是一个例外。Windows也没有procfs。Cygwin具有procfs但是对Java不可见,因为它仅对使用Cygwin DLL而不是Windows系统调用的应用程序可见,并且Java不了解Cygwin。

别忘了使用 ProcessBuilder.inheritIO()

缺省值是将已启动的Process的stdin/ stdout/ stderr(在Java中称为System.in/ System.out/ System.err)设置为管道,以允许当前正在运行的进程与新启动的进程进行通信。如果要重新启动当前进程,则很可能不是您想要的。相反,你想的是stdin/ stdout/ stderr是相同当前VM的。这称为继承。你可以通过调用这样做inheritIO()你的ProcessBuilder实例。

Windows上的陷阱

restart()函数的一个常见用例是在更新后重新启动应用程序。我上次在Windows上尝试此操作时遇到了问题。当.jar使用新版本覆盖应用程序的文件时,该应用程序开始行为不当并给出有关该.jar文件的异常信息。我只是说说,以防万一这是您的用例。那时,我通过将应用程序包装在一个批处理文件中并使用System.exit()我在该批处理文件中查询的魔术返回值并让该批处理文件重新启动应用程序来解决了该问题。


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

2

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

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

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

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


4
用户要求重新启动应用程序,而不仅仅是隐藏和显示窗口。
Amr Lotfy 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.