虚拟机研究系列-站在Linux操作系统角度去看Thread(线程)

Linux进程与线程

无论是Java还是其他语言,无论如何定义线程模型和实现,基于底层角度而言都要归属到操作系统层面上的线程(LWP:轻量级线程技术映射到了内核线程)概念就不提了。

Richard Stevens对线程的描述(原文)

fork is expensive. Memory is copied from the parent to the child, all descriptors are duplicated in the child, and so on. Current implementations use a technique called copy-on-write, which avoids a copy of the parent’s data space to the child until the child needs its own copy. But, regardless of this optimization, fork is expensive. IPC is required to pass information between the parent and child after the fork. Passing information from the parent to the child before the fork is easy, since the child starts with a copy of the parent’s data space and with a copy of all the parent’s descriptors. But, returning information from the child to the parent takes more work. Threads help with both problems. Threads are sometimes called lightweight processes since a thread is “lighter weight” than a process. That is, thread creation can be 10–100 times faster than process creation. All threads within a process share the same global memory. This makes the sharing of information easy between the threads, but along with this simplicity comes the problem.

Richard Stevens对线程的描述(中文)

  • Linux中创建进程用fork操作,线程用clone操作

  • 通过ps -ef 看到的是进程列表,线程可以通过ps -eLf来查看

  • 用top命令的话,通过H开关也可以切换到线程视图。

  • 具体到Java线程模型,规范是没有规定Java线程和系统线程的对应关系的,不过目前常见的实现是一对一的。

参考

http://openjdk.java.net/groups/hotspot/docs/RuntimeOverview.html#Thread%20Management|outline

问题排查思路

如果创建不了Java线程,报错是

Exception in thread “main” java.lang.OutOfMemoryError: unable to create new native thread

下面是常见的问题原因

内存太小

在Java中创建一个线程需要消耗一定的栈空间,默认的栈空间是1M(可以根据应用情况指定-Xss参数进行调整),栈空间过小或递归调用过深,可能会出现StackOverflowError

对于一个进程来说,假设一定量可使用的内存,分配给堆空间的越多,留给栈空间的就越少。

  • 这个限制常见于32位Java应用,进程空间4G,用户空间2G(Linux下3G,所以通常堆可以设置更大些)。

    • 减去堆空间大小(通过-Xms、-Xmx指定范围)

    • 减去非堆空间(其中永久代部分通过PermSize、MaxPermSize指定大小,在Java8换成了MetaSpace,默认不限制大小)。

    • 再减去虚拟机自身消耗。

  • 剩下的就是栈空间,假设剩下300M,那么理论上就限制了只能开300线程(-Xss1M)。

不过对于64位应用,由于进程空间近乎无限大,所以可以不考虑这个问题

ulimit限制

线程数还会受到系统限制,系统限制通过ulimit -a可以查看到

https://ss64.com/bash/ulimit.html

caixj@Lenovo-PC:~$ 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) 7823

max locked memory      (kbytes, -l) 64

max memory size        (kbytes, -m) unlimited

open files                      (-n) 1024

pipe size            (512 bytes, -p) 8

POSIX message queues    (bytes, -q) 819200

real-time priority              (-r) 0

stack size              (kbytes, -s) 8192

cpu time              (seconds, -t) unlimited

max user processes              (-u) 7823

virtual memory          (kbytes, -v) unlimited

file locks                      (-x) unlimited

相关的限制有
  • max memory size :最大内存限制,在64位系统上通常都设置成unlimited

  • max user processes : 每用户总的最大进程数(包括线程)

  • virtual memory - 虚拟内存限制,在64位系统上通常都设置成unlimited

这些参数可以通过ulimit命令(当前用户临时生效)或者配置文件/etc/security/limits.conf(永久生效)进行修改。检查某个进程的限制是否生效,可以通过/proc/PID/limits查看运行时状态

参数sys.kernel.threads-max限制

https://www.kernel.org/doc/Documentation/sysctl/kernel.txt

This value controls the maximum number of threads that can be created

using fork().

During initialization the kernel sets this value such that even if the

maximum number of threads is created, the thread structures occupy only

a part (1/8th) of the available RAM pages.

The minimum value that can be written to threads-max is 20.

The maximum value that can be written to threads-max is given by the

constant FUTEX_TID_MASK (0x3fffffff).

If a value outside of this range is written to threads-max an error

EINVAL occurs.

The value written is checked against the available RAM pages. If the

thread structures would occupy too much (more than 1/8th) of the

available RAM pages threads-max is reduced accordingly.

表示系统全局的总线程数限制。设置方式有:

运行时限制,临时生效

echo 999999 > /proc/sys/kernel/threads-max

修改/etc/sysctl.conf,永久生效

sys.kernel.threads-max = 999999

参数sys.kernel.pid_max限制

https://www.kernel.org/doc/Documentation/sysctl/kernel.txt

PID allocation wrap value.  When the kernel's next PID value

reaches this value, it wraps back to a minimum PID value.

PIDs of value pid_max or larger are not allocated.

表示系统全局的PID号数值的限制。设置方式有:

运行时限制,临时生效

echo 999999 > /proc/sys/kernel/pid_max

修改/etc/sysctl.conf,永久生效

sys.kernel.pid_max = 999999

参数sys.vm.max_map_count限制

https://www.kernel.org/doc/Documentation/sysctl/vm.txt

This file contains the maximum number of memory map areas a process

may have. Memory map areas are used as a side-effect of calling

malloc, directly by mmap, mprotect, and madvise, and also when loading

shared libraries.

While most applications need less than a thousand maps, certain

programs, particularly malloc debuggers, may consume lots of them,

e.g., up to one or two maps per allocation.

The default value is 65536.

表示单个进程所能使用内存映射空间的数量限制。设置方式有:

方式1 运行时限制,临时生效

echo 999999 > /proc/sys/vm/max_map_count

方式2 修改/etc/sysctl.conf,永久生效

sys.vm.max_map_count = 999999

在其他资源可用的情况下,单个vm能开启的最大线程数是这个值的一半,可以通过cat /proc/PID/maps | wc -l查看目前使用的映射数量。

至于为什么只有一半,结合一些材料和源码分析了一下:

常见的警告信息是这样的,见

JavaThread::create_stack_guard_pages()
Attempt to protect stack guard pages failed.
Attempt to deallocate stack guard pages failed.

见current_stack_region()的图示,结合一下R大的相关解
释:http://hllvm.group.iteye.com/group/topic/37717

如下所示,通常的Java线程,会包括一个glibc的guard page和HotSpot的guard pages,其中JavaThread::create_stack_guard_pages()就是创建HotSpot Guard Pages用的,这里正常应该会有2次VMA,所以最大值只能有一半,从/proc/PID/maps中也可以看到增加一个线程会增加2个地址相连的映射空间。

// Java thread:

//

//  Low memory addresses

//    +------------------------+

//    |                        |\  JavaThread created by VM does not have glibc

//    |    glibc guard page    | - guard, attached Java thread usually has

//    |                        |/  1 page glibc guard.

// P1 +------------------------+ Thread::stack_base() - Thread::stack_size()

//    |                        |\

//    |  HotSpot Guard Pages  | - red and yellow pages

//    |                        |/

//    +------------------------+ JavaThread::stack_yellow_zone_base()

//    |                        |\

//    |      Normal Stack      | -

//    |                        |/

// P2 +------------------------+ Thread::stack_base()

//

// Non-Java thread:

//

//  Low memory addresses

//    +------------------------+

//    |                        |\

//    |  glibc guard page      | - usually 1 page

//    |                        |/

// P1 +------------------------+ Thread::stack_base() - Thread::stack_size()

//    |                        |\

//    |      Normal Stack      | -

//    |                        |/

// P2 +------------------------+ Thread::stack_base()

//

// ** P1 (aka bottom) and size ( P2 = P1 - size) are the address and stack size returned from

//    pthread_attr_getstack()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,928评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,192评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,468评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,186评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,295评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,374评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,403评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,186评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,610评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,906评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,075评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,755评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,393评论 3 320
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,079评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,313评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,934评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,963评论 2 351

推荐阅读更多精彩内容