JVM 线程与进程,主线程

前言

经常JVM进程启动过程中就自动退出,但是有时候却不会,笔者也没有深究原理,直到最近处理问题,发现不知道为什么进程退出。原来JVM早就定义了规范。这对我们开发中间件会提供一种设计规范。

1. 进程退出

1.1 线程执行结束进程退出

demo如下:

public class ThreadDaemon {
    public static void main(String[] args) {
        System.out.println("main thread start...");
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("sub thread start...");
                try {
                    Thread.sleep(10*1000);
                    System.out.println("sub thread run end...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }


}

运行后:主线程执行结束,进程仍然存在,直到子线程运行结束才会退出

1) 主线程运行

2) 主线程运行结束,子线程运行,可以看到主线程已经销毁了

 

3) 进程结束

由此可见,所有线程运行结束,进程自动退出

 1.2 守护线程

 我们上个demo创建的线程是自定义的非守护线程,这里提及守护线程,是由于守护线程的定义


    /**
     * Marks this thread as either a {@linkplain #isDaemon daemon} thread
     * or a user thread. The Java Virtual Machine exits when the only
     * threads running are all daemon threads.
     *
     * <p> This method must be invoked before the thread is started.
     *
     * @param  on
     *         if {@code true}, marks this thread as a daemon thread
     *
     * @throws  IllegalThreadStateException
     *          if this thread is {@linkplain #isAlive alive}
     *
     * @throws  SecurityException
     *          if {@link #checkAccess} determines that the current
     *          thread cannot modify this thread
     */
    public final void setDaemon(boolean on) {
        checkAccess();
        if (isAlive()) {
            throw new IllegalThreadStateException();
        }
        daemon = on;
    }

核心的一句:意思是仅仅只有守护线程运行时,Java虚拟机就会退出

The Java Virtual Machine exits when the only threads running are all daemon threads. 

 来试试

public class ThreadDaemon {
    public static void main(String[] args) {
        System.out.println("main thread start...");
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("sub thread start...");
                try {
                    Thread.sleep(10*1000);
                    System.out.println("sub thread run end...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("sub thread start...");
                try {
                    Thread.sleep(10*1000000000);
                    System.out.println("sub thread daemon... run end...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread2.setDaemon(true);
        thread2.start();
    }


}

这里设置为daemon true,默认为false

执行后:

仅剩daemon线程时,进程直接退出

 

并未打印,实际上JVM自带的一些线程也是守护线程,比如

 

 

 可以看到JVM自己的回收,标记,引用线程都是守护线程,实际上我们开发基础框架或者中间件插件都建议遵循此标准,方便jvm管理线程,当然也可以自定义生命周期,就需要自己全部处理,任何环节都不能漏掉。

2. 主线程

在实际的工程运行中,主线程有时候仅用于启动的作用,比如传统Tomcat部署的应用;有时候确是核心框架的加载线程,比如spring boot的jar启动。此时如果要启动分析就需要针对设计特殊处理,不过随着Spring boot应用的大规模流行,除了比较老旧的应用,基本上都是主线程加载核心逻辑,一般而言分析主线程即可。

我启动了一个Tomcat(传统的)

打印线程

可以看到主线程

"main" #1 prio=5 os_prio=31 cpu=993.13ms elapsed=108.87s tid=0x00007fbe9700b600 nid=0x1c03 runnable  [0x00007000018a2000]
   java.lang.Thread.State: RUNNABLE
        at sun.nio.ch.Net.accept(java.base@17/Native Method)
        at sun.nio.ch.NioSocketImpl.accept(java.base@17/NioSocketImpl.java:755)
        at java.net.ServerSocket.implAccept(java.base@17/ServerSocket.java:675)
        at java.net.ServerSocket.platformImplAccept(java.base@17/ServerSocket.java:641)
        at java.net.ServerSocket.implAccept(java.base@17/ServerSocket.java:617)
        at java.net.ServerSocket.implAccept(java.base@17/ServerSocket.java:574)
        at java.net.ServerSocket.accept(java.base@17/ServerSocket.java:532)
        at org.apache.catalina.core.StandardServer.await(StandardServer.java:602
)
        at org.apache.catalina.startup.Catalina.await(Catalina.java:864)
        at org.apache.catalina.startup.Catalina.start(Catalina.java:810)
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@17/Na
tive Method)
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@17/Nat
iveMethodAccessorImpl.java:77)
        at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@17
/DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(java.base@17/Method.java:568)
        at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:345)
        at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:476)

查看源码,果然

 

然而嵌入式Tomcat又是另外一种情况,spring boot的应用

打印堆栈,然而找不到主线程,说明主线程销毁了

 

3. 主线程阻塞分析

一般而言,启动应用时阻塞,此时除非看日志,或者打标记等,很难知道是否阻塞,哪个业务阻塞了主线程。如果是非主线程启动应用,那么怎么分析了

比如传统Tomcat,写一个sleep方法

import javax.servlet.*;
import java.io.IOException;

public class DemoFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("------------init ------------===============");
        try {
            Thread.sleep(Long.parseLong("1000000000"));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

    }
}

web.xml

    <filter>  
        <filter-name>filter</filter-name>  
        <filter-class>DemoFilter</filter-class>  
        
    </filter>  
    <filter-mapping>  
        <filter-name>filter</filter-name>  
        
        <url-pattern>/*</url-pattern>  
    </filter-mapping> 

启动Tomcat后,有如下日志:对于Tomcat 8

 jstack可以看到是localhost-startStop-1这个线程在加载

 这种就很难排查了,幸好Tomcat打印了日志,如果没有,只能看Tomcat源码

对于Tomcat9 或者 嵌入式的Tomcat

 直接main方法去执行,可能Tomcat也考虑到这点了,主线程即加载线程,boot demo一个

@Component
public class DemoBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("--------------- init ====================");
        Thread.sleep(Long.parseLong("100000000"));
    }
}

 

从Tomcat9开始就统一了,main线程加载框架,中间件等

总结

经过分析发现:如果运行的所有线程是守护线程,那么jvm就退出了,进程将结束。

另外Tomcat 8的传统版,不是main线程加载业务,当定位启动阻塞的时候就需要看日志,找到相关的线程,其他容器同理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值