在最近的项目的运用了netty5.0,之后就发现过两天tomcat就莫名的内存消耗增大,或者直接就进程死掉了。
跟踪了很久发现是netty5在任务线程组里面很多的没有进行回收,然后又创建了很多新的任务线程。但也不是一次性就创建了很多,慢慢的增加的。
通过VisualVM追踪发现很多的nioEventLoopGroup线程,而且很明显的使用中线程不会增加,但是闲下来了,慢慢就会增加。非常奇怪,应该是netty5的一个bug,
我并没有跟踪源码去看,换到4.1之后所有的问题就自然消失了。
附带我netty4.1的客服端代码,有断线重连和空闲发送心跳包,因为是和设备交互,所有的数据都是转码后的。
package com.local.hr.tcp;
import org.apache.log4j.Logger;
import com.dashu.client.NettyClientBootstrap;
import com.local.hr.udp.UdpClientSocket;
import com.local.hr.util.ReturnEntity;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.timeout.IdleStateHandler;
public class NettyClient {
public static Integer clientId = 0;
private Logger log = Logger.getLogger(NettyClientBootstrap.class);
private int port;
private String host;
private String ip;
private String mac;
private SocketChannel channel;
private String userName;
private String password;
private Bootstrap bootstrap;
public boolean isopen = true;
public NettyClient(String host, int port, String mac,String userName,String password) throws InterruptedException {
String ip ="";
try {
ReturnEntity entity = UdpClientSocket.getIP(mac, userName, password);//通过udp获取主机地址
if (entity!=null) {
ip =entity.getIp();
}
} catch (Exception e) {
e.printStackTrace();
}
this.userName=userName;
this.password = password;
this.port = port;
if (ip!=null&&!"".equals(ip)) {
this.host = ip;
}else{
this.host = host;
}
this.mac = mac;
start(this);
}
public String getMac() {
return mac;
}
private void start(final NettyClient client) throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.group(eventLoopGroup);
bootstrap.remoteAddress(host, port);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new IdleStateHandler(150, 150, 300));// 空闲时间
socketChannel.pipeline().addLast(new ByteArrayEncoder());
socketChannel.pipeline().addLast(new NettyClientHandler(mac, host, client));
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
if (future.isSuccess()) {
this.channel = (SocketChannel) future.channel();
isopen = true;
log.info("connect server 成功---------");
}
}
public void doConnect() {
try {
String ip ="";
try {
ReturnEntity entity = UdpClientSocket.getIP(mac, userName, password);
if (entity!=null) {
ip =entity.getIp();
}
} catch (Exception e) {
e.printStackTrace();
}
if (ip!=null&&!"".equals(ip)&&!this.host.equals(ip)) {
this.host=ip;
}
ChannelFuture future = bootstrap.connect(host, port).sync();
this.channel = (SocketChannel) future.channel();
isopen = true;
} catch (Exception e) {
log.info("断线重连失败: " + e.getMessage());
}
}
public SocketChannel getChannel() {
return channel;
}
public boolean isopen() {
return isopen;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public static void main(String[] args) throws InterruptedException {
NettyClient client = new NettyClient("192.168.10.17", 9090, "80.24.00.00", "", "");
Thread.sleep(3000);
for (int i = 0; i < 10000; i++) {
client.channel.writeAndFlush("你好" + i);
Thread.sleep(3000);
}
Thread.sleep(1000000);
}
}
package com.local.hr.tcp;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import com.local.hr.util.EdHrCode;
import com.local.hr.util.NewsCache;
import com.local.hr.util.ReturnEntity;
import com.local.hr.util.UnHrCode;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
public class NettyClientHandler extends SimpleChannelInboundHandler<Object> {
private static final Logger log = LogManager
.getLogger(NettyClientHandler.class);
private String mac;
private String hostIp;
private NettyClient client;
private EdHrCode code = new EdHrCode();
private UnHrCode un = new UnHrCode();
public NettyClientHandler(String mac,String hostIp,NettyClient client) {
this.mac=mac;
this.hostIp=hostIp;
this.client = client;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//断线重连
client.isopen = false;
final EventLoop eventLoop = ctx.channel().eventLoop();
eventLoop.schedule(new Runnable() {
@Override
public void run() {
client.doConnect();
while(!client.isopen) {
try {
Thread.sleep(180000);//等三分钟重新再试
} catch (InterruptedException e1) {
e1.printStackTrace();
}
client.doConnect();
}
}
}, 5L, TimeUnit.SECONDS);
ctx.close();
super.channelInactive(ctx);
}
// 利用写空闲发送心跳检测消息
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
throws Exception {
if (evt instanceof IdleStateEvent) {
log.debug("空闲了啊!");
String h = this.un.heartbeat(this.mac, this.hostIp);
ctx.writeAndFlush(this.un.hexStr2ByteArray(h));
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = code.bytesToHexString(req);
log.debug(this.mac+" 收到消息:"+body);
NewsCache.add(this.mac, body);
ReturnEntity entity= code.caseControlCode("2a", body);
if (entity.getStatus()==ReturnEntity.DATA_STATUS_NORMAL) {
NewsCache.addRead(this.mac, entity);
}
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
// TODO Auto-generated method stub
}
}