当前位置: 首页 > news >正文

景点网站建设西丽网站建设

景点网站建设,西丽网站建设,清远做网站的公司,seo是什么及作用作者#xff1a;0x264 问题 线上长时间存在一个跟异步 inflate 相关的量级较大的内存泄漏#xff0c;如下所示#xff1a; 第一次分析 从内存泄漏粗略看有几个信息#xff1a; 被泄漏的Activity有很多#xff0c;所以可能跟某个具体业务的关系不大引用链特别短#xf… 作者0x264 问题 线上长时间存在一个跟异步 inflate 相关的量级较大的内存泄漏如下所示 第一次分析 从内存泄漏粗略看有几个信息 被泄漏的Activity有很多所以可能跟某个具体业务的关系不大引用链特别短并且可以看出 gc root 是 Java Frame 中的BasicInflater实例然后它通过 mContext 字段持有了 Activity 从上面的这个信息推测导致内存泄漏的原因是 业务代码触发了一些布局的异步 Inflate当前页面退出destroy之前发出的异步 Inflate 请求还没有执行完还在子线程中inflate所以会短暂的持有 context如果这个时候dump hprof 分析就会发现 contextactivity泄漏了 public void runInner() { InflateRequest request; try { request mQueue.take(); } catch (InterruptedException ex) { // Odd, just continue Log.w(TAG, ex); return; } // 子线程需要把 mQueue 里面的 request 处理完而request 持有 inflaterinflater 持有 contexttry { request.view request.inflater.mInflater.inflate( request.resid, request.parent, false); } catch (RuntimeException ex) { // Probably a Looper failure, retry on the UI thread Log.w(TAG, Failed to inflate resource in the background! Retrying on the UI thread, ex); } Message.obtain(request.inflater.mHandler, 0, request) .sendToTarget(); } Override public void run() { while (true) { runInner(); } }根据上面的分析这种泄漏理论上是存在的但是 inflate 一个layout一般很快几毫秒、几十毫秒、最多几百毫秒这种属于短时泄漏而且时间特别短影响不大所以第一次简单看了下这个问题后觉得影响不大不必处理 第二次分析 虽然上面判断这个“影响不大且泄漏时间很短”但是每个版本都会触发报警而且是量级很大的泄漏于是继续排查一下这个泄漏是否有其他原因 从内存泄漏的量级看之前的判断似乎有点说不通 泄漏时间窗口这么短hprof 刚好就在这期间dump的概率极低量怎会这么大短时泄漏问题其实很多比如业务 postDelay 一个几秒的Runnable持有外部类引用在这期间 activity destroy了也会出现短时泄漏这个时间几秒比 inflate 要长多了而且业务上这类情况很多但是线上抓到的这类情况很少。 因此第一次分析的情况似乎不太对于是捞个 hprof 再分析一下看看 我们先通过内存信息来验证下我们第一次分析的猜测原因activity destroy的时候异步 inflate 任务还没执行完成是否正确 看下 InflateThread 的 mQueue 中还有多少 InflateRequest 待处理 捞了很多个hprof结果都是如此令人惊讶InflateRequest 队列都是空的里面没有任务待处理 再一次猜想会不会是每次dump hprof的时候刚好最后一个 InflateRequest 被从队列中取出来了但是还没有执行完成呢其实想想这种概率已经不能再低了但是目前也没有其他怀疑点换个角度看假设是这种情况会不会是某个布局有点问题导致 inflate 耗时特别长然后增加了被抓到的概率呢 先来看下那个持有activity的 BasicInflater 信息 我们知道LayoutInflater在inflate开始前会把当前要用的context存到他的 mConstructorArgs[0] 中inflate 完成后再把 mConstructorArgs[0] 恢复可以参考如下代码 public View inflate(XmlPullParser parser, Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { // ......Context lastContext (Context) mConstructorArgs[0]; mConstructorArgs[0] inflaterContext; try {// ...... do inflate} finally { // Dont retain static reference on context. mConstructorArgs[0] lastContext; mConstructorArgs[1] null; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } return result; } }也就是说如果正在 inflatemConstructorArgs[0]应该持有context但是我们看到这个时候 BasicInflater 中的 mConstructorArgs 的2个element都是 null也就是说当前它处于空闲状态并非正在 inflate 初步结论 根据目前的信息来看泄漏的Activity是被一个空闲的 BasicInflater 持有的。 进一步排查发现一个更奇怪的现象在dump出来的 hprof中AsyncLayoutInflater$BasicInflater 的实例数始终比 AsyncLayoutInflater 刚好多一个而多出来的那一个就是导致 activity 泄漏的那个实例 从代码上看AsyncLayoutInflater$BasicInflater 都是在 AsyncLayoutInflater的构造函数中创建的按理说AsyncLayoutInflater$BasicInflater不会比AsyncLayoutInflater更多才对️ public AsyncLayoutInflater(NonNull Context context) { mInflater new BasicInflater(context); mHandler new Handler(mHandlerCallback); mInflateThread InflateThread.getInstance(); }难道导致泄漏的这个 BasicInflater 是其他地方创建出来的 首先反射创建不大可能因为这个类没有keep那么最有可能的就是下面这个路径了 Override public LayoutInflater cloneInContext(Context newContext) { return new BasicInflater(newContext); }而这个方法似乎只在ViewStub中使用 // layoutinflater public final View createView(NonNull Context viewContext, NonNull String name, Nullable String prefix, Nullable AttributeSet attrs) throws ClassNotFoundException, InflateException { // ... final ViewStub viewStub (ViewStub) view; viewStub.setLayoutInflater(cloneInContext((Context) args[0])); // ... }这个clone出来的 inflater 会被 ViewStub.mInflater 持有但是从内存数据来看它自己是gc root并且没有其他对象持有它 而这个 BasicInflater 之所以是 gc root是因为它是在当前Java frame的本地变量表中再回头看一下相关代码 public void runInner() { InflateRequest request; try { request mQueue.take(); } catch (InterruptedException ex) { // Odd, just continue Log.w(TAG, ex); return; } try { request.view request.inflater.mInflater.inflate( request.resid, request.parent, false); } catch (RuntimeException ex) { // Probably a Looper failure, retry on the UI thread Log.w(TAG, Failed to inflate resource in the background! Retrying on the UI thread, ex); } Message.obtain(request.inflater.mHandler, 0, request) .sendToTarget(); } Override public void run() { while (true) { runInner(); } }第二次猜想 倘若 runInner()被内联到 run()中那么正好就是这个情况并且能解释上面的所有现象于是找了个线下包看了下结果惊呆了没有内联。。。 无奈似乎没有思路了但是也不能不处理啊翻了下业务代码后发现有个LiveAsyncLayoutInflater 它就是从 AsyncLayoutInflater中copy出来的唯一的改动就是在 runInner() 中取出 request 执行 inflate之前先判断一下 contextactivity是否已经destroy如果是的话直接丢弃这个 request。跟这个代码的作者聊了下他这样做也是因为担心Activity销毁后还有没处理完的request。 第一次尝试修复 虽然我们上面分析过导致这个内存泄漏的原因似乎不是 因为“Activity销毁后还有没处理完的request”但是我搜了一下发现却真没有跟 LiveAsyncLayoutInflater 相关的泄漏业务用法也都一样。。。 没有其他思路于是也这样改一下试试吧起码不会有坏处。并且我们还加了一个逻辑 监听Activity destroy然后遍历 request 队列把和其关联的 request 移除。这样的目的是因为有些 request 是用的 Application context 来处理的只在 runInner 中判断可能会影响后面 request 处理的及时性。 尝试修复上线后发现跟预期的基本“一致”可以说是毫无作用 第三次分析 尝试一下看看线下能不能复现看看复现的场景是什么。 目前我们线下包在 activity destroy的时候都会去分析泄漏于是改了下代码当发现跟当前问题引用链一样的时候就将额外补充的一些信息一起上报上来排查。 然而用这个包测试了一段时间没复现。。。然后又看了下线下的内存泄漏监控这个每天也会上报不少的泄漏问题结果这个问题一个都没有。。。 线上量很大线下一个都没有难道是某处逻辑线上包跟线下包不一样触发了这个问题 回想一开始分析的时候有个判断如果runInner被内联到run里面那问题就可以解释当时反编译已经排除了但是忽然想到当时包用错了。。。拿的是线下包线下包都是 fast 模式是不会走 optimized 的所以肯定不会内联。相关配置如下 if (BuildContext.isFast) {// ...proguardFile proguard_not_opt.pro// ... }// ... proguard_not_opt.pro -dontoptimize // ...因为 optimize 特别耗时线下包关闭也是很合理的。但是线上包是没有这个配置的也就是打开了 optimize。另外混淆配置中没有关闭 unique method inline # 下面的没有配 # -optimizations !method/inlining/unique而 runInner 也只有一处调用在打开optimize且没有禁止 unique method inline 时是可能inline的。于是找了个线上包再来看看果然 内 联 了 既然内联了那么 runInner 的本地变量表中的对象就被合到了 run 中而 run 里面是个 while (true) 死循环生命周期无限长所以如果这里面的本地变量表中持有 BasicInflater 那么它就是gc root并且进一步导致它的 context 泄漏我们来看下AsyncLayoutInflater的这段代码 .method public final run()V .registers 6 .prologue :catch_0 :goto_0 :try_start_0 iget-object v0, p0, LX/pJX;-LIZIZ:Ljava/util/concurrent/ArrayBlockingQueue; invoke-virtual {v0}, Ljava/util/concurrent/ArrayBlockingQueue;-take()Ljava/lang/Object; move-result-object v4 check-cast v4, LX/pJZ; const/4 v3, 0x0 :try_end_9 .catch Ljava/lang/InterruptedException; {:try_start_0 .. :try_end_9} :catch_0 :try_start_9 iget-object v0, v4, LX/pJZ;-LIZ:LX/pJW; iget-object v2, v0, LX/pJW;-LIZ:Landroid/view/LayoutInflater; iget v1, v4, LX/pJZ;-LIZJ:I iget-object v0, v4, LX/pJZ;-LIZIZ:Landroid/view/ViewGroup; invoke-virtual {v2, v1, v0, v3}, Landroid/view/LayoutInflater;-inflate(ILandroid/view/ViewGroup;Z)Landroid/view/View; move-result-object v0 iput-object v0, v4, LX/pJZ;-LIZLLL:Landroid/view/View; :try_end_17 .catch Ljava/lang/RuntimeException; {:try_start_9 .. :try_end_17} :catch_17 :catch_17 iget-object v0, v4, LX/pJZ;-LIZ:LX/pJW; iget-object v0, v0, LX/pJW;-LIZIZ:Landroid/os/Handler; invoke-static {v0, v3, v4}, Landroid/os/Message;-obtain(Landroid/os/Handler;ILjava/lang/Object;)Landroid/os/Message; move-result-object v0 invoke-virtual {v0}, Landroid/os/Message;-sendToTarget()V goto :goto_0 .end method从 iget-object v2, v0, LX/pJW;-LIZ:Landroid/view/LayoutInflater; 知道 BasicInflater 被赋值到了 v2寄存器invoke-virtual {v2, v1, v0, v3}, Landroid/view/LayoutInflater;-inflate(ILandroid/view/ViewGroup;Z)Landroid/view/View; 通过 v2中的BasicInflater去inflate 布局v2寄存器没有复用当通过 goto :goto_0 进行下一次循环并且取出下一个 requestv2 被赋予下一个 BasicInflater 实例引用之前v2 一直持有者上一个 BasicInflater 引用而这个就是导致泄漏的引用。 当前处于while循环中等待下一个request跟我们上面分析的“导致泄漏的BasicInflater处于空闲状态一致”并且长时间处于这个状态所以抓到的概率就很大了。 为什么AsyncLayoutInflater$BasicInflater 的实例数始终比 AsyncLayoutInflater多一个呢原因是AsyncLayoutInflater 的引用是存在 v0 寄存器中的而v0寄存器被多次复用所以AsyncLayoutInflater的引用并没有被一直持有。 到此问题基本就分析清楚了但是还有一个遗留问题上面提到的业务中copy出来的 LiveAsyncLayoutInflater为什么没有泄漏呢原因是 他的 BasicInflater 引用也是放到 v2寄存器中的但是这个类中的方法有插桩runInner被内联之后插桩代码也被内联了在进行while(true)的下一轮循环时首先会去执行插桩代码而插桩代码复用了v2寄存器因此就不再持有BasicInflater的引用了因此没有泄漏 所以这块代码没有导致泄漏其实是有点运气在其中的。寄存器的分配本身比较复杂而且D8在寄存器分配上也不是非常完善的几年前在西瓜也曾遇到一个D8寄存器分配导致的一个crash问题D8编译器“bug”导致简单代码crash 修复 问题已经明确修复就比较简单了只要让 runInner 不内联就行了。而之所以它会被内联是因为 proguard/R8 有个优化如果某个方法只有一处调用当然还要满足很多其他条件那么就将它内联并删除原方法。因此我们改一下找一个其他地方调一下就可以规避比如 if (/* 此处返回 false让if block不执行就行 */) { runInner();// Make sure never reach here }这样静态分析不是一处调用不会内联实际上也不会走到也不影响逻辑。 不过线上我没有这样改因为还有其他办法可以不用改代码给 runInner keep 一下就也不会内联了原因也好理解如果方法被keep了那么原方法不能删而这个又不是个小方法要是内联的话字节码变大了方法数也没少必然负向了那还内联干啥。 -keepclassmembers class androidx.asynclayoutinflater.view.AsyncLayoutInflater$InflateThread { public void runInner(); }改了之后泄漏解决了 顺便提一句 在 runInner 里面判断contextactivity是否destroy如果destroy的话就拦截不处理这个 request还有个小麻烦onInflateFinished 这个回调是否要触发 public interface OnInflateFinishedListener {void onInflateFinished(NonNull View view, LayoutRes int resid,Nullable ViewGroup parent); }如果要回调onInflateFinished那么 view 如何获取null 肯定不行因为原本接口中有 NonNull导致很多业务代码不会判空不回调也不行因为有些业务有个“优化”逻辑如果上一个 inflate 没有回来后续就走同步 inflate所以如果不回调相当于关闭了异步 inflate 功能。。。 所以当时我们加了个 onCancel 回调业务可以在这里处理被拦截的情况 public interface OnInflateFinishedListener {void onInflateFinished(NonNull View view, LayoutRes int resid,Nullable ViewGroup parent);/*** if context (activity) destroyed, InflateRequest will be cancel, and this method will be invoked.** It can be invoked on different thread* param resid*/default void onCancel(LayoutRes int resid) {} }比如 override fun onCancel(resid: Int) {isAsyncInflating false }但是这样也不优雅而且也不是所有业务都知道有这么个 onCancel api如果改成非default接口又要改动很多地方的代码。 好在上面也看到了这个泄漏并非因为 “Activity destroy后还有没处理完的 InflateRequest可能导致短暂泄漏”拦截的必要性也不大。 Android 学习笔录 Android 性能优化篇https://qr18.cn/FVlo89 Android 车载篇https://qr18.cn/F05ZCM Android 逆向安全学习笔记https://qr18.cn/CQ5TcL Android Framework底层原理篇https://qr18.cn/AQpN4J Android 音视频篇https://qr18.cn/Ei3VPD Jetpack全家桶篇内含Composehttps://qr18.cn/A0gajp Kotlin 篇https://qr18.cn/CdjtAF Gradle 篇https://qr18.cn/DzrmMB OkHttp 源码解析笔记https://qr18.cn/Cw0pBD Flutter 篇https://qr18.cn/DIvKma Android 八大知识体https://qr18.cn/CyxarU Android 核心笔记https://qr21.cn/CaZQLo Android 往年面试题锦https://qr18.cn/CKV8OZ 2023年最新Android 面试题集https://qr18.cn/CgxrRy Android 车载开发岗位面试习题https://qr18.cn/FTlyCJ 音视频面试题锦https://qr18.cn/AcV6Ap
http://www.hkea.cn/news/14305472/

相关文章:

  • ppt做的最好的网站做头像网站有哪些
  • 怎样重新安装电脑wordpress盐城seo培训
  • 网站做兼容需要多少钱大数据营销的运营方式有哪些
  • 互利互通网站建设东莞电商网站建设
  • 仿站怎么修改成自己的网站工信部网站备案验证码
  • wordpress网站换主题flash网站设计教程
  • 网站建设购买数据库的流程图网站正在升级建设中
  • 公司网站的留言板怎么做多城市地方门户网站系统
  • 沈阳酒店企业网站制作网址大全免费网站
  • 自己弄个网站要怎么弄建筑网片施工中的用途
  • seo网站建设流程网站优化服务
  • 有孩子做的网站wordpress 段落 两格
  • 做英文网站内容来源app拉新佣金排行榜
  • 阿里云个人网站制作服装品牌策划方案
  • 怎么免费制作网站价格低的跑车
  • 互联网建设网站的的好处微信支付公司网站
  • 湛江制作企业网站徽章设计制作网站
  • 邢台网站建设哪家公司好jsp网站开发 英文
  • 怎么申请pc网站域名263企业邮箱登陆入囗
  • 中国建设银行网站主页网站可以用什么做
  • 黄金网站app软件下载安装wordpress显示图片慢
  • 企业建网站哪家好wordpress标题关键词描述
  • 免费文档网站企业管理咨询中心
  • 网站建设开发费用入什么科目自学做网站要学什么
  • 广告制作是做什么的临淄专业网站优化哪家好
  • 官方网站建设最重要的是什么德州网站制作哪家好
  • 大连制作网站公司杭州网站推广服务
  • 深圳网站建设相关推荐报考二级建造师官网
  • 搅拌机东莞网站建设技术支持品牌做网站
  • 昆明做网站魄罗科技在哪可以做网站