天天看點

tomcat通過shutdown 關閉後,發現tomcat線程依然在,netty綁定的socket線程也是

netty版本:4.1.45.Final

tomcat版本:tomcat-9.0.31

springboot版本:2.2.5.RELEASE

場景:

在springboot中使用netty進行socket udp通信,

項目在開發環境是沒有問題的,通過maven打成war包布到測試環境的tomcat上,

然後shutdown tomcat,發現tomcat程序還在,netty程序還在,檢視Catalina日志:

13-May-2020 14:23:42.635 警告 [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads Web應用程式[xxx]似乎啟動了一個名為[Thread-5]的線程,但未能停止它。這很可能會造成記憶體洩漏。線程的堆棧跟蹤:[
 java.lang.Object.wait(Native Method)
 java.lang.Object.wait(Object.java:502)
 io.netty.util.concurrent.DefaultPromise.await(DefaultPromise.java:252)
 io.netty.channel.DefaultChannelPromise.await(DefaultChannelPromise.java:131)
 io.netty.channel.DefaultChannelPromise.await(DefaultChannelPromise.java:30)
 io.netty.util.concurrent.DefaultPromise.sync(DefaultPromise.java:403)
 io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:119)
 io.netty.channel.DefaultChannelPromise.sync(DefaultChannelPromise.java:30)
 icbc.com.logcollection.netty.BootNettyServer.lambda$0(BootNettyServer.java:48)
 icbc.com.logcollection.netty.BootNettyServer$$Lambda$304/1460920929.run(Unknown Source)
 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
 java.lang.Thread.run(Thread.java:748)]
13-May-2020 14:23:42.650 警告 [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads Web應用程式[xxx]似乎啟動了一個名為[Thread-6]的線程,但未能停止它。這很可能會造成記憶體洩漏。線程的堆棧跟蹤:[
 icbc.com.logcollection.serviceImpl.SaveLogServiceImpl.getStore(SaveLogServiceImpl.java:94)
 icbc.com.logcollection.serviceImpl.SaveLogServiceImpl.lambda$0(SaveLogServiceImpl.java:85)
 icbc.com.logcollection.serviceImpl.SaveLogServiceImpl$$Lambda$305/1283595719.run(Unknown Source)
 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
 java.lang.Thread.run(Thread.java:748)]
13-May-2020 14:23:42.650 警告 [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads Web應用程式[xxx]似乎啟動了一個名為[bootNettyServer-1-1]的線程,但未能停止它。這很可能會造成記憶體洩漏。線程的堆棧跟蹤:[
 sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
 sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)
 sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)
 sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)
 sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
 sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
 sun.nio.ch.SelectorImpl.select(SelectorImpl.java:101)
 io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:68)
 io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:803)
 io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:457)
 io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
 io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
 io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
 java.lang.Thread.run(Thread.java:748)]
13-May-2020 14:23:42.650 警告 [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads Web應用程式[xxx]似乎啟動了一個名為[nioEventLoopGroup-3-1]的線程,但未能停止它。這很可能會造成記憶體洩漏。線程的堆棧跟蹤:[
 sun.nio.ch.WindowsSelectorImpl$SubSelector.poll0(Native Method)
 sun.nio.ch.WindowsSelectorImpl$SubSelector.poll(WindowsSelectorImpl.java:296)
 sun.nio.ch.WindowsSelectorImpl$SubSelector.access$400(WindowsSelectorImpl.java:278)
 sun.nio.ch.WindowsSelectorImpl.doSelect(WindowsSelectorImpl.java:159)
 sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
 sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
 sun.nio.ch.SelectorImpl.select(SelectorImpl.java:101)
 io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:68)
 io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:803)
 io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:457)
 io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
 io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
 io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
 java.lang.Thread.run(Thread.java:748)]


           

報錯還有線程沒停掉!!!

然後想到應該是線程池中的線程是非守護線程,于是把線程池中的線程設定成守護線程

public class MyThreadFactory implements ThreadFactory {
	
	@Override
	public Thread newThread(Runnable r) {
		Thread thread = new Thread(r);
		thread.setDaemon(true); //設定守護線程
		return thread;
	}

}
           
public static ThreadPoolExecutor getThreadPool() {
        if (threadPool != null) {
            return threadPool;
        } else {
            synchronized (ThreadPoolUtil.class) {
                if (threadPool == null) {
                    threadPool = new ThreadPoolExecutor(8, 16, 60, TimeUnit.SECONDS,
                            new LinkedBlockingQueue<>(32),new MyThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
                }
                return threadPool;
            }
        }
    }
           

然後,發現沒有什麼用!!!然後各種在網上找資料關閉netty線程,方法試了個遍,都不行,然後隻能看netty源碼,

自己寫的netty代碼

ThreadPoolUtil.execute(() -> {
// 經檢視源碼發現這樣寫是建立了一個非守護線程,啊!!蒼天啊!!之前找到的都是說Bootstrap資源的釋放,原來是方向找錯了
			EventLoopGroup bossGroup = new NioEventLoopGroup(); 
			try {
				Bootstrap b = new Bootstrap();
				b.group(bossGroup).channel(NioDatagramChannel.class)
			       .option(ChannelOption.SO_BROADCAST, true) // SO_BROADCAST 廣播方式
					// 讀緩沖區
					.option(ChannelOption.SO_RCVBUF, 2048 * 1024)
					// 寫緩沖區
					.option(ChannelOption.SO_SNDBUF, 1024 * 1024).handler(new BootNettyChannelInitializer());
				 b.bind(port).sync().channel().closeFuture().sync();
				

			} catch (Exception e) {
				log.error(e.toString());
			} finally {
				b.shutdownGracefully();
			}
		});
           

然後,把NioEventLoopGroup設定為守護線程

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.util.concurrent.DefaultThreadFactory;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.ThreadPerTaskExecutor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import icbc.com.logcollection.util.MyThreadFactory;
import icbc.com.logcollection.util.ThreadPoolUtil;

import javax.annotation.PostConstruct;

/**
 * netty的server
 */
ThreadPoolUtil.execute(() -> {
			EventLoopGroup bossGroup = new NioEventLoopGroup(new MyThreadFactory()); // 建立一個守護線程
			try {
				Bootstrap b = new Bootstrap();
				b.group(bossGroup).channel(NioDatagramChannel.class)
			       .option(ChannelOption.SO_BROADCAST, true) // SO_BROADCAST 廣播方式
					// 讀緩沖區
					.option(ChannelOption.SO_RCVBUF, 2048 * 1024)
					// 寫緩沖區
					.option(ChannelOption.SO_SNDBUF, 1024 * 1024).handler(new BootNettyChannelInitializer());
				 b.bind(port).sync().channel().closeFuture().sync();
				

			} catch (Exception e) {
				log.error(e.toString());
			} finally {
				b.shutdownGracefully();
			}
		});
           

然後,依然不行。。。,就以為不是非守護線程的原因,又是各種找,後來發現,看漏了,還有一個地方沒有設定守護線程,哎

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.handler.codec.string.StringDecoder;

import java.nio.charset.Charset;

import icbc.com.logcollection.util.MyThreadFactory;

/**
 * 通道初始化
 */
public class BootNettyChannelInitializer extends ChannelInitializer<NioDatagramChannel> {
 //看漏這裡了,也要設定成守護線程
    private EventLoopGroup group = new NioEventLoopGroup(new MyThreadFactory());  // 建立一個守護線程

    @Override
    protected void initChannel(NioDatagramChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(group, new BootNettyChannelInboundHandlerAdapter());
    }

}
           
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import icbc.com.logcollection.mq.MessageQueueManager;

import java.nio.charset.Charset;

/**
 * I/O資料讀寫處理類
 */
public class BootNettyChannelInboundHandlerAdapter extends SimpleChannelInboundHandler<DatagramPacket> {

    private final Logger log = LoggerFactory.getLogger(BootNettyChannelInboundHandlerAdapter.class);

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
        String msg = packet.content().toString(Charset.forName("UTF-8"));
        MessageQueueManager.add(msg);
    }
}
           

好啦,此時再shutdown tomcat,就已解決了線程無法關閉的問題了