重启Java程序的方法

1.引言

1.1背景问题

一个spring boo应用程序每天定时执行一次。

启动时,内存占用800M,结算后占用6G。

执行后,堆内存实际使用1G,但分配的内存达6G。

调整以下JVM参数,只能降到3G,不确定是否延长了处理时间。

-XX:MaxHeapFreeRatio=10

-XX:MinHeapFreeRatio=10

目标是使程序执行后,能够恢复到接近启动时的内存消耗规模。

通过应用程序和参数调整,没有办法实现目标。

只能重启程序。

1.2概念

程序重启通过是否产生新进程开区分2种方式。

热启动:无新进程产生

冷启动:有新进程产生,原进程结束,新进程启动相同程序的进程,包括相同的参数。

 

冷启动激发分为内部,外部:

。外部重启:通过操作系统,或者资源管理系统执行cron调度策略

程序执行完毕后即退出,由外部系统按照调度策略激活

。应用程序自启:

有直接和间接两种方式。间接是通过中间程序重启,直接则是在原进程结束时重启自身。

直接方式需要在原程序释放资源完毕后执行,避免可能的端口冲突。

重启进程的命令,参数要与原进程一致,并能在运行时获取。

 

外部重启可以简单地满足需要。 这里仅从技术实现角度探究其它重启Spring boot程序方式:热启动,冷启动-应用程序自启。

 

1.3热启动

Spring Boot Actuator:

。在程序结束时调用RestartEndpoint.restart,可以重启,但内存并没有降下来

。另起线程执行RestartEndpoint.restart:未验证 。

REST API方式是一种外部调用方式,内部也是RestartEndpoint实现的. 程序内部没有必要使用

 

Spring DevTools:

。Spring DevTools有重置应用的能力,只能在开发中使用。但其思想应该可以参考。

Spring DevTools 介绍见https://www.jianshu.com/p/b2d4f83aa777

 

1.4冷启动

。构造启动命令:优雅的方式,避免平台差异和硬编码

。启动时机:在程序任务成功完成后,资源释放后。 利用Runtime.addShutdownHook。

。委托方式:通过中间程序启动

 

2.热启动

1.Spring Boot Actuator

Spring Boot Actuator的RestartEndpoint 见参考资料[1].

RestartService.java

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.context.restart.RestartEndpoint;
import org.springframework.stereotype.Service;

@Service
public class RestartService {
@Autowired
private RestartEndpoint restartEndpoint;

public void restartApp() {
restartEndpoint.restart();
}
}

调用: @Autowired private RestartService restartService;

@Override
public void run(String... var) {
    /// 需要重启时调用
   restartService.restartApp();
}

application.ymal management: endpoint: restart: enabled: true

pom.xml增加依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    <!--
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-cloud-starter</artifactId>
        <version></version>
    </dependency>
    -->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-context</artifactId>
        <version>2.0.2.RELEASE</version>
    </dependency>

下文是另起线程执行RestartEndpoint的restart的例子。

Call Spring actuator /restart endpoint from Spring boot using a java function

@Autowired
private RestartEndpoint restartEndpoint;

...

Thread restartThread = new Thread(() -> restartEndpoint.restart());
restartThread.setDaemon(false);
restartThread.start();

文中说会有异常,提示程序启动了一个线程但未终止,可能存在内存泄漏。

另起线程有必要吗?

非要另起线程,如何消除文中所说的异常? ---创建的线程怎么会没有结束呢?

RestartEndpoint的invoke和restart区别? ----invoke是提问者使用的方式.

Thread默认是用户线程还是deamon线程? ----跟父线程相同?

为什么restartThread.setDaemon(false)?

Spring Boot programmatically restart app

1.added dependecies:

compile("org.springframework.boot:spring-boot-starter-actuator")

compile("org.springframework.cloud:spring-cloud-starter:1.2.4.RELEASE")

2.autowired restartEndpoint:

import org.springframework.cloud.context.restart.RestartEndpoint;

....

@Autowired

private RestartEndpoint restartEndpoint;

  1. and invoke:

Thread thread = new Thread(new Runnable() {

@Override

public void run() {

    restartEndpoint.invoke();
}

});

thread.setDaemon(false);

thread.start();

I need new Thread because spring mvc creates daemon threads for each request

访问的场景: 。这里是在处理web请求 。请求处理线程是deamon的,不能在这个线程上执行restart吗? 。spring mvc为每个请求创建deamon线程? ---不是线程池吗? 。这个怎么用的是restartEndpoint.invoke,而不是restart呢?

2.2 REST API

Spring Boot programmatically restart app

 You need to add spring-boot-starter-actuator and Spring cloud dependencies to your application and use /restart endpoint to restart the app. Here is the documentation:

For a Spring Boot Actuator application there are some additional management endpoints:

POST to /env to update the Environment and rebind @ConfigurationProperties and log levels

/refresh for re-loading the boot strap context and refreshing the @RefreshScope beans

/restart for closing the ApplicationContext and restarting it (disabled by default)

/pause and /resume for calling the Lifecycle methods (stop() and start() on the ApplicationContext)


Once done, you can use a REST API call to restart the application (via RestTemplate or curl). This will be cleaner way to restart the app than killing it and re-running it.

3.冷启动

3.1示例

参考资料[2]代码:

在结束程序的位置调用:

Application.restartApplication(()->{});

测试结果如下:

  • 原进程结束,有新进程产生,并且占用了端口,但没有控制台输出(在console和IDE下都看不到新进程的存在),Windows资源管理器中也没有该进程ID的进程
  • 启动Notepad.exe,窗口打开,正常

没有控制台是因为

ProcessBuilder.start和Runtime.exec方法创建一个本机进程,并返回 Process 子类的一个实例。

创建的子进程没有自己的终端或控制台。

对于windows平台,可以采用cmd /c start运行方式使子进程有自己的控制台。

 Run java process in windows console from another java process

ProcessBuilder pb = new ProcessBuilder("cmd", "/k", "java", "-jar", "AnotherApp.jar");
pb.start();
// 修改为
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "start", "java", "-jar", "AnotherApp.jar");



String cmd[]={"cmd", "/c", "start", "java", "-jar", "AnotherApp.jar"};
Runtime rt=Runtime.getRuntime();
Process p=rt.exec(cmd);

Linux下呢?

---应用关心平台差异不是好事情,为什么java不提供满足此需要的api呢?

**没有必要为了本文初衷去选择这种方案。仅作为技术研究活动进行。 **

3.2委托外部程序

程序结束前,启动另外一个程序,由该程序负责在原程序结束后重新启动。

参考代码: JAVA重启自身程序

https://blog.csdn.net/yushi6310/article/details/78792548/

这种方式也有参考资料[2]代码同样的问题。

冷启动方式有以下问题:

  • 启动的进程没有控制台,输出日志如何处理
  • IDE支持:IDE环境下重启后的情况
  • 跨平台:避免特定平台的代码

所以,冷启动没有理想的方式。 网上的参考代码都是什么应用场景呢?

4参考资料

[1]Programmatically Restarting a Spring Boot Application

https://www.baeldung.com/java-restart-spring-boot-app

[2]Programmatically Restart a Java Application

https://dzone.com/articles/programmatically-restart-java

[3]Spring Boot programmatically restart app

https://stackoverflow.com/questions/45074443/spring-boot-programmatically-restart-app

[4]Understanding Java Process and Java ProcessBuilder

https://www.developer.com/java/data/understanding-java-process-and-java-processbuilder.html

5.参考代码

参考资料[2]代码

       /**
         * Sun property pointing the main class and its arguments.
         * Might not be defined on non Hotspot VM implementations.
         */
        public static final String SUN_JAVA_COMMAND = "sun.java.command";

        /**
         * Restart the current Java application
         *
         * @param runBeforeRestart some custom code to be run before restarting
         * @throws IOException
         */
        public static void restartApplication(Runnable runBeforeRestart) throws IOException {
            try {
    // java binary
                String java = System.getProperty("java.home") + "/bin/java";
    // vm arguments
                List<String> vmArguments = ManagementFactory.getRuntimeMXBean().getInputArguments();
                StringBuffer vmArgsOneLine = new StringBuffer();
                for (String arg : vmArguments) {
    // if it's the agent argument : we ignore it otherwise the
    // address of the old application and the new one will be in conflict
                    if (!arg.contains("-agentlib")) {
                        vmArgsOneLine.append(arg);
                        vmArgsOneLine.append(" ");
                    }
                }
    // init the command to execute, add the vm args
                final StringBuffer cmd = new StringBuffer("\"" + java + "\" " + vmArgsOneLine);

    // program main and program arguments
                String[] mainCommand = System.getProperty(SUN_JAVA_COMMAND).split(" ");
    // program main is a jar
                if (mainCommand[0].endsWith(".jar")) {
    // if it's a jar, add -jar mainJar
                    cmd.append("-jar " + new File(mainCommand[0]).getPath());
                } else {
    // else it's a .class, add the classpath and mainClass
                    cmd.append("-cp \"" + System.getProperty("java.class.path") + "\" " + mainCommand[0]);
                }
    // finally add program arguments
                for (int i = 1; i < mainCommand.length; i++) {
                    cmd.append(" ");
                    cmd.append(mainCommand[i]);
                }
    // execute the command in a shutdown hook, to be sure that all the
    // resources have been disposed before restarting the application
                Runtime.getRuntime().addShutdownHook(new Thread() {
                    @Override
                    public void run() {
                        try {
                            Runtime.getRuntime().exec(cmd.toString());
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                });
    // execute some custom code before restarting
                if (runBeforeRestart != null) {
                    runBeforeRestart.run();
                }
    // exit
                System.exit(0);
            } catch (Exception e) {
    // something went wrong
                throw new IOException("Error while trying to restart the application", e);
            }
        }

使用ProcessBuilder

How can I restart a Java application?

https://stackoverflow.com/questions/4159802/how-can-i-restart-a-java-application

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

获取jar文件路径

    URL url = Application.class.getProtectionDomain().getCodeSource().getLocation();
    System.out.println(url);

在IDE环境下执行(未打包成jar)

输出: file:/E:/workspace/ybt/ybt-clearing/target/classes/

如果打包后运行,则会有包名内容。

这种方式有限制,未考虑:命令参数,不支持IDE环境运行。 没有参考资料[2]考虑的全面。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值