2019年08月06日

linux报Resource temporarily unavailable的问题

问题

某物理机在重启java的进程后,经常报 Resource temporarily unavailable 的错误,但是kill掉java进程后,系统恢复正常。

Resource temporarily unavailable 错误的原因分类

  1. 物理内存耗尽。
  2. 进程启动超过了用户的ulimit限制。
  3. 进程、线程启动过多,linux无法分配新的pid。

物理内存耗尽

常见的 Resource temporarily unavailable 报错原因,一般是虚机一般内存都比较小,如8G内存,由于java程序起了太多的线程,很容易把物理内存吃光。直接看物理内存还有多少。

每个thread默认分配8192K内存,如果起得太多,如java进程有1000个thread,8*1000=8G,光thread的stack所占用的内存就达到8G,在一个8G内存虚机上会直接耗尽物理内存,更不要说java进程启动时一般设置4G以上的heap。

$ulimit -s
8192

该机是物理机,不是虚机。物理内存为256G,free看内存有大量剩余。不是内存耗尽导致报错。

https://stackoverflow.com/questions/344203/maximum-number-of-threads-per-process-in-linux/344292#344292

Depends on your system, just write a sample program [ by creating processes in a loop ] and check using

ps axo pid,ppid,rss,vsz,nlwp,cmd

When it can no more create threads check nlwp count [ nlwp is the number threads ] voila you got your fool proof answer instead of going thru books

TODO: 貌似centos 7上可以起很多thread,远远超过物理内存限制,在创建thread是没分配stack吗? [2019-08-05 Mon 15:13] 唉,代码写错了,thread的代码,应该

do {
    sleep(10);
}
while(1);

默认max process是4096,改为8000,能创建7982个thread,报错 Resource temporarily unavailable,同时执行命令报错,哈哈。怎么没把内存吃光。看VIRT是非常大,但是RES实际使用的才100多MB内存。可能就是没有stack,所以?

[albert@master.k8s /home/albert]
$ps axo pid,ppid,rss,vsz,nlwp,cmd
bash: fork: retry: No child processes
bash: fork: retry: No child processes
bash: fork: retry: No child processes

进程启动的数量超过了用户的ulimit限制

另外一个导致报错的原因,一般是超过了max user processes。centos默认4096,可以调整最大进程数解决。

执行ulimit查看用户可以启动的max user processes,655360,真够大的,谁没事设置这么大的值啊,65535就够可以了。一个机器上同时有6万多进程,这机器是在干什么啊?得多忙啊?

$ulimit -u
655360 

显然,max user processes不是导致报错原因。

进程、线程启动过多,linux无法分配新的pid

  1. /proc/sys/kernel/pid_max

    (操作系统进程、线程ID的最大值)系统支持的最大线程数 sysctl kernel.pid_max

  2. /proc/sys/kernel/threads-max

    表示内核所能使用的线程的最大数目

  3. max_user_process

    ulimit -u #系统限制某用户下最多可以运行多少进程或线程

  4. /proc/sys/vm/max_map_count

    单进程mmap的限制会影响单个进程可创建的线程数

[root@03 ~]# cat /proc/sys/kernel/threads-max
2060562
[root@03 ~]# cat /proc/sys/kernel/pid_max
114688

pid_maxthreads-max 小一个数量级,pid_max可能是瓶颈。

在执行top和ps时能感觉到特别慢,要不就是系统性能有问题,要不就是进程太多了导致的。该物理机是2C,56core的。cpu不高。性能不行的问题基本可以排除,那可能是进程太多导致的。

top显示出结果就要几十秒,top显示的task有10万+,这就有点厉害了。接近pid_max了,再看zombie进程数也在10万多。

原因应该是linux可分配的pid数量不够,无法分配新的pid导致报错。

pid的数量不够了导致的,现在的pid都用到11万多,昨天pid_max也就11万多,随便起来几个牛逼的java进程,线程起得多,马上就到上限。

没改pid_max前,pid_max是11万多。发现top执行特别慢,进程太多了,nmon也慢。

process和thread都会分配pid,pid不光是只有process才有,linux按process分配资源,按thread进行调度。

临时动态调整pid_max,不修改sysctl.conf,以免下次重启系统后,pid_max太大,浪费内存。

[root@03 ~]# sysctl -w kernel.pid_max=2060562
kernel.pid_max = 2060562 

[root@03 ~]# sysctl kernel.pid_max
kernel.pid_max = 2060562

zombie进程不怎么吃cpu。top是实时显示当前系统中的进程,会把所有的pid(10万+的pid)都查一遍,对系统中的进程,按cpu使用率排序从大到小显示,所以top进程占用的cpu就高了,直接吃掉了一个core的cpu。

解决方案

  1. 不重启系统,动态调整pid_max。 重启物理机动静比较大,先治标,解决问题。
  2. 重启系统,就不会有这么多zombie进程占用pid,导致可分配的pid变少,也可以释放zombie进程占用的其他资源。 重启治百病啊。

Linux的进程和线程

Linux的进程和线程有很多异同点,可以Google下。但只要能清楚地理解一下几点,则足够理解Linux中各种ID的含义。

  1. 进程是资源分配的基本单位, 线程是调度的基本单位。
  2. 进程是资源的集合 ,这些资源包括内存地址空间,文件描述符等等,一个进程中的多个线程共享这些资源。
  3. CPU对任务进行调度时, 可调度的基本单位 (dispatchable entity)是线程 。如果一个进程中没有其他线程,可以理解成这个进程中只有一个 主线程 ,这个主进程独享进程中的所有资源。
  4. 进程的个体间是完全独立的,而线程间是彼此依存,并且共享资源。多进程环境中,任何一个进程的终止,不会影响到其他非子进程。而多线程环境中,父线程终止,全部子线程被迫终止(没有了资源)。

上述第一点说明是最基础的,也是最重要的。

初步理解各种ID。基本上按照重要程度从高到低,在分割线下方的IDs不太重要。

Table 1: 各种ID
分类 说明
pid 进程ID
lwp 线程ID。在用户态的命令(比如ps)中常用的显示方式。
tid 线程ID,等于lwp。tid在系统提供的接口函数中更常用,比如syscall(SYS_gettid)和syscall(__NR_gettid)。
tgid 线程组ID,也就是线程组leader的进程ID,等于pid。
pgid 进程组ID,也就是进程组leader的进程ID。
pthread id pthread库提供的ID,生效范围不在系统级别,可以忽略。
sid session ID for the session leader。
tpgid tty process group ID for the process group leader。

从上面的列表看出,各种ID最后都归结到pid和lwp(tid)上。所以理解各种ID, 最终归结为理解pid和lwp(tid)的联系和区别。

关于线程继承关系图如下:

               USER VIEW
 <-- PID 43 --> <----------------- PID 42 ----------------->
                     +---------+
                     | process |
                    _| pid=42  |_
                  _/ | tgid=42 | \_ (new thread) _
       _ (fork) _/   +---------+                  \
      /                                        +---------+
+---------+                                    | process |
| process |                                    | pid=44  |
| pid=43  |                                    | tgid=42 |
| tgid=43 |                                    +---------+
+---------+
 <-- PID 43 --> <--------- PID 42 --------> <--- PID 44 --->
                     KERNEL VIEW

上图很好地描述了用户视角(user view)和内核视角(kernel view)看到线程的差别:

需要指出的是,有时候在Linux中进程和线程的区分也不是十分严格的。即thread和process混用,pid和tid混用,根据上下文,还是可以清楚地区分对方想要表达的意思。上图中,从内核视角出发看到了pid 44,是从调度单元的角度出发,但是在top或ps命令中,你是绝对找不到一个pid为44的进程的,只能看到一个lwp(tid)为44的线程。

在这里可以清晰的看到,创建一个新的进程会操作系统分配一个新的PID和TGID,并且2个值相同,当主线程创建一个新的线程的时候,会分配一个新的PID,并且TGID和之前开始的进程一致。

Linux通过进程查看线程的方法

  1. htop 默认系统没有安装

    yum install htop  
    

    按t(显示进程线程嵌套关系)和H(显示线程),然后F4过滤进程名。

    按K,显示或者隐藏系统进程。默认不显示系统进程。太多了也不好看。

    $rpm -qi htop
    Name        : htop
    Version     : 2.2.0
    Release     : 3.el7
    Architecture: x86_64
    Install Date: Wed 31 Jul 2019 03:23:07 PM CST
    Group       : Unspecified
    Size        : 222730
    License     : GPLv2+
    Signature   : RSA/SHA256, Thu 17 Jan 2019 10:50:37 AM CST, Key ID 6a2faea2352c64e5
    Source RPM  : htop-2.2.0-3.el7.src.rpm
    Build Date  : Thu 17 Jan 2019 10:46:51 AM CST
    Build Host  : buildvm-24.phx2.fedoraproject.org
    Relocations : (not relocatable)
    Packager    : Fedora Project
    Vendor      : Fedora Project
    URL         : http://hisham.hm/htop/
    Bug URL     : https://bugz.fedoraproject.org/htop
    Summary     : Interactive process viewer
    Description :
    htop is an interactive text-mode process viewer for Linux, similar to top(1).  
    
  2. ps -eLf | grep java (快照,带线程命令,e是显示全部进程,L是显示线程,f全格式输出) -L选项可以看到线程,通常能打印出LWP和NLWP相关信息。
  3. pstree -p <pid> (显示进程树,不加pid显示所有)
  4. top -Hp <pid> - (实时) 查看某个进程的线程 默认top显示的是tasks数量,即进程。 可以按"H",来切换成线程。可以看到实际上有96个线程(Threads)。也可以直接利用top -H命令来直接打印线程情况。
  5. ps -T -p <pid> (快照)

推荐程度按数字从小到大。

pidstat -t [-p ${pid}] 打印出线程之间的关系 - pidstat 命令详解

pidstat -t [-p pid] 打印出线程之间的关系。

[albert@master.k8s /home/albert]
$pidstat -t -p 3953
Linux 3.10.0-957.1.3.el7.x86_64 (master.k8s)    07/31/2019      _x86_64_        (2 CPU)

04:02:33 PM   UID      TGID       TID    %usr %system  %guest    %CPU   CPU  Command
04:02:33 PM  1000      3953         -    0.00    0.00    0.00    0.00     1  tid
04:02:33 PM  1000         -      3953    0.00    0.00    0.00    0.00     1  |__tid
04:02:33 PM  1000         -      3954    0.00    0.00    0.00    0.00     1  |__THRAED-1
04:02:33 PM  1000         -      3955    0.00    0.00    0.00    0.00     1  |__THRAED-2