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,就已解決了線程無法關閉的問題了