在某个版本,上报平台新增了一些OOM的问题。
1. 分析闪退日志
1 2 3 4 5 6 7 8 9 10 11
| java.lang.OutOfMemoryError
Could not allocate JNI Env
java.lang.Thread.nativeCreate(Native Method) 2 java.lang.Thread.start(Thread.java:730) 3 java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:941) 4 java.util.concurrent.ThreadPoolExecutor.processWorkerExit(ThreadPoolExecutor.java:1009) 5 java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1151) 6 java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607) 7 java.lang.Thread.run(Thread.java:761)
|
从堆栈分析,线程导致的OOM是线程创建导致的,我们分析手机内存大小,发现内存并不紧张。
2. 本地验证
for循环创建大量的线程,可以复现这个问题。
网上查阅资料,发现部分手机厂商将进程创建的线程个数进行了限制,这些手机的线程数限制都很小(应该是华为rom特意修改的limits),每个进程只允许最大同时开500个线程,因此很容易复现。
3. 问题定位
3.1 本地监控线程的创建,检查是否是代码不合理导致的线程创建过多。
方案1:通过代码插桩实现线程创建监控 这类操作可以参考didi出的两套框架 booster和DroidAssist
方案2:采用hook的方案监控线程创建。SandHook
这里我采用的是第二种方案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| Constructor<?>[] constructors = Thread.class.getConstructors(); for (Constructor constructor : constructors) { Class[] types = constructor.getParameterTypes();
int threadName = -1; for (int i = 0; i < types.length; i++) { if (types[i] == String.class) { threadName = i; break; } } int finalThreadName = threadName; XC_MethodHook xc_methodHook = new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { super.beforeHookedMethod(param); threadNum ++; if (finalThreadName >= 0){ LogUtil.e(TAG, "created thread: " + threadNum + "-->" + param.args[finalThreadName]); } else { LogUtil.e(TAG, "created thread: " + threadNum); } LogUtil.e(TAG, " is created." + LogUtil.getStack()); }
@Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { super.afterHookedMethod(param);
} }; Object[] params = new Object[types.length + 1]; System.arraycopy(types, 0, params, 0, types.length); params[types.length] = xc_methodHook; XposedHelpers.findAndHookConstructor(Thread.class, params); }
|
通过监控代码的创建,我们没有发现本地有大量创建线程的逻辑。无法复现场景,但是hook框架我们没办法放到正式环境,在正式环境我们采用了另外一种方案。
4. 现网问题定位
现在的bug上报平台都支持扩展信息,以bugly为例,在扩展信息带上当前进程的线程数和名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| strategy.setCrashHandleCallback
if ("java.lang.OutOfMemoryError".equals(errorType)) { ThreadGroup group = Thread.currentThread().getThreadGroup(); ThreadGroup topGroup = group; while (group != null) { topGroup = group; group = group.getParent(); } int slackSize = topGroup.activeCount() * 2; Thread[] threads = new Thread[slackSize];
int threadSize = topGroup.enumerate(threads); stringBuilder.append("thread count: ").append(threadSize).append("\n");
for (int i = 0; i < threadSize; i++) { Thread thread = threads[i]; if (thread != null) { stringBuilder.append(thread.getName()).append("\n"); } } }
|
我们在上线以后,我们通过线程名,发现了使用一个第三方sdk不合理导致线程大量创建的问题,修复以后的版本再无这个OOM的问题。