美文网首页原理收藏-技术篇
Java程序性能分析之道

Java程序性能分析之道

作者: 郭彦超 | 来源:发表于2020-02-14 11:58 被阅读0次

一、背景描述#

基于Netty做了一个消息推送服务,将消息总线中的消息推送给与消息推送服务器进行socket连接的client。

推送服务描述

二、问题描述#

进行压力测试,当消息发送速率达到5w条/秒时:

1、 JVM进程出现频繁FullGC,且每次FullGC并不能回收年老代堆内存空间;

2、 top命令查看服务器资源使用情况,服务器内存被打满;

3、负责FullGC的线程不断循环执行导致CPU100%;

三、问题排查#

1、查询占用CPU较多线程的堆栈信息####

排查步骤

1)jps或ps命令获取jvm进程的pid;

2)top -H p pid查询jvm进程中各个线程占用的系统资源;

3)jstack pid获取jvm进程的堆栈信息->stack.txt;

4)将占用CPU资源较多线程的tid转换为十六进制,从堆栈信息文件stack.txt中匹配对应堆栈信息;

截取几个堆栈信息如下

at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)

at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:622)

at sun.nio.ch.FileDispatcherImpl.write0(Native Method)

at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:47)

- locked <0x0000000080d00f38> (a java.lang.Object)

at io.netty.channel.socket.nio.NioSocketChannel.doWrite(NioSocketChannel.java:270)

at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:688)

at io.netty.channel.ChannelOutboundBuffer$Entry.newInstance(ChannelOutboundBuffer.java:693)

at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:658)

at io.netty.handler.stream.ChunkedWriteHandler.doFlush(ChunkedWriteHandler.java:304)

at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:688)

at sun.misc.Unsafe.freeMemory(Native Method)

at java.nio.DirectByteBuffer$Deallocator.run(DirectByteBuffer.java:94)

at io.netty.util.internal.Cleaner0.freeDirectBuffer(Cleaner0.java:66)

at io.netty.channel.AbstractChannelHandlerContext.invokeFlush(AbstractChannelHandlerContext.java:688)

分析堆栈信息,基本是Netty的IO线程进行select、write、flush和freeDirectBuffer的操作。

2、jvm堆内存使用分析####

jstat -gcutil pid 1s 命令,查询JVM进程GC情况,发现Full GC短期大量执行,且无法回收年老代内存空间。

jmap -histo pid > histo.txt命令,打印JVM进程中每个class的实例数目,内存占用,类全名信息,如下:

正常情况刚Yong GC完时的内存占用

  1:        457482      35,387,296  [C

  2:        16678      13,773,280  [I

  3:        63772        9,237,488  [B

  4:        122,459        7,837,376  java.nio.DirectByteBuffer

  5:        53,091        7,193,480  [Ljava.lang.Object;

  6:        90076        6485472  io.netty.channel.DefaultChannelHandlerContext

  7:        59706        4298832  io.netty.buffer.UnpooledUnsafeDirectByteBuf

  8:        122777        3928864  java.util.HashMap$Node

  9:        120320        2887680  java.lang.String

  10:        43714        2447984  io.netty.buffer.UnpooledHeapByteBuf

  Total      2360002      138,412,016

正常情况发生Yong GC前的内存占用

  1:      5800859      438,101,408  [C

  2:        717064      105,584,984  [B

  3:      1375972      88,062,208  java.nio.DirectByteBuffer

  4:        675654      48,647,088  io.netty.buffer.UnpooledUnsafeDirectByteBuf

  5:      1526018      36,624,432  java.lang.String

  6:        621771      34,819,176  io.netty.buffer.UnpooledHeapByteBuf

  7:        620271      34,735,176  java.nio.StringCharBuffer

  8:        621910      29,851,680  io.netty.channel.DefaultChannelPromise

  9:        621523      29833104  java.nio.HeapByteBuffer

  10:      1235512      29652288  java.lang.StringBuilder

Total      19200129    1,093,142,504

不断进行Full GC时的内存占用

  1:      14,315,627      687,150,096  io.netty.channel.DefaultChannelPromise

  2:      14,314,546      572,581,840  io.netty.channel.AbstractChannelHandlerContext$WriteAndFlushTask

  3:      14,314,631      458,068,192  io.netty.util.Recycler$DefaultHandle

  4:      14,061,517      337,476,408  com.youzan.platform.ordermsgpush.server.handler.MacdonaldMessageHandler$$Lambda$34/1592197748

  5:        339988      11723152  [C

  6:        443875      10653000  com.youzan.platform.ordermsgpush.server.common.CommandOut

  7:        339909        8157816  java.lang.String

  8:        252671        6064104  com.youzan.platform.ordermsgpush.server.common.CommandIn

  9:        252451        6058824  com.youzan.platform.ordermsgpush.server.dispatcher.network.NetWorkEventDispatcher$1

  10:        252451        6058824  com.youzan.platform.ordermsgpush.server.dispatcher.network.

  Total      59789703    2,146,390,000

分析结论:

Netty使用DirectByteBuffer缓存待发送的数据,正常情况下,会存在DirectByteBuffer对象,实际内存空间在堆外申请;

不断发生Full GC异常时,JVM堆内存被大量的DefaultChannelPromise、WriteAndFlushTask、Recycler对象填满,可见是因为writeAndFlush任务的产生速度大大超过socket连接的TCP发送缓冲区消化速度,导致Netty不断申请堆外内存在应用层缓存待发送的数据,最终耗尽系统内存资源;

3、产生问题的代码####

发送数据的代码片段如下:

            channelHandlerContext.writeAndFlush(commandOut).addListener(future -> {

                if (!future.isSuccess()) {

                    logger.warn("unexpected push. msg:{} fail:{}", msg, future.cause().getMessage());

                }

            });

由于ChannelHandlerContext.writeAndFlush()只是产生write任务和flush任务,交由Netty的IO线程处理,其ChannelFuture返回时并不保证flush任务已执行完毕并将数据写入到了连接对应的TCP发送缓冲区。

如果不对writeAndFlush()的调用速率进行限制,当应用对socket进行写操作的速率超过socket实际发送速率时,对应连接的TCP发送缓冲区会被填满,后续写操作的待发送数据会由Netty不断申请内存缓存,最终导致内存溢出。

解决方法:

            if(channelHandlerContext.channel().isWritable()){

                channelHandlerContext.writeAndFlush(commandOut).addListener(future -> {

                    if (!future.isSuccess()) {

                        logger.warn("unexpected push. msg:{} fail:{}", msg, future.cause().getMessage());

                    }

                });

            }else{

                try {

                    channelHandlerContext.writeAndFlush(commandOut).sync();

                    logger.info("publish macdonaldMsg,sended. remoteAddress:[{}], msg:[{}]", channelHandlerContext.channel().remoteAddress(), msg);

                } catch (InterruptedException e) {

                    logger.info("write and flush msg exception. msg:[{}]",msg,e);

                }

            }

调用writeAndFlush()方法前先判断当前Channel的可写入状态,若状态为可写入说明待发送数据量并未堆积超限,可以进行异步发送;否则需要执行sync()方法等待发送数据真正成功写入socket的TCP发送缓冲区,然后在执行下一次的write操作

相关文章

  • Java程序优化

    Java程序优化 参考书籍: Java程序性能优化 让你的Java程序更快、更稳定.pdf 性能分析-程序性能指...

  • Java程序性能分析之道

    一、背景描述# 基于Netty做了一个消息推送服务,将消息总线中的消息推送给与消息推送服务器进行socket连接的...

  • JConsole的远程连接

    JConsole介绍 JConsole是JDK自带的Java性能分析器,用来监听Java应用程序性能和跟踪代码。默...

  • Java进阶书籍(持续更新)

    Java程序员修炼之道,java7的新特性 写给大忙人看的java se 8 函数式编程思维 Java性能优化权威...

  • JVM分析工具——jstack实践

    JVM分析工具——jstack实践 一、不是万能的jstack 企业级的Java程序中,java程序出现性能低下甚...

  • JVM部分

    JVM 指令集整理 知其然知其所以然程序员必备利器——Java程序性能分析工具Java VisualVM(Visu...

  • jprofiler:java性能分析(转)

    JAVA性能分析:超详细的JProfiler安装使用JAVA性能分析:超详细的JProfiler数据分析(官方中文...

  • 工具:VisualVM

    使用 VisualVM 进行性能分析及调优 概述 开发大型 Java 应用程序的过程中难免遇到内存泄露、性能瓶颈等...

  • Java——单例模式

    学习资料: Java 的 23 种设计模式全解析 《Java程序性能优化》 《Java程序性能优化》,这本书蛮不错...

  • 今日份打卡 120/365

    技术文章Java程序员需要突破的点源码分析分布式架构微服务性能优化Java工程化

网友评论

    本文标题:Java程序性能分析之道

    本文链接:https://www.haomeiwen.com/subject/vtedfhtx.html