您的位置:68399皇家赌场 > 集群主机 > 皇家赌场:记一回Java的内部存款和储蓄器走漏剖

皇家赌场:记一回Java的内部存款和储蓄器走漏剖

发布时间:2019-08-10 10:56编辑:集群主机浏览(115)

    httpasyncclient 使用介绍:

    • maven 依赖
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpasyncclient</artifactId>
        <version>4.1.3</version>
    </dependency>
    
    • HttpAsyncClient 客户端
    public class HttpAsyncClient {
    
        private CloseableHttpAsyncClient httpclient;
    
        public HttpAsyncClient() {
            httpclient = HttpAsyncClients.createDefault();
            httpclient.start();
        }
    
        public void execute(HttpUriRequest request, FutureCallback<HttpResponse> callback){
            httpclient.execute(request, callback);
        }
    
        public void close() throws IOException {
            httpclient.close();
        }
    
    }
    

    排查

    通过 top 命令发现经过占用较高CPU,然后 top -Hp 开掘经过中有四条线程一向处于繁忙,剖判了须臾间,具体怎么深入分析?参照他事他说加以考察Linux下哪些对Java线程进行剖析

    那四条线程的音信没什么差别

    "GC task thread#0 (ParallelGC)" prio=10 tid=0x083d2c00 nid=0x57bd runnable 
    

    表达经过一贯艰苦GC。超越四分之一年华STW,业务线程基本不施行。

    那怎么会一直在GC呢? 有多少个恐怕,一是heap设置得过小,而又要频仍分配成对象;二是内部存款和储蓄器败露,对象一直不能够被回收。由于大家的heap设置了512M事实上也不算少,于是方向直指内部存款和储蓄器泄漏。

    接下来经过 jstat -gcutil pid发掘年轻代和年老代都是99%的占用率,并且不能够被回收,确实是内存败露。

    规定问题原因后,下一步就要解析一下 java heap,找寻什么指标发生泄漏。于是赶紧实行命令jmap -dump:live,format=b,file=heap.prof pid把heap dump下来实行分析,live选项会先进行三次
    full gc保障dump下来的都以水土保持的靶子。

    dump 下来后,执行 jhat heap.prof,然后展开浏览器查看结果,分析开采,com.rabbitmq.client.recovery.RecoveryAwareChannelN
    com.rabbitmq.client.QueueingConsumer的指标数量有20w ,多得惊魂动魄。
    何况数量同样。于是大胆臆想,这两侧应该存在贰个援引关系,所以数量才会一直以来。而恐怕是channel,也说不定是comsumer还会有被别的对象引用这导致直接无法被回收,所以产生内部存款和储蓄器败露。

    留神:jhat其实是一个很简陋的深入分析工具,假设通过其找不出头绪,推荐应用Eclipse的MAT插件或然JDK自带的VisualVM分析会更加好。

    勇于猜想过后,便是小心求证了。首先来看一下大家使用RabbitMQ Java Client管理音讯的代码,

    Channel channel = connection.createChannel();
    channel.queueDeclare(queueName);
    QueueingConsumer consumer = new QueueingConsumer(channel);
    channel.basicConsume(queueName, false, consumer);
    
    try{
    int count = 1;
    while(count < 1000) {
        QueueingConsumer.Delivery delivery = consumer.nextDelivery(500);
        ...
        ...
        各种业务逻辑
        ...
        ...
        }
    } catch(Exception e) {
        e.printStackTrace();
    } finally {
        channel.close();
    }
    

    总体的拍卖流程正是如上代码所示,有几点需求留神:

    1. Channel是个接口,设置automaticRecoveryEnalbed为true后,实现类是RecoveryAwareChannel
    2. channel是QueueingConsumer的叁本质量,即QueueingConsumer引用着channel,那也注明了从前的猜想。
    3. 每一轮最多管理一千条消息,管理完之后只close掉channel,未有close connection。

    以为离真相更加的近了,在知情了consumer援用着channel之后,那正是评释还只怕有其他地点援引consumer,导致无法被回收。那么到底何在会援用consumer呢,那时将在继续深刻,查看源码。
    于是乎开采basciConsume方法中调用了三个叫recordConsumer方法:

    private void recordConsumer(String result,
                                    String queue,
                                    boolean autoAck,
                                    boolean exclusive,
                                    Map<String, Object> arguments,
                                    Consumer callback) {
            RecordedConsumer consumer = new RecordedConsumer(this, queue).
                                                autoAck(autoAck).
                                                consumerTag(result).
                                                exclusive(exclusive).
                                                arguments(arguments).
                                                consumer(callback);
            this.connection.recordConsumer(result, consumer);
        }
    

    QueueingConsumer被RecordedConsumer引用,而RecordedConsumer又被connection引用着。

    再看channel.close方法:

    public void close() throws IOException, TimeoutException {
            try {
              delegate.close();
            } finally {
              this.connection.unregisterChannel(this);
            }
        }
    

    到此处,其实就曾经知道了,当channel被close后,connection如故援用着RecordedConsumer,所以QueueingConsumer不可能被放出掉。

    那正是说,那有十分的大希望是合法客户端的坑,于是上网搜了须臾间,开采github已经有issue,issue #208,
    何况已经在3.6.6版本fix了,因为我们用的是3.6.3,所以及早进级版本。测量检验一番后,确实不会漏风了,难题一蹴即至。

    3.6.6的close方法源码:

    @Override
          public void close() throws IOException, TimeoutException {
              try {
                 delegate.close();
              } finally {
                 for (String consumerTag : consumerTags) {
                     this.connection.deleteRecordedConsumer(consumerTag);
                 }
                 this.connection.unregisterChannel(this);
              }
          }
    

    能够见到,在fix后的代码中,当关闭channel时,会先把该channel相关的RecordedConsumer先删除掉,那就幸免了内部存款和储蓄器泄漏。

    GC日志中年天命之年时代特别大,何况回收效果也不肯定。境遇这种难题,第一以为照旧有内部存款和储蓄器走漏,固然Java能友好回收内部存款和储蓄器,不过不能够担保作者本人写的次序没不平日,比如曾经犯过二个不当往list一直add object,却尚未remove,而且list也不自由,导致list更加大,最终内部存款和储蓄器OOM。本着大胆假如,小心求证的观点,起先排查难点。

    怎么着是内部存款和储蓄器走漏

    内部存款和储蓄器不在GC的操纵之内,GC垃圾回收机制漏掉的污物对象,即无法回收
    内部存储器溢出:内存败露过多,就能够变成内部存款和储蓄器溢出

    当前条件

    近年来线上某些项目发生内部存款和储蓄器走漏,JVM忙于GC,导致专门的学问线程基本不奉行。经过排查最后发掘是RabbitMQ的Java客户端代码导致的。现把排查进程记录下来

    皇家赌场 1

    污源对象分三种##

    1.GC能够直接回收的
    2.GC回收不了,不过程序猿又忘记回收的
    上面大家通贰个事例来证实内部存款和储蓄器走漏

    背景

    近些日子,上线了三个新类型,那一个类型是多个压测系统,能够简轻松单的作为通过回放词表(http央浼数据),不断地向劳动发送央求,以达到压测服务的指标。在测量试验进程中,一切还算顺遂,修复了多少个小bug后,就上线了。在上线后给到第一个业务方使用时,就意识来二个严重的标题,应用大约跑了10多分钟,就收到了汪洋的 Full GC 的报告警方。

    本着这一难题,大家第一和业务方确认了压测的意况内容,重播的词表数量大意是10万条,重放的速率单机在 100qps 左右,依据我们以前的预估,那远远小于单机能接受的终端。按道理是不会发出内存难题的。

    总结

    纵然如此那是大家用的RabbitmqMQ Java Client版本太旧所引起的标题,而官方其实也一度fix了。但发生内部存款和储蓄器走漏时,深入分析排查的思路基本都以同等的。

    咱们也要学会使用好jdk自带的各样指令工具来深入分析有关jvm的难题。

    皇家赌场 2

    能够看做GC征引的点

    • Java Stack栈中的援引的对象
    • Java 方法区中的静态引用指向的对象
    • Java 方法区中的常量援引指向的靶子
    • Native方法中JNI援引的靶子
    • 活着的线程

    总结

    至于内部存款和储蓄器败露难点在第一遍排查时,往往是有一些不知道该如何是好的。大家须求有正确的不二秘籍和手腕,配上好用的工具,那样在缓慢解决难点时,本事听得多了就能说的清楚。当然对JAVA内部存储器的基础知识也是不可或缺的,那时你定位难点的显要,不然正是工具告诉您那块有错,你也不可能确定地点原因。

    最终,关于 httpasyncclient 的应用,工具本身是不曾难题的。只是大家得询问它的采取意况,往往爆发难题多的,都以行使的失当导致的。所以,在行使工具时,对于它的垂询程度,往往调整了出现bug 的机率。

    当前条件 代码地址 git 地址: 背景 前不久,上线了一...

    难点背景

    某日,线上的RabbitMQ开采有大气音讯积聚,迟迟无法出队。汤姆cat未有发掘至极报错,却在输出的日志中窥见管理一千条消息要耗费时间几十分钟不等(日常境况只需3~5秒)。

    背景:JAVA 应用软件,主要职能是拍卖日志并存入db

    GC拉圾回收机制

    有些对象不再有其余的援引时,才回被回收,也许不可向上追溯到GCRoot的时候,技艺被回收

    皇家赌场 3

    icon

    最主要逻辑:

    德姆o 的关键逻辑是那样的,首先创立贰个缓存列表,用来保存必要发送的乞求数据。然后,通过轮回的办法从缓存列表中抽取必要发送的呼吁,将其交由 httpasyncclient 客户端进行发送。

    切实代码如下:

    public class ReplayApplication {
    
        public static void main(String[] args) throws InterruptedException {
    
       //创建有内存泄露的回放客户端
            ReplayWithProblem replay1 = new ReplayWithProblem();
    
       //加载一万条请求数据放入缓存
            List<HttpUriRequest> cache1 = replay1.loadMockRequest(10000);
    
            //开始循环回放
            replay1.start(cache1);
    
        }
    }
    

    附录:

    内部存款和储蓄器败露例子

    public class UtilsTool {
      private static final String TAG = "UtilsTool";
      private static Context   mContext;
      private static UtilsTool utilsTool;
    
      public static UtilsTool getInstance(Context context) {
        mContext = context;
        if (utilsTool == null) {
            utilsTool = new UtilsTool();
        }
        return utilsTool;
      }
    }
    public class MainActivity
        extends AppCompatActivity
    {
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
    
            }
        }, 5000 * 200);
        UtilsTool.getInstance(this);
      }
    }
    

    咱俩运营App,通过Android Studio自带的Dump Java Heap工具,来捕获运转中的app内存使用意况,生成hprof文件

    皇家赌场 4

    dump java heap

    皇家赌场 5

    hprof

    皇家赌场 6

    hprof

    这么生成的文件,对于Memory Analyzer来说不是正经的,大家要将其转成标准的格式

    皇家赌场 7

    分级将其导出保存为normal.hprof,leak.hprof 几个文件
    透过Memory Analyzer 工具张开

    皇家赌场 8

    Memory Analyzer

    接下来大家通过相比较三个公文

    皇家赌场 9

    皇家赌场 10

    因此筛选,发掘MainActivity对象 1,还也可能有UtilsTool等
    第一们锁定 轻易内部存款和储蓄器走漏又很惊恐的Activity,View等等系统重量级组件
    第一举行筛选ListObjects选用 "with outgoing references"

    皇家赌场 11

    皇家赌场 12

    接下来对它尤其筛选

    皇家赌场 13

    聊到底大家一直到

    皇家赌场 14

    大家开采UtilsTool援用了MainActivity
    接下去大家对发出内部存储器走漏的代码举行调度
    public class UtilsTool {
    private static final String TAG = "UtilsTool";
    private static Context mContext;
    private static UtilsTool utilsTool;

    public static UtilsTool getInstance(Context context) {
        mContext = context.getApplicationContext();//取应用的生命时长
        if (utilsTool == null) {
            utilsTool = new UtilsTool();
        }
        return utilsTool;
      }
    }
    public class MainActivity    extends AppCompatActivity{    
      private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
    
            }
        }, 5000 * 200);
        UtilsTool.getInstance(this);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);//all callbacks and messages will be removed
      }
    }
    

    有关的源码及hprof文件
    链接: http://pan.baidu.com/s/1i5OHBvv 密码: 6d8e
    转载自:爱上海博物院客街 » Android 质量优化之内部存款和储蓄器败露深入分析

    离线解析

    从服务器上下载了 dump 的 heap.dump 后,我们须求通过工具进行深远的解析。这里推荐的工具有 mat、visualVM。

    本人个人相比较喜欢使用 visualVM 举办辨析,它除了能够分析离线的 dump 文件,还能够与 IDEA 进行合併,通过 IDEA 运转应用,进行实时的剖判利用的CPU、内部存款和储蓄器以及GC景况(GC情况,需要在visualVM中安装visual GC 插件)。工具具体浮现如下(此处独有为了显得效果,数据不是的确):

    皇家赌场 15

    皇家赌场 16

    自然,mat 也是相当好用的工具,它能帮大家急迅的一贯到内部存储器败露的地点,便于大家排查。 展示如下:

    皇家赌场 17

    皇家赌场 18

    情景:运营一段时间就应际而生OOM难点,查看GC log开采摘运输营没多长时间就直接Full GC,並且抛出OOM的可怜。

    代码优化

    找到题目标来由,大家明日来优化代码,验证我们的结论。因为List<HttpUriRequest> cache1中会保存回调对象,所以大家不能够缓存诉求类,只可以缓存基本数据,在接纳时张开动态的退换,来担保回调对象的立时回收。

    代码如下:

    public class ReplayApplication {
    
        public static void main(String[] args) throws InterruptedException {
    
            ReplayWithoutProblem replay2 = new ReplayWithoutProblem();
            List<String> cache2 = replay2.loadMockRequest(10000);
            replay2.start(cache2);
    
        }
    }
    

     

     /**
      * Java学习调换QQ群:589809992 大家一并学Java!
        */

    public class ReplayWithoutProblem {
    
        public List<String> loadMockRequest(int n){
            List<String> cache = new ArrayList<String>(n);
            for (int i = 0; i < n; i  ) {
                cache.add("http://www.baidu.com?a=" i);
            }
            return cache;
        }
    
        public void start(List<String> cache) throws InterruptedException {
    
            HttpAsyncClient httpClient = new HttpAsyncClient();
            int i = 0;
    
            while (true){
    
                String url = cache.get(i
    		

    本文由68399皇家赌场发布于集群主机,转载请注明出处:皇家赌场:记一回Java的内部存款和储蓄器走漏剖

    关键词: 68399皇家赌场 Java JVM Android的旅途

上一篇:java project 项目在 linux 上边陈设方法

下一篇:没有了