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,就已解决了线程无法关闭的问题了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值