本文首发我的微信公众号徐公,收录于 Github·AndroidGuide,这里有 Android 进阶成长知识体系, 希望我们能够一起学习进步,关注公众号徐公,5 年中大厂程序员,一起建立核心竞争力

背景

最近我们项目在编译的时候,编译多次之后,有挺多人反馈会出现 OOM 的,在项目的根目录下面会出现 hs_err_pid*.log 的错误文件。内容大概如下

这个对我们的开发效率还是有挺大影响的,如果能够解决,对我们的开发效率还是有一定提升的。因此,我们尝试进行解决。

探索原因

从报错的信息来看,'jar transform Thread' 有时候的线程数非常多, 很有可能是同时开启的线程数过大,导致内存不足,最终 OOM。

从线程名是 'jar transform Thread' ,根据经验,我们第一时间想到可能是 transform 相关的。

于是,我们找项目当中 transfrom 相关的, 从 buildScan 文件中,找 transfrom 相关的

发现主要有几个

  1. transformClassesWithRealmTransformerForDebug
  2. transformClassesWithCom.xx.gradle.plugin.hilt.HiltContextWrapperRemovePluginForDebug
  3. transformDebugClassesWithAsm

很快我们找到 Hilt, Realm 里面 transform 里面的代码,发现里面的 Thread Name 都不是 jar transform Thread。那应该不是这两个的原因。

讨论之后,我们尝试 dump 编译时 Java 进程的内存信息,看能不能复现?

首先,我们先确定当前进程的 PID

接着我们借助 VisualVM 这个工具,dump 下 JVM 编译时候的进程,编译的时候发现,线程数有时候会越来越多。

于是,我们在想能不能 debug 创建线程的地方,于是,我们在 java.lang.Thread#setName 这里设置条件断点 name.contains("jar transform")

debug gradle assembleDebug 任务,很快我们发现,调用栈关系如下

我们重点关注到了几个跟线程相关的东西

我们跟踪进去,发现这个线程池的核心线程数设置为 2147483647

而上面的线程数不断增多,并且线程名包括 "jar transform", 那很有可能就是这个线程池了。

我们逐一排查,发现线程池 executor 是在这里传递进来的

跟踪代码,很快我们发现创建改线程池 executor 的地方 DefaultCachedClasspathTransformer#executor

他这里果然没有限制线程的数量。

而我们项目中的 gradle 代码是 6.9.1,于是在想,我们去跟官方最新代码对比一下。

对比官方 gradle 代码

我们首先 clone 官方代码 gradle,找到 DefaultCachedClasspathTransformer,

发现最新代码已经进行了修改,限制了线程的数量。改为跟 CPU 核心数挂钩。

而他是在什么时候进行了修改了,其实很简单,我们可以借助 git 命令,找到他属于哪一个 TAG.

git tag --contains 2a1e74166bc82607e15de78002ef56582b34af0d

很快我们发现了,他在 gradle 7.0 上面对线程池的线程数进行了限制,改为跟 CPU 核心数挂钩。

思考

先来思考一个问题,可能很多人有同样的疑问。

为什么在 windows 上面比较容易出现,在 mac 上面很少出现呢?

java tranfrom 线程是干什么用的,

我们可以看这里的代码

org.gradle.internal.classpath.DefaultCachedClasspathTransformer.TransformFile#schedule

跟踪下去,你会发现主要是一些 IO 读取操作。

如果说你的机器磁盘性能比较好,那么 IO 读取比较快,出现这样现象的可能性会表多。如果磁盘性能较差,出现的可能性会比较大

问题解决

既然怀疑问题是因为这里的线程数引起的,于是第一时间我们想到了几种方法

  1. 反射修改线程池的数量
  2. 升级 gradle 版本

于是,我们跟中代码,试试反射能不能修改代码,但很快,我们发现,并没有找到一个好的 hook 点,无法修改。

可能有人会想到 epic,没错,刚开始我也想用 epic。但是 epic 是基于安卓 ART 虚拟机的,而我们编译的时候,是基于 JVM 的,epic 是无法使用的。

接着我们尝试了第二种方法,尝试升级 gradle 版本到 7.0,折腾了一笔之后,发现升级要适配的东西还是蛮多的,一下子无法解决

  1. maven repo 仓库设置 allowInsecureProtocol
  2. grrovy 版本冲突
  3. JavaParser 错误
  4. ......

总之,错误是解决完一个接着一个,还是挺多坑的

柳暗花明又一村

跟同事讨论之后,说能不能自己编译一个版本出来。于是我们在官网上找到了编译 gradle 版本的方法

编译完成之后,我们在 gradle-wrapper.properties 下面修改,替换成自己的 gradle 版本

#Thu May 30 18:31:45 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
# 解决 编译线程数过多
distributionUrl=https://xx.cn/static/gradle/gradle-6.9.3-all.zip

再次编译,可以看到 jar transform 相关的线程数,最多变为 8 了,跟我们 CPU 数量一样

我们对 gradle jar transfrom thread 的线程数进行了限制,经过

总结

可以看到,我们这次的问题解决思路大概是这样的。

  1. 从 error 日志排查发现,很有可能跟 transfrom 相关
  2. 排查项目里面 transfrom 相关的,有没有 jar transform Thread 相关的
  3. dump JVM 内存,看线程相关的,观察 jar transform Thread 是否异常
  4. debug gradle assemble 任务,观察 线程名包括 jar transform Thread Thread 的调用堆栈
  5. 分析 调用堆栈,找到原因
  6. 结合 gradle 官方代码,查看问题是否已经解决

那有没有更快的方法呢?

其实如果一开始能确定是 gradle 问题的话,可以直接在 gradle 里面搜索字符串 jar transforms,然后再一步步反推,其实也是可以的。

推荐阅读

耗时一周,我解决了微信 Matrix 增量编译的 Bug,已提 PR

自定义 Hook,解决 RxJava 异常时堆栈信息显示不全

Android 启动优化(一) - 有向无环图

Android 启动优化(二) - 拓扑排序的原理以及解题思路

Android 启动优化(三)- AnchorTask 开源了

Android 启动优化(四)- AnchorTask 是怎么实现的

Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了

Android 启动优化(六)- 深入理解布局优化

Android 编译线程爆了, gradle 内存 OOM 解决之路的更多相关文章

  1. android编译时出现org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:compileDebugJavaWithJavac'.错误

    android studio中使用terminal工具.在android studio最下面的底部菜单栏中有(如果没有那cmd中进入项目根目录后): 使用命令  gradlew compileDebu ...

  2. Android提升Gradle编译速度或减少Gradle编译时间.md

    目录 Android如何提升Gradle编译速度或减少Gradle编译时间 最终优化方案 优化效果比对 将所有项目源码,各种缓存临时目录都移动到高性能SSD磁盘上 gradle.properties ...

  3. Android Studio编译的时候提示Gradle无法下载的解决方案

    首先,打开android studio项目 找到项目目录gradle\wrapper\gradle-wrapper.properties这个文件.内容如下:#Wed Apr 10 15:27:10 P ...

  4. Android编译优化系列-kapt篇

    作者:字节跳动终端技术---王龙海 封光 兰军健 一.背景 本文是编译优化系列文章之 kapt 优化篇,后续还会有 build cache, kotlin, dex 优化等文章,敬请期待.本文由Cli ...

  5. Android开发之深入理解Android Studio构建文件build.gradle配置

    摘要: 每周一次,深入学习Android教程,TeachCourse今天带来的一篇关于Android Studio构建文件build.gradle的相关配置,重点学习几个方面的内容:1.applica ...

  6. Android内存优化解决 资料和总结的经验分享

    在前公司做一个图片处理的应用时, 项目交付的时候,客户的手机在运行应用的时候,一直在崩溃,而这个异常就是OutOfMemory的错误,简称为OOM, 搞得我们也是极其的崩溃,最后 ,我们是通过网上搜集 ...

  7. [Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程

    本文转载自:[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6. ...

  8. 为 Android 编译并集成 FFmpeg 的尝试与踩坑

    前言与环境说明 随着 FFmpeg.NDK 与 Android Studio 的不断迭代,本文可能也会像我参考过的过期文章一样失效(很遗憾),但希望本文中提到的问题排查以及步骤说明能够帮到你,如果发现 ...

  9. [转]Android Studio系列教程六--Gradle多渠道打包

    转自:http://www.stormzhang.com/devtools/2015/01/15/android-studio-tutorial6/ Android Studio系列教程六--Grad ...

  10. android 进程/线程管理(四)续----消息机制的思考(自定义消息机制)

    继续分析handler 和looper 先看看handler的 public void dispatchMessage(Message msg) { if (msg.callback != null) ...

随机推荐

  1. Qt中QTabWidget添加控件(按钮,label等)以及使用方法

    今天遇到了一个问题,已经在QTabWidget每一行添加了一个按钮,我有一个需求就是,点击每一行的按钮都有各自的响应 首先说一下添加控件代码: 添加文字可以用setItem,添加控件就得用setCel ...

  2. DP:按摩师(3.24leetcode每日打卡)

    一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接.在每次预约服务之间要有休息时间,因此她不能接受相邻的预约.给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回 ...

  3. 在 Ubuntu 22.04 系统上为 SSH 开启基于时间的 TOTP 认证

    前言 一次性密码(英语:one-time password,简称OTP),又称动态密码或单次有效密码,是指电脑系统或其他数字设备上只能使用一次的密码,有效期为只有一次登录会话或一段短时间内.基于时间的 ...

  4. 一个.Net开源的协作办公套件,包括文档、表格、演示文稿和表单

    推荐一个开源的文档协作办公套件,可以很好的满足团队对方便.高效.安全的方式来处理文档工作,促进团队协作和信息共享. 项目简介 ONLYOFFICE 是一个开源的办公套件,包括文档.表格.演示文稿和表单 ...

  5. 【Android】学习day05|简单登陆页面的实现|监听代码

    实现效果如下图所示 实现代码[部分] MainActivity.java 1 package com.example.app02; 2 3 import androidx.appcompat.app. ...

  6. MySQL8.0 安装教程

    一.下载 1.官网地址 MySQL :: Download MySQL Community Server 2.选择安装包安装方式 3.选择安装版本 4.开始下载 5.下载成功 二.安装 1.双击安装包 ...

  7. 神经网络入门篇:详解搭建神经网络块(Building blocks of deep neural networks)

    搭建神经网络块 这是一个层数较少的神经网络,选择其中一层(方框部分),从这一层的计算着手.在第\(l\)层有参数\(W^{[l]}\)和\(b^{[l]}\),正向传播里有输入的激活函数,输入是前一层 ...

  8. 构建满足流批数据质量监控用火山引擎DataLeap

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 面对今日头条.抖音等不同产品线的复杂数据质量场景,火山引擎 DataLeap 数据质量平台如何满足多样的需求?本文 ...

  9. Vue源码学习(十八):实现组件注册(一)Vue.component()和Vue.extend()

    好家伙, 0.完整代码已开源 https://github.com/Fattiger4399/analytic-vue.git 1.思路 1.1.什么是组件化? Vue 组件化是指将复杂的应用程序拆分 ...

  10. 如何根据月份查询每月Xxx的总数

    我以我的项目根据月份查询每月新增会员的总数为例 Controller @GetMapping("/getMemberReport.do") public R getMemberRe ...