一次惊心动魄的线上事故:记一次内存泄漏Bug的排查与解决全过程
前言
大家好,今天想和大家分享一个我接手外包项目时遇到的严重性能问题。这个问题排查过程异常曲折,整整花了3天时间才定位到根本原因。通过这次经历,我对OkHttp的连接池机制有了更深刻的理解,也意识到代码规范的重要性。
问题背景
我们接手了一个外包团队开发的直播类App,接手后测试发现一个奇怪的问题:只有华为手机在使用某个直播页面时会出现OOM(Out Of Memory)错误,其他品牌手机完全正常。
最初我们错误地认为这是华为机型的适配问题,但深入分析后发现问题的根源完全不同。
问题现象
- 机型差异:只有华为手机出现OOM,其他品牌手机正常
- 偶发性:问题不是必现,需要反复打开关闭直播页面多次才会触发
- 内存异常:OOM错误堆栈显示线程创建失败,暗示线程数过多
错误日志大致如下:
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Out of memoryat java.lang.Thread.nativeCreate(Native Method)at java.lang.Thread.start(Thread.java:733)at java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:975)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1393)...
排查过程
第一步:初步分析
看到OOM错误,我们首先想到的是内存泄漏,于是集成了LeakCanary进行监控,但没有发现明显的内存泄漏报告。这让我们意识到问题可能不是传统的内存泄漏。
第二步:使用Android Studio Profiler
我们使用AS Profiler监控应用运行状态,发现了一个惊人的现象:
每次打开直播页面,线程数量都会显著增加,而且在页面关闭后线程并没有被回收。反复操作几次后,线程数从正常的几十个增长到300多个,最终导致OOM。
第三步:分析页面代码
我们仔细分析直播页面的代码,发现页面中确实有很多网络请求,但奇怪的是其他页面也有网络请求,为什么只有这个页面有问题?
通过分析发现:
- 该页面确实比其他页面请求的接口多
- 反复打开页面时,线程数持续增长
- 页面关闭时,网络请求没有被正确取消
第四步:检查网络请求框架
项目使用的是Retrofit + OkHttp的经典组合。我们发现Retrofit确实是单例的,但问题出在OkHttpClient上。
通过断点调试,我们发现了一个严重问题:
虽然Retrofit是单例,但每次网络请求时都创建了新的OkHttpClient实例,导致连接池配置失效!
问题代码如下:
// 外包团队的错误实现
public class ApiManager {private static volatile Retrofit retrofit;public static ApiService getApiService() {if (retrofit == null) {synchronized (ApiManager.class) {if (retrofit == null) {retrofit = new Retrofit.Builder().baseUrl(BASE_URL).