web-dev-qa-db-ja.com

Javaアプリケーションを再起動するにはどうすればよいですか?

Java AWTアプリケーションを再起動するにはどうすればよいですか?イベントハンドラーをアタッチしたボタンがあります。アプリケーションを再起動するには、どのコードを使用する必要がありますか?

C#アプリケーションでApplication.Restart()するのと同じことをしたい。

90
Azfar Niaz

もちろん、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. アプリケーションを見つけます(私の場合はMyClassInTheJarクラスを使用してjarの場所自体を見つけるjar)。
  3. Jarを再起動するコマンドを作成します(この場合はJavaバイナリを使用)
  4. 実行してください! (したがって、現在のアプリケーションを終了して再起動します)
102
Veger
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);
    }
}

それは不可能だと言うすべての人々に捧げます。

このプログラムは、元のコマンドラインを再構築するために利用可能なすべての情報を収集します。次に、それを起動し、まったく同じコマンドであるため、アプリケーションが2回起動します。その後、元のプログラムを終了し、子プログラムは(Linuxでも)実行されたままで、まったく同じことを行います。

WARNING:これを実行する場合、 fork bomb と同様に、新しいプロセスの作成が終了しないことに注意してください。

34
Meinersbur

基本的にできません。少なくとも信頼できる方法ではありません。

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が存在すると失敗します。


一方、する必要はありません。

すべてのものを簡単にクリーンアップできるようにアプリケーションを設計し、その後で「メイン」クラスの新しいインスタンスを作成することをお勧めします。

多くのアプリケーションは、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を返すようにします。

22
aioobe

厳密に言うと、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を終了する必要があります。

8
maerics

通常、Eclipseはプラグインのインストール後に再起動します。 WindowsのラッパーEclipse.exe(ランチャーアプリ)を使用してこれを行います。このアプリケーションはコアEclipseランナーjarを実行し、Eclipse Javaアプリケーションが再起動コードで終了した場合、Eclipse.exeはワークベンチを再起動します。再起動を実現するために、同様のネイティブコード、シェルスクリプト、または別のJavaコードラッパーをビルドできます。

7
whatnick

アプリを本当に再起動する必要がある場合、別のアプリを作成して起動することができます...

このページには、さまざまなシナリオのさまざまな例があります。

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

4
Sean W.

この質問は古くて回答がありましたが、いくつかのソリューションの問題に出くわし、私の提案をミックスに追加することにしました。

一部のソリューションの問題は、単一のコマンド文字列を作成することです。これにより、一部のパラメーターにスペースが含まれる場合、特に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();
        }
    }
4
Malt

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

sleep 1 10秒間スリープ

4
Amr Lotfy

他の回答にはない情報を追加するだけです。

procfs /proc/self/cmdlineが利用可能な場合

procfs を提供し、したがって/procファイルシステムを使用できる環境で実行している場合(これはポータブルソリューションではないことを意味します) 、次のように自分自身を再起動するためにJava read /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 がありますが、Windowsシステムコールの代わりにCygwin DLLを使用するアプリケーションにのみ表示されるため、Javaには見えず、JavaはCygwinを知らない。

ProcessBuilder.inheritIO()の使用を忘れないでください

デフォルトでは、開始されたプロセスのstdin/stdout/stderr(in Java called System.in/System.out/System.err)はに設定されます。 pipes は、現在実行中のプロセスが新しく開始されたプロセスと通信できるようにします。現在のプロセスを再起動する場合、これはであり、ほとんどの場合は必要なものではありません。代わりに、stdin/stdout/stderrが現在のVMのものと同じであることを望むでしょう。これは inherited と呼ばれます。それには、ProcessBuilderインスタンスのinheritIO()を呼び出します。

Windowsの落とし穴

restart()関数の頻繁な使用例は、更新後にアプリケーションを再起動することです。 Windowsでこれを最後に試したとき、これは問題がありました。アプリケーションの.jarファイルを新しいバージョンで上書きすると、アプリケーションは誤動作を開始し、.jarファイルに関する例外が発生しました。これがあなたのユースケースである場合に備えて、私はただ伝えています。当時、アプリケーションをバッチファイルでラップし、System.exit()からのマジックリターン値を使用して、バッチファイルでクエリを実行し、代わりにバッチファイルにアプリケーションを再起動させることで問題を解決しました。

3
Christian Hujer

この質問に出くわしたとき、私は自分で主題を研究していました。

答えがすでに受け入れられているという事実にかかわらず、完全性のための代替アプローチを提供したいと思います。特に、Apache Antは非常に柔軟なソリューションとして機能しました。

基本的に、すべてが1つのJava実行タスクを持つAntスクリプトファイルに要約されます( here および here を参照) Javaコードから呼び出されます( here を参照)。このJavaコードはメソッドlaunchである可能性があり、再起動が必要なアプリケーションの一部である可能性があります。アプリケーションは、Apache Antライブラリー(jar)に依存する必要があります。

アプリケーションを再起動する必要があるときはいつでも、メソッドlaunchを呼び出して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);

}

ここで非常に便利なことは、アプリケーションの初期起動と再起動に同じスクリプトが使用されることです。

3
01es

ヨーダの「 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が設定されていない場合のnullポインター


例:

$ 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]
$
2
Mark Jeronimus

古い質問とそのすべて。しかし、これはいくつかの利点を提供するさらに別の方法です。

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
Dale