Android 编译线程爆了, gradle 内存 OOM 解决之路
本文首发我的微信公众号徐公,收录于 Github·AndroidGuide,这里有 Android 进阶成长知识体系, 希望我们能够一起学习进步,关注公众号徐公,5 年中大厂程序员,一起建立核心竞争力
背景
最近我们项目在编译的时候,编译多次之后,有挺多人反馈会出现 OOM 的,在项目的根目录下面会出现 hs_err_pid*.log 的错误文件。内容大概如下
这个对我们的开发效率还是有挺大影响的,如果能够解决,对我们的开发效率还是有一定提升的。因此,我们尝试进行解决。
探索原因
从报错的信息来看,'jar transform Thread' 有时候的线程数非常多, 很有可能是同时开启的线程数过大,导致内存不足,最终 OOM。
从线程名是 'jar transform Thread' ,根据经验,我们第一时间想到可能是 transform 相关的。
于是,我们找项目当中 transfrom 相关的, 从 buildScan 文件中,找 transfrom 相关的
发现主要有几个
- transformClassesWithRealmTransformerForDebug
- transformClassesWithCom.xx.gradle.plugin.hilt.HiltContextWrapperRemovePluginForDebug
- 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 读取比较快,出现这样现象的可能性会表多。如果磁盘性能较差,出现的可能性会比较大
问题解决
既然怀疑问题是因为这里的线程数引起的,于是第一时间我们想到了几种方法
- 反射修改线程池的数量
- 升级 gradle 版本
于是,我们跟中代码,试试反射能不能修改代码,但很快,我们发现,并没有找到一个好的 hook 点,无法修改。
可能有人会想到 epic,没错,刚开始我也想用 epic。但是 epic 是基于安卓 ART 虚拟机的,而我们编译的时候,是基于 JVM 的,epic 是无法使用的。
接着我们尝试了第二种方法,尝试升级 gradle 版本到 7.0,折腾了一笔之后,发现升级要适配的东西还是蛮多的,一下子无法解决
- maven repo 仓库设置 allowInsecureProtocol
- grrovy 版本冲突
- JavaParser 错误
- ......
总之,错误是解决完一个接着一个,还是挺多坑的
柳暗花明又一村
跟同事讨论之后,说能不能自己编译一个版本出来。于是我们在官网上找到了编译 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 的线程数进行了限制,经过
总结
可以看到,我们这次的问题解决思路大概是这样的。
- 从 error 日志排查发现,很有可能跟 transfrom 相关
- 排查项目里面 transfrom 相关的,有没有
jar transform Thread相关的 - dump JVM 内存,看线程相关的,观察
jar transform Thread是否异常 - debug gradle assemble 任务,观察 线程名包括
jar transform ThreadThread 的调用堆栈 - 分析 调用堆栈,找到原因
- 结合 gradle 官方代码,查看问题是否已经解决
那有没有更快的方法呢?
其实如果一开始能确定是 gradle 问题的话,可以直接在 gradle 里面搜索字符串 jar transforms,然后再一步步反推,其实也是可以的。
推荐阅读
耗时一周,我解决了微信 Matrix 增量编译的 Bug,已提 PR
自定义 Hook,解决 RxJava 异常时堆栈信息显示不全
Android 启动优化(二) - 拓扑排序的原理以及解题思路
Android 启动优化(三)- AnchorTask 开源了
Android 启动优化(四)- AnchorTask 是怎么实现的
Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了
Android 编译线程爆了, gradle 内存 OOM 解决之路的更多相关文章
- android编译时出现org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:compileDebugJavaWithJavac'.错误
android studio中使用terminal工具.在android studio最下面的底部菜单栏中有(如果没有那cmd中进入项目根目录后): 使用命令 gradlew compileDebu ...
- Android提升Gradle编译速度或减少Gradle编译时间.md
目录 Android如何提升Gradle编译速度或减少Gradle编译时间 最终优化方案 优化效果比对 将所有项目源码,各种缓存临时目录都移动到高性能SSD磁盘上 gradle.properties ...
- Android Studio编译的时候提示Gradle无法下载的解决方案
首先,打开android studio项目 找到项目目录gradle\wrapper\gradle-wrapper.properties这个文件.内容如下:#Wed Apr 10 15:27:10 P ...
- Android编译优化系列-kapt篇
作者:字节跳动终端技术---王龙海 封光 兰军健 一.背景 本文是编译优化系列文章之 kapt 优化篇,后续还会有 build cache, kotlin, dex 优化等文章,敬请期待.本文由Cli ...
- Android开发之深入理解Android Studio构建文件build.gradle配置
摘要: 每周一次,深入学习Android教程,TeachCourse今天带来的一篇关于Android Studio构建文件build.gradle的相关配置,重点学习几个方面的内容:1.applica ...
- Android内存优化解决 资料和总结的经验分享
在前公司做一个图片处理的应用时, 项目交付的时候,客户的手机在运行应用的时候,一直在崩溃,而这个异常就是OutOfMemory的错误,简称为OOM, 搞得我们也是极其的崩溃,最后 ,我们是通过网上搜集 ...
- [Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程
本文转载自:[Android 编译(一)] Ubuntu 16.04 LTS 成功编译 Android 6.0 源码教程 1 前言 经过3天奋战,终于在Ubuntu 16.04上把Android 6. ...
- 为 Android 编译并集成 FFmpeg 的尝试与踩坑
前言与环境说明 随着 FFmpeg.NDK 与 Android Studio 的不断迭代,本文可能也会像我参考过的过期文章一样失效(很遗憾),但希望本文中提到的问题排查以及步骤说明能够帮到你,如果发现 ...
- [转]Android Studio系列教程六--Gradle多渠道打包
转自:http://www.stormzhang.com/devtools/2015/01/15/android-studio-tutorial6/ Android Studio系列教程六--Grad ...
- android 进程/线程管理(四)续----消息机制的思考(自定义消息机制)
继续分析handler 和looper 先看看handler的 public void dispatchMessage(Message msg) { if (msg.callback != null) ...
随机推荐
- Qt中QTabWidget添加控件(按钮,label等)以及使用方法
今天遇到了一个问题,已经在QTabWidget每一行添加了一个按钮,我有一个需求就是,点击每一行的按钮都有各自的响应 首先说一下添加控件代码: 添加文字可以用setItem,添加控件就得用setCel ...
- DP:按摩师(3.24leetcode每日打卡)
一个有名的按摩师会收到源源不断的预约请求,每个预约都可以选择接或不接.在每次预约服务之间要有休息时间,因此她不能接受相邻的预约.给定一个预约请求序列,替按摩师找到最优的预约集合(总预约时间最长),返回 ...
- 在 Ubuntu 22.04 系统上为 SSH 开启基于时间的 TOTP 认证
前言 一次性密码(英语:one-time password,简称OTP),又称动态密码或单次有效密码,是指电脑系统或其他数字设备上只能使用一次的密码,有效期为只有一次登录会话或一段短时间内.基于时间的 ...
- 一个.Net开源的协作办公套件,包括文档、表格、演示文稿和表单
推荐一个开源的文档协作办公套件,可以很好的满足团队对方便.高效.安全的方式来处理文档工作,促进团队协作和信息共享. 项目简介 ONLYOFFICE 是一个开源的办公套件,包括文档.表格.演示文稿和表单 ...
- 【Android】学习day05|简单登陆页面的实现|监听代码
实现效果如下图所示 实现代码[部分] MainActivity.java 1 package com.example.app02; 2 3 import androidx.appcompat.app. ...
- MySQL8.0 安装教程
一.下载 1.官网地址 MySQL :: Download MySQL Community Server 2.选择安装包安装方式 3.选择安装版本 4.开始下载 5.下载成功 二.安装 1.双击安装包 ...
- 神经网络入门篇:详解搭建神经网络块(Building blocks of deep neural networks)
搭建神经网络块 这是一个层数较少的神经网络,选择其中一层(方框部分),从这一层的计算着手.在第\(l\)层有参数\(W^{[l]}\)和\(b^{[l]}\),正向传播里有输入的激活函数,输入是前一层 ...
- 构建满足流批数据质量监控用火山引擎DataLeap
更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 面对今日头条.抖音等不同产品线的复杂数据质量场景,火山引擎 DataLeap 数据质量平台如何满足多样的需求?本文 ...
- Vue源码学习(十八):实现组件注册(一)Vue.component()和Vue.extend()
好家伙, 0.完整代码已开源 https://github.com/Fattiger4399/analytic-vue.git 1.思路 1.1.什么是组件化? Vue 组件化是指将复杂的应用程序拆分 ...
- 如何根据月份查询每月Xxx的总数
我以我的项目根据月份查询每月新增会员的总数为例 Controller @GetMapping("/getMemberReport.do") public R getMemberRe ...