关于JVM中运行的应用程序何时被关闭退出呢

最近在调研一个分布式分片的任务调度框架的事情, 接触到了 Xxl-job, 在运行这个项目的服务案例时, 作者提供了一个spring-web形式的案例工程, clone到本地编译运行确实没有问题, 同时也部署了中心化管理服务Xxl-job-admin, 还用手动即时触发的方式调度了执行器服务中的任务逻辑;
然后在查阅项目文档时, 作者说明了执行器服务内部是启动了Jetty服务器与调度中心进行通信, 这里就有疑惑了, 既然是内部Jetty通信的方式, 那为何还要以servlet-web项目的形式部署到tomcat容器中呢?直接打包执行器项目为jar, 以普通java应用的方式启动不是更方便吗

通常我们运行一个java程序, 是从一个类的main方法为入口。背后就是JVM启动一个独立的非守护线程(non-daemon), 去执行我们的 static main 方法, 当出现以下情况, 应用程序线程就会被JVM关闭结束掉;

  1. 应用程序main方法执行完成并返回后, 应用内不存在其他用户线程, JVM就会关闭结束应用
  2. 应用程序内部调用System.exit(0);, 这样无论应用内是否存在用户线程, 守护线程, 都会强制退出;

再来说说上面, 我想通过直接执行一个static main方法的启动执行器服务(基于spring的), 所以写了下面的代码

import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 应用程序启动入口
 * @author Hinsteny
 * @version $ID: MyJobApplication 2018-06-29 15:01 All rights reserved.$
 */
public class MyJobApplication {

    /**
     * start spring container
     * @param args
     */
    public static void main(String[] args) throws InterruptedException {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"appcontext-xxl-job.xml"});
        context.start();
        //Thread.sleep(1000);
    }

}

// "appcontext-xxl-job.xml"
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">

    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="fileEncoding" value="utf-8" />
        <property name="locations">
            <list>
                <value>classpath*:xxl-job-executor.properties</value>
            </list>
        </property>
    </bean>

    <!-- ********************************* 基础配置 ********************************* -->

    <!-- 配置01、JobHandler 扫描路径 -->
    <context:component-scan base-package="org.hisoka.job" />

    <!-- 配置02、执行器 -->
    <bean id="xxlJobExecutor" class="com.xxl.job.core.executor.XxlJobExecutor" init-method="start" destroy-method="destroy" >
        <!-- 执行器注册中心地址[选填],为空则关闭自动注册 -->
        <property name="adminAddresses" value="${xxl.job.admin.addresses}" />
        <!-- 执行器AppName[选填],为空则关闭自动注册 -->
        <property name="appName" value="${xxl.job.executor.appname}" />
        <!-- 执行器IP[选填],为空则自动获取 -->
        <property name="ip" value="${xxl.job.executor.ip}" />
        <!-- 执行器端口号[选填],为空则自动获取 -->
        <property name="port" value="${xxl.job.executor.port}" />
        <!-- 访问令牌[选填],非空则进行匹配校验 -->
        <property name="accessToken" value="${xxl.job.accessToken}" />
        <!-- 执行器日志路径[选填],为空则使用默认路径 -->
        <property name="logPath" value="${xxl.job.executor.logpath}" />
        <!-- 日志保存天数[选填],值大于3时生效 -->
        <property name="logRetentionDays" value="${xxl.job.executor.logretentiondays}" />
    </bean>


</beans>

然后运行发行, 应用程序执行完main函数就关闭了, 死活不能起起来后持续运行, 到底啥原因呢?

一番查找后, 大概是这样的, 上面的xml配置文件中, 是调度框架作者在执行器服务的初始化过程中启动了一个守护线程, 内部有启动jetty服务器的操作, 然后jetty内部的启动逻辑中, 又启动了一些列的守护线程(jvm退出监听的)和用户线程(监听端口的), 这样的话, 那我给主程序的main函数最后一句加一行休眠, 一秒钟已经足够jetty内部相关启动程序执行, 创建出用户线程了, 所以休眠一秒后, 虽然main方法结束退出了, 但是应用还是在持续运行啦!

给出个例子看看应用程序到底哪些情况会持续运行不退出呢

package org.hinsteny.jvm.commons;

import java.time.LocalDateTime;

/**
 * 一个运行在jvm中的应用程序, 当所有用户线程退出后, 守护线程也就退出了, 然后应用程序便会关闭结束
 * ::只要程序中有一个用户线程, 应用程序就不会被jvm关闭结束;
 * ::只要程序中只剩有一个守护线程, 应用程序就会被jvm关闭结束;
 *
 * @author Hinsteny
 * @version $ID: MyAPP 2018-06-30 13:17 All rights reserved.$
 */
public class MyAPP {

    public static void main(String[] args) {
        System.out.println("starting app main tread, am i daemon " + Thread.currentThread().isDaemon());
//        startDaemonThread();
        startUserThread();
        System.out.println("stop app main thread ");
//        System.exit(0);
    }

    private static void startDaemonThread() {
        Thread app = new Thread(() -> {
            System.out.println("I am daemon thread, i started");
            try {
                while (true) {
                    Thread.sleep(100000);
                    System.out.println(LocalDateTime.now());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("I am daemon thread, i am dead!!!");
        });
        app.setDaemon(true);
        app.start();
    }

    private static void startUserThread() {
        new Thread(() -> {
            System.out.println("I am user thread, i started");
            try {
                while (true) {
                    System.out.println(LocalDateTime.now());
                    Thread.sleep(1000);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("I am user thread, i am dead!!!");
        }).start();
    }

}

猜你喜欢

转载自blog.csdn.net/HinstenyHisoka/article/details/80867213