pthread_create创建线程失败问题排查
Posted Zhongyi_Li
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了pthread_create创建线程失败问题排查相关的知识,希望对你有一定的参考价值。
一些基础概念的了解
Android中线程(Thread)的创建及内存分配过程分析
pthread_create创建线程失败的OOM详解
不可思议的OOM
通过上面的文章,我们知道为什么会报pthread_create错误
在创建线程的时候,报的下面这些错误,都是linux系统层面的导致的错误,而不是在虚拟机层面,在java中创建一个线程,最终是在linux操作系统上创建了一个线程
-
错误类型一:
java.lang.OutOfMemoryError: Could not allocate JNI Env
JNIENV创建不成功时产生OOM的错误信息为"Could not allocate JNI Env"
有如下可能的两种情况:
(1)用户态虚拟内存地址空间耗尽,mmap创建失败
(2)FD数超限导致 -
错误类型二pthread_create:
调用C库创建线程时的环节,创建线程首先调用__allocate_thread函数申请线程私有的栈内存(stack)等,采用的是mmap的方式,然后调用clone方法进行线程创建。
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Out of memory
pthread_create失败时抛出OOM的错误信息为"pthread_create (%s stack) failed: %s"
Try again
是申请线程栈内存的时候,失败了
Out of memory
是在clone线程的时候,创建线程数超过了设置的最大线程数
我们遇到的问题
Manufacturer : samsung
Model : SM-F9360
SdkInt : 32
ProcessorNumber : 8
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
at java.lang.Thread.nativeCreate(Native Method)
at java.lang.Thread.start(Thread.java:884)
at java.util.Timer.<init>(Timer.java:167)
at com.ksyun.media.streamer.logstats.StatsLogReport.c(StatsLogReport.java:3)
at com.ksyun.media.streamer.logstats.StatsLogReport.startStreamSuccess(StatsLogReport.java:16)
at com.ksyun.media.streamer.publisher.RtmpPublisher$1.run(RtmpPublisher.java:17)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8855)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
Exception occurs first time, date is :2022/10/26 12:55:47
大老板在直播的时候,app崩溃了
发现是pthread_create的时候,try again类型错误导致的崩溃,通过上面查阅的各种信息来看,try again这种错误,按道理是很少遇到的,很多文章对这种错误都是附带的提了一下。因为,这是线程申请内存导致的错误,现在的机器物理内存都很大,何况线程申请内存是向虚拟内存申请的,对于64位的机器,虚拟内存会更大,可以超过实际的物理内存。SM-F9360 是三星最新出的折叠屏手机,64位的12G内存的。怎么会有虚拟内存不足的问题出现呢?
在不可思议的OOM文章中,有一段这样的描述:
也就是说,在32位的机器上,由于进程可用的虚拟(逻辑)地址空间最多只有3G,创建线程时,很有可能报try again错误,可是,我们遇到try again的机器是64位呀!
排查这问题时,我有些无助和疲惫,压力很大,首先,这是我们大老板遇到的问题,而且,领导还和我说这个问题“紧急且重要”,这。。。二来,这个错误报错在linux层,我对C++了解程度都有限,心里觉得自己够呛能解决,三来,我当时还有点感冒低烧头疼。可想,我当时的状态有多糟糕,可是,问题还是得解决,欲哭无泪!!!
继续回到解决问题的话题上来吧。在看C++代码的时候,语法问题,请教了一下做C++方面工作的同事,同事无意说他们之前遇到一个问题是,32的程序跑在64位的机器上,会有内存不足的问,我一想,我们会不会也有这个问题啊???
32位的程序跑在64位的机器上,机器会按照32位来运行,也就是说最大虚拟地址空间是3G。
android apk也是有32和64位区分的,进一步来说,其实是包含的so库有32位和64位的区分。理解到,我们的apk包会单独打只包含32位或64位so库的,然后按照渠道要求上传,现在很多商品都要求是64位的。了解到大老板安装的apk包是从官网下载在,官网只是32位的包。
破案了,普天同庆呀,很大概率就是这个原因,没想到能解决这个问题,自己瞬间轻松了,感觉天亮了,太难了。
疑问汇总
32位和64位
最常见的就是 32位和64位的问题了。
CPU 通过物理总线访问内存,那么访问地址的范围就受限于机器总线的数量,在32位机器上,有32条总线,每条总线有高低两种电位分别代表 bit 的 1 和 0,那么可访问的最大地址就是 2^32bit = 4GB,所以说 32 位机器上插入大于 4G 的内存是无效的,CPU 访问不到多于 4G 的内存。
但 64位机器并没有 64位总线,而且其最大内存还要受限于操作系统,Linux 目前支持最大 256G 内存。
根据虚拟内存的概念,在 32 位系统上运行 64 位软件也并无不可,但由于系统对虚拟内存地址的结构设计,64位的虚拟地址在32位系统内并不能使用。
用户态虚拟地址空间和内核态虚拟地址空间
在操作系统中引入了虚拟内存的概念,所以,很多概念都是针对虚拟内存说的,比如用户态、内核态。
进程的虚拟地址空间分为用户区(0-3G)和内核区(3-4G), 其中内核区是受保护的, 用户是不能够对其进行读写操作的;
内核区对于所有进程是共享的;系统中所有进程对应的虚拟地址空间的内核区都会映射到同一块物理内存上(系统内核只有一个)。
虚拟地址空间中用户区地址范围是 0~3G,里边分为多个区块:
保留区: 位于虚拟地址空间的最底部,未赋予物理地址。任何对它的引用都是非法的,程序中的空指针(NULL)指向的就是这块内存地址。
.text段: 代码段也称正文段或文本段,通常用于存放程序的执行代码 (即 CPU 执行的机器指令),代码段一般情况下是只读的,这是对执行代码的一种保护机制。
.data段: 数据段通常用于存放程序中已初始化且初值不为 0 的全局变量和静态变量。数据段属于静态内存分配 (静态存储区),可读可写。
.bss段: 未初始化以及初始为 0 的全局变量和静态变量,操作系统会将这些未初始化的变量初始化为 0
堆(heap):用于存放进程运行时动态分配的内存。
堆中内容是匿名的,不能按名字直接访问,只能通过指针间接访问。
堆向高地址扩展 (即 “向上生长”),是不连续的内存区域。这是由于系统用链表来存储空闲内存地址,自然不连续,而链表从低地址向高地址遍历。
内存映射区(mmap):作为内存映射区加载磁盘文件,或者加载程序运作过程中需要调用的动态库。
栈(stack): 存储函数内部声明的非静态局部变量,函数参数,函数返回地址等信息,栈内存由编译器自动分配释放。栈和堆相反地址 “向下生长”,分配的内存是连续的。
命令行参数:存储进程执行的时候传递给 main() 函数的参数,argc,argv [],env[]
环境变量: 存储和进行相关的环境变量,比如:工作路径,进程所有者等信息
从上面的概念,知道java虚拟机进程中管理分配对象和自动回收的堆内存,是在linux虚拟地址空间的堆(heap)空间这块区域。自己,在初次接触虚拟内存概念的时候,就一直有个问题,困扰着我,linux的虚拟内存和虚拟机的堆内存是个什么关系?
我个人理解,java程序是跑着虚拟机里面的,虚拟机对于linux来说就是一个进程,也就是,一个apk都需要一个虚拟机,apk+虚拟机,作为一个进程跑在linux操作系统上。虚拟机会向linux申请一大块虚拟内存,这个虚拟内存就是上图一个完整的用户区虚拟内存,然后,虚拟机自己管理的堆内存就是在虚拟内存的堆所在的空间。
自己实际测试
我们64位的包安装到64位的机器上,VIRT虚拟内存10G,我这手机物理内存是8G,RES实际使用内存200M
32位的包安装到64位的机器上,VIRT虚拟内存只有2.6G,而且还得分成很多块做不同用途,所以很容易出现内存问题
参考
Linux虚拟内存这样理解就到位了
Liunux内核内存管理之虚拟地址空间
JVM和Linux内存的关系
吐血推荐–Linux与JVM的内存关系分析
Android 进程监控(top命令)
java.lang.OutOfMemoryError: pthread_create (1040KB stack) failed: Try again
经典 OOM 问题|pthread_create
《Android 创建线程源码与OOM分析》
OutOfMemoryError pthread_create (1040KB stack) failed Try again
快速缓解 32 位 Android 环境下虚拟内存地址空间不足的“黑科技”
pthread_create中Resource temporarily unavailable问题
有台设备发现程序启动失败,上去通过日志发现是线程启动(pthread_create)失败,进一步定位,打印errno:11,Resource temporarily unavailable。但是发现失败前不管是top还是free,还是swap虚拟内存都不高,不存在内存不足的问题。
百度发现说是ulimit给的线程数量不足。
ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 257733
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 30000
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 1024
virtual memory (kbytes, -v) 1200000
file locks (-x) unlimited
进程1024个完全足够,出于实验目的,修改为更大的数字,但是发现进程只能启动几十个线程,尝试修改其他参数,发现最多只能启动115个线程。
只好和其他设备进行对比,发现其他设备的信息是virtual memory (kbytes, -v) unlimited
于是进行修改,发现启动正常。
查了下资源,线程占用的虚拟内存很高,但是实际上并不使用,只是计算一个预计的大小,所以swap依然会是0
一个线程占用的虚拟内存是根据stack size来设置的,这个设备刚好是10240,1200000/10240=117.18,所以启动了115个线程。
以上是关于pthread_create创建线程失败问题排查的主要内容,如果未能解决你的问题,请参考以下文章
Linux C语言 pthread_create()创建线程失败的handle_error_en宏(取自man文档demo)errnoperror()