引言链接
在Android中,典型的pthread_create内存溢出堆栈信息如下:
//此异常多为栈内存分配失败
java.lang.OutOfMemoryError
pthread_create (1040KB stack) failed: Try again
java.lang.Thread.nativeCreate(Native Method)
java.lang.Thread.start(Thread.java:733)
java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:975)
java.util.concurrent.ThreadPoolExecutor.processWorkerExit(ThreadPoolExecutor.java:1043)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1185)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
java.lang.Thread.run(Thread.java:764)
//此异常多为线程数到达上限
java.lang.OutOfMemoryError
pthread_create (1040KB stack) failed: Out of memory
java.lang.Thread.nativeCreate(Native Method)
java.lang.Thread.start(Thread.java:743)
java.util.concurrent.ThreadPoolExecutor.addWorker(ThreadPoolExecutor.java:941)
java.util.concurrent.ThreadPoolExecutor.processWorkerExit(ThreadPoolExecutor.java:1009)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1151)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
java.lang.Thread.run(Thread.java:774)
出现创建线程内存溢出无非两个原因:
1、进程的栈内存超过了虚拟机的最大内存数;
2、线程数达到了系统最大限制数;
关于线程数达到了系统最大限制数,在国内手机厂商中,华为手机在7.0+手机上已将最大线程数修改成了300。我们APP有大量的华为用户,不得不面对华为系统限制问题。
Android Dalvik和ART,将stack分为了java stack和native stack,本文没去具体实验,从kongxinsun的博客,我们了解到两者总量是1056KB。栈内存回收和堆内存策略不一样,比较简单,当线程结束,线程占用的栈内存也就回收了。
查看当前进程的线程数 参考链接
adb shell
top -m 20
找到进程对应的PID xxxps -T xxx |wc -l
查看xxx进程的线程数
查看Android系统的某个进程的线程 参考链接
方法1:ps命令
1. adb shell 进入shell里面
在ps命令中,“-T”选项可以开启线程查看。下面的命令列出了由进程号为<pid>的进程创建的所有线程。
2. ps -T -p <pid>
PID 是process进程id, TID(thead id)可以理解为线程的Id。CMD线程名
方法2:top命令
adb shell 登录设备shell
top命令可以实时显示各个线程情况。要在top输出中开启线程查看,请调用top命令的“-H”选项,该选项会列出所有Linux线程。
top -H
要让top输出某个特定进程<pid>并检查该进程内运行的线程状况:
top -H -p <pid>
方法3: Profiler
Android Studio 提供了一个对用户更加友好的方式,通过 Profiler 查看单个进程的 CPU,内存,网络,电池情况,双击进入 CPU 可以查看该进程中的线程。
现在,你就会看到下面这样单个进程的线程视图。
通过代码查看所有的激活的线程:
//输出总数
File file = new File(Environment.getExternalStorageDirectory(), "threads.txt");
BufferedWriter writer = new BufferedWriter(new FileWriter(file, false));
writer.write("count:" + Thread.getAllStackTraces().size() + "\n");
for (Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) {
writer.write(entry.getKey().getName() + ":" + "\n");
for (StackTraceElement traceElement : entry.getValue()) {
writer.write("\tat " + traceElement + "\n");
}
}
这种方式我们能拿到Java层与其对接的native层thread总数,但是拿不到没有attach到java层的native thread,比如Futter engine中的native thread等。
如果为线程分配一个可定位到代码的名称,那我们完全能对症下药。但实际很难,我们没法约束开发同学和三方SDK为每个线程起自定义名称,例如 pool-16-thread-1的线程,我们很难定位到是哪个类发起的调用。
建议使用adb命令查看
线程优化
1、使用线程池管理
2、少用HandlerThread
3、优化常见的池程池
4、为每个线程命名 如果团队在经过一系列优化后,还是避免不了pthread OOM异常,那我们就要从异常Log中能快速定位到问题代码。默认新建线程和Executors类中命名线程相关代码如下
如何我们要快速定位问题线程,采用默认方式肯定不行,所以我们要修改字节码,重新为每个线程命名,在新名字,带上使用类或其他有用信息。
滴滴团队估计也面临了相同问题,在开源booster中利用ASM对Java字节码修改,实现了上述我们要为每个线程命名的需求。目前为止,booster在 ThreadTransformer 存在修改节码缺陷,以及ShadowExecutors.newOptimizedFixedThreadPool方法中错误的使用了LinkedBlockingQueue队列等缺陷,建议大家如果采用booster开源实现,尽量多作一些测试以及代码修复。
好文章:
经典 OOM 问题|pthread_create