本文首发我的微信公众号徐公,收录于 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. Util应用框架基础(七) - 缓存

    本节介绍Util应用框架如何操作缓存. 概述 缓存是提升性能的关键手段之一. 除了提升性能,缓存对系统健壮性和安全性也有影响. 不同类型的系统对缓存的依赖程度不同. 对于后台管理系统,由于是给管理人员 ...

  2. Netty源码学习5——服务端是如何读取数据的

    系列文章目录和关于我 零丶引入 在前面<Netty源码学习4--服务端是处理新连接的&netty的reactor模式>的学习中,我们了解到服务端是如何处理新连接的,即注册Serve ...

  3. 老是听到做PPT要会“内容可视化”,到底啥是内容可视化?

    在PPT中,内容可视化是指将文字.数据和概念等抽象信息转化为图像.图表.图表及其他可视化元素来呈现.通过合适的颜色.形状.大小和布局等视觉设计元素来强调信息的关键点和关系, 从而提高观众对信息的理解和 ...

  4. CentOS(7.6)环境下迁移Mysql(5.7)的data目录到指定位置

    第一步:关闭Mysql #关闭Mysql服务systemctl stop mysqld#查看Mysql服务状态 ps -ef|grep mysql 第二步:创建新目录,并拷贝数据文件 #创建data文 ...

  5. [CF1830E] Bully Sort

    题目描述 On a permutation $ p $ of length $ n $ , we define a bully swap as follows: Let $ i $ be the in ...

  6. [ABC262C] Min Max Pair

    Problem Statement You are given a sequence $a = (a_1, \dots, a_N)$ of length $N$ consisting of integ ...

  7. shell 脚本中的 '-f' 和 '-d' 分别代表什么意思

    shell脚本中,'-f' 和 '-d'是用于测试文件类型的条件表达式. 1.'-f'表达式: 表达式: '[ -f file ]' 描述: 判断给定路径是否是一个常规文件 (regular file ...

  8. Kubernetes: client-go 源码剖析(一)

    0. 前言 在看 kube-scheduler 组件的过程中遇到了 kube-scheduler 对于 client-go 的调用,泛泛的理解调用过程总有种隔靴搔痒的感觉,于是调转头先把 client ...

  9. 华企盾DSC导致金蝶导入Excel导入不了的问题

    需要把Excel的OLE控制关掉,并且金蝶的进程和Excel的进程高级设置要么都启用重定向,要么都不启用重定向

  10. Feign源码解析:初始化过程(三)

    背景 前面两篇讲了下,在一个典型的引入了feign.loadbalancer.nacos等相关依赖的环境中,会有哪些bean需要创建. 其中第一篇讲了非自动配置的bean,第二篇是自动配置的bean. ...