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