新聞中心
- java 最多能創(chuàng)建多少線程? 由以下因素限制:
a. stack_size

建水網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)公司!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開(kāi)發(fā)、APP開(kāi)發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項(xiàng)目制作,到程序開(kāi)發(fā),運(yùn)營(yíng)維護(hù)。成都創(chuàng)新互聯(lián)公司于2013年成立到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選成都創(chuàng)新互聯(lián)公司。
b. max_user_processes
c. sys.vm.max_map_count
d. sys.kernel.threads-max
e. sys.kernel.pid_max
- java 線程的棧深能有多深?
a. stack_size
b. 本地變量表
1. java 最多能創(chuàng)建多少線程?
java 中的線程跟 linux 線程是 1:1 關(guān)系, 在java 中 new Thread() 為創(chuàng)建一個(gè)java 線程, Thread().start() 為啟動(dòng)java 線程, 但是 new Thread() 不會(huì)創(chuàng)建一個(gè)真正的 linux 線程,而是調(diào)用 start 方法之后 Thread().start(),才會(huì)創(chuàng)建一個(gè)真正 linux 線程。Thread的構(gòu)造函數(shù)是純Java代碼,start方法會(huì)調(diào)到一個(gè)native方法start0里,而start0其實(shí)就是JVM_StartThread這個(gè)方法。
openjdk: 1.8
jdk/src/hotspot/share/prims/jvm.cpp#2634
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
...
jlong size =
java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread));
size_t sz = size > 0 ? (size_t) size : 0;
native_thread = new JavaThread(&thread_entry, sz);
...
if (native_thread->osthread() == NULL) {
...
THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(),
"unable to create new native thread");
}
Thread::start(native_thread);
JVM_END
關(guān)注下最后的那個(gè)if判斷if (native_thread->osthread() == NULL),如果osthread為空,那將會(huì)拋出大家比較熟悉的unable to create new native thread OOM異常,因此osthread為空非常關(guān)鍵,后面會(huì)看到什么情況下osthread會(huì)為空。
另外大家應(yīng)該注意到了native_thread = new JavaThread(&thread_entry, sz),在這里才會(huì)真正創(chuàng)建一個(gè)線程。
openjdk: 1.8
jdk/src/hotspot/share/runtime/thread.cpp#1443
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :
Thread()
#ifndef SERIALGC
, _satb_mark_queue(&_satb_mark_queue_set),
_dirty_card_queue(&_dirty_card_queue_set)
#endif // !SERIALGC
{
if (TraceThreadEvents) {
tty->print_cr("creating thread %p", this);
}
initialize();
_jni_attach_state = _not_attaching_via_jni;
set_entry_point(entry_point);
// Create the native thread itself.
// %note runtime_23
os::ThreadType thr_type = os::java_thread;
thr_type = entry_point == &compiler_thread_entry ? os::compiler_thread :
os::java_thread;
os::create_thread(this, thr_type, stack_sz);
}
上面代碼里的os::create_thread(this, thr_type, stack_sz)會(huì)通過(guò)pthread_create來(lái)創(chuàng)建線程,對(duì)應(yīng) 代碼如下:
openjdk: 1.8
jdk/src/hotspot/os/linux/os_linux.cpp#891
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
assert(thread->osthread() == NULL, "caller responsible");
// Allocate the OSThread object
OSThread* osthread = new OSThread(NULL, NULL);
if (osthread == NULL) {
return false;
}
// set the correct thread state
osthread->set_thread_type(thr_type);
// Initial state is ALLOCATED but not INITIALIZED
osthread->set_state(ALLOCATED);
thread->set_osthread(osthread);
// init thread attributes
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// stack size
if (os::Linux::supports_variable_stack_size()) {
// calculate stack size if it's not specified by caller
if (stack_size == 0) {
stack_size = os::Linux::default_stack_size(thr_type);
switch (thr_type) {
case os::java_thread:
// Java threads use ThreadStackSize which default value can be
// changed with the flag -Xss
assert (JavaThread::stack_size_at_create() > 0, "this should be set");
stack_size = JavaThread::stack_size_at_create();
break;
case os::compiler_thread:
if (CompilerThreadStackSize > 0) {
stack_size = (size_t)(CompilerThreadStackSize * K);
break;
} // else fall through:
// use VMThreadStackSize if CompilerThreadStackSize is not defined
case os::vm_thread:
case os::pgc_thread:
case os::cgc_thread:
case os::watcher_thread:
if (VMThreadStackSize > 0) stack_size = (size_t)(VMThreadStackSize * K);
break;
}
}
stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
pthread_attr_setstacksize(&attr, stack_size);
} else {
// let pthread_create() pick the default value.
}
// glibc guard page
pthread_attr_setguardsize(&attr, os::Linux::default_guard_size(thr_type));
ThreadState state;
{
// Serialize thread creation if we are running with fixed stack LinuxThreads
bool lock = os::Linux::is_LinuxThreads() && !os::Linux::is_floating_stack();
if (lock) {
os::Linux::createThread_lock()->lock_without_safepoint_check();
}
pthread_t tid;
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread);
pthread_attr_destroy(&attr);
if (ret != 0) {
if (PrintMiscellaneous && (Verbose || WizardMode)) {
perror("pthread_create()");
}
// Need to clean up stuff we've allocated so far
thread->set_osthread(NULL);
delete osthread;
if (lock) os::Linux::createThread_lock()->unlock();
return false;
}
// Store pthread info into the OSThread
osthread->set_pthread_id(tid);
...
}
...
return true;
}
如果在new OSThread的過(guò)程中就失敗了,那顯然osthread為NULL,那再回到上面第一段代碼,此時(shí)會(huì)拋出java.lang.OutOfMemoryError: unable to create new native thread的異常,而什么情況下new OSThread會(huì)失敗,比如說(shuō)內(nèi)存不夠了,而這里的內(nèi)存其實(shí)是C Heap,而非Java Heap,指的是Linux 剩余內(nèi)存。由此可見(jiàn)從JVM的角度來(lái)說(shuō),影響線程創(chuàng)建的因素包括了Xmx,MaxPermSize,MaxDirectMemorySize,ReservedCodeCacheSize等,因?yàn)檫@些參數(shù)會(huì)影響剩余的內(nèi)存 。
另外注意到如果pthread_create執(zhí)行失敗,那通過(guò)thread->set_osthread(NULL)會(huì)設(shè)置空值,這個(gè)時(shí)候osthread也為NULL,因此也會(huì)拋出上面的OOM異常,導(dǎo)致創(chuàng)建線程失敗,因此接下來(lái)要分析下pthread_create失敗的因素。
glibc
pthread_create的實(shí)現(xiàn)在glibc里。
glibc
glibc/sysdeps/unix/sysv/linux/createthread.c#624
int
__pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg)
{
STACK_VARIABLES;
const struct pthread_attr *iattr = (struct pthread_attr *) attr;
struct pthread_attr default_attr;
...
struct pthread *pd = NULL;
int err = ALLOCATE_STACK (iattr, &pd);
int retval = 0;
if (__glibc_unlikely (err != 0))
{
retval = err == ENOMEM ? EAGAIN : err;
goto out;
}
...
}
上面我主要想說(shuō)的一段代碼是int err = ALLOCATE_STACK (iattr, &pd),顧名思義就是分配線程棧,簡(jiǎn)單來(lái)說(shuō)就是根據(jù)iattr里指定的stackSize,通過(guò)mmap分配一塊內(nèi)存出來(lái)給線程作為棧使用。
那我們來(lái)說(shuō)說(shuō)stackSize,這個(gè)大家應(yīng)該都明白,線程要執(zhí)行,要有一些棧空間,試想一下,如果分配棧的時(shí)候內(nèi)存不夠了,是不是創(chuàng)建肯定失???而stackSize在JVM下是可以通過(guò)-Xss指定的,當(dāng)然如果沒(méi)有指定也有默認(rèn)的值,下面是JDK6之后(含)默認(rèn)值的情況。
openjdk: 1.8
文件位置:jdk/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp#494
// return default stack size for thr_type
size_t os::Posix::default_stack_size(os::ThreadType thr_type) {
// default stack size (compiler thread needs larger stack)
#ifdef AMD64
size_t s = (thr_type == os::compiler_thread ? 4 * M : 1 * M);
#else
size_t s = (thr_type == os::compiler_thread ? 2 * M : 512 * K);
#endif // AMD64
return s;
}
估計(jì)不少人有一個(gè)疑問(wèn),棧內(nèi)存到底屬于-Xmx控制的Java Heap里的部分嗎,這里明確告訴大家不屬于,因此從glibc的這塊邏輯來(lái)看,JVM里的Xss也是影響線程創(chuàng)建的一個(gè)非常重要的因素。也就是 stack_size。
Linux
如果棧分配成功,那接下來(lái)就要?jiǎng)?chuàng)建線程了,大概邏輯如下:
openjdk: 1.8
jdk/src/hotspot/os/linux/os_linux.cpp#891
static int create_thread (struct pthread *pd, const struct pthread_attr *attr,
bool *stopped_start, void *stackaddr,
size_t stacksize, bool *thread_ran)
{
struct clone_args args =
{
.flags = clone_flags,
.pidfd = (uintptr_t) &pd->tid,
.parent_tid = (uintptr_t) &pd->tid,
.child_tid = (uintptr_t) &pd->tid,
.stack = (uintptr_t) stackaddr,
.stack_size = stacksize,
.tls = (uintptr_t) tp,
};
int ret = __clone_internal (&args, &start_thread, pd);
if (__glibc_unlikely (ret == -1))
return errno;
...
}
而create_thread其實(shí)是調(diào)用的系統(tǒng)調(diào)用clone ,clone系統(tǒng)調(diào)用最終會(huì)調(diào)用do_fork方法,接下來(lái)通過(guò)剖解這個(gè)方法來(lái)分析Kernel里還存在哪些因素。
max_user_process
linux: 3.10
kernel/fork.c#1199
retval = -EAGAIN;
if (atomic_read(&p->real_cred->user->processes) >=
task_rlimit(p, RLIMIT_NPROC)) {
if (!capable(CAP_SYS_ADMIN) && !capable(CAP_SYS_RESOURCE) &&
p->real_cred->user != INIT_USER)
goto bad_fork_free;
}
先看這么一段,這里其實(shí)就是判斷用戶的進(jìn)程數(shù)有多少,大家知道在linux下,進(jìn)程和線程其數(shù)據(jù)結(jié)構(gòu)都是一樣的,因此這里說(shuō)的進(jìn)程數(shù)可以理解為輕量級(jí)線程數(shù),而這個(gè)最大值是可以通過(guò)ulimit -u可以查到的,所以如果當(dāng)前用戶起的線程數(shù)超過(guò)了這個(gè)限制,那肯定是不會(huì)創(chuàng)建線程成功的。
max_map_count
在這個(gè)過(guò)程中不乏有malloc的操作,底層是通過(guò)系統(tǒng)調(diào)用brk來(lái)實(shí)現(xiàn)的,或者上面提到的棧是通過(guò)mmap來(lái)分配的,不管是malloc還是mmap,在底層都會(huì)有類似的判斷
linux: 3.10
mm/mmap.c#2480
if (mm->map_count >= sysctl_max_map_count)
return -ENOMEM;
如果進(jìn)程被分配的內(nèi)存段超過(guò)sysctl_max_map_count就會(huì)失敗,而這個(gè)值在linux下對(duì)應(yīng)/proc/sys/vm/max_map_count,默認(rèn)值是65530,可以通過(guò)修改上面的文件來(lái)改變這個(gè)閾值。
max_threads
linux: 3.10
kernel/fork.c#1217
/*
* If multiple threads are within copy_process(), then this check
* triggers too late. This doesn't hurt, the check is only there
* to stop root fork bombs.
*/
retval = -EAGAIN;
if (nr_threads >= max_threads)
goto bad_fork_cleanup_count;
該值是系統(tǒng)全局(包括非 JVM 進(jìn)程)最大線程數(shù)。查看和修改該值: /proc/sys/kernel/threads-max,這個(gè)值是受到物理內(nèi)存的限制,在fork_init的時(shí)候就計(jì)算好了,也就是說(shuō)linux 主機(jī)內(nèi)存越大改值越大。
linux: 3.10
kernel/fork.c#267
/*
* The default maximum number of threads is set to a safe
* value: the thread structures can take up at most half
* of memory.
*/
max_threads = mempages / (8 * THREAD_SIZE / PAGE_SIZE);
/*
* we need to allow at least 20 threads to boot a system
*/
if (max_threads < 20)
max_threads = 20;
pid_max
pid也存在限制
linux: 3.10
kernel/fork.c#1350
if (pid != &init_struct_pid) {
retval = -ENOMEM;
pid = alloc_pid(p->nsproxy->pid_ns);
if (!pid)
goto bad_fork_cleanup_io;
}
而alloc_pid的定義如下:
linux: 3.10
kernel/pid.c#287
struct pid *alloc_pid(struct pid_namespace *ns)
{
struct pid *pid;
enum pid_type type;
int i, nr;
struct pid_namespace *tmp;
struct upid *upid;
pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL);
if (!pid)
goto out;
tmp = ns;
pid->level = ns->level;
for (i = ns->level; i >= 0; i--) {
nr = alloc_pidmap(tmp);
if (nr < 0)
goto out_free;
pid->numbers[i].nr = nr;
pid->numbers[i].ns = tmp;
tmp = tmp->parent;
}
...
}
在alloc_pidmap中會(huì)判斷pid_max,而這個(gè)值的定義如下:
linux: 3.10
include/linux/threads.h#27
/*
* This controls the default maximum pid allocated to a process
*/
#define PID_MAX_DEFAULT (CONFIG_BASE_SMALL ? 0x1000 : 0x8000)
/*
* A maximum of 4 million PIDs should be enough for a while.
* [NOTE: PID/TIDs are limited to 2^29 ~= 500+ million, see futex.h.]
*/
#define PID_MAX_LIMIT (CONFIG_BASE_SMALL ? PAGE_SIZE * 8 : \
(sizeof(long) > 4 ? 4 * 1024 * 1024 : PID_MAX_DEFAULT))
linux: 3.10
kernel/pid.c#48
int pid_max = PID_MAX_DEFAULT;
#define RESERVED_PIDS 300
int pid_max_min = RESERVED_PIDS + 1;
int pid_max_max = PID_MAX_LIMIT;
這個(gè)值可以通過(guò)/proc/sys/kernel/pid_max來(lái)查看或者修改。
小結(jié):
通過(guò)對(duì)JVM,glibc,Linux kernel的源碼分析,我們暫時(shí)得出了一些結(jié)論:
- java thread stack 使用的內(nèi)存屬于系統(tǒng)可用內(nèi)存不歸 jvm heap,no heap 管理。
- java thread stack size 默認(rèn)是 1MB,改值也是影響能創(chuàng)建多少線程數(shù)的因素之一。
- linux kernel 層面因素有
- max_user_processes
- max_map_count
- mac_threads
- pid_max
2. Java 線程的棧深能有多深
線程中的 棧結(jié)構(gòu)如下:
每個(gè)棧幀包含:本地變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,返回地址等東西...也就是說(shuō)棧調(diào)用深度越大,棧幀就越多,就越耗內(nèi)存。
小結(jié):
- java threand stack depth 棧幀 沒(méi)有一個(gè)固定的參數(shù)設(shè)置。由系統(tǒng)內(nèi)存, stack size 動(dòng)態(tài)限制。
- java thread stack size 指的是一個(gè)線程能用到的最多內(nèi)存。
- java thread stack size 越大,擁有的 棧幀就越多,棧深也就越深。
- 線程中每一個(gè)棧幀如本地變量表越小,在固定的 stack size 下,棧幀數(shù)量也就越多,棧深也就越深。
3. 實(shí)驗(yàn)
|
主機(jī)配置 |
32C 128G |
|
操作系統(tǒng) |
Linux 4.19 |
|
max_user_process |
506354 |
|
max_threads |
1012708 |
|
pid_max |
4194303 |
|
max_map_count |
262144 |
java demo:
private static Object s = new Object();
private static int count = 0;
@RequestMapping(value = "/threads", method = RequestMethod.GET)
public String threads() {
log.info("threads");
for (; ; ) {
new Thread(new Runnable() {
public void run() {
synchronized (s) {
count += 1;
log.info("New thread #" + count);
}
for (; ; ) {
try {
Thread.sleep(1000);
} catch (Exception e) {
System.err.println(e);
}
}
}
}).start();
}
}
在該主機(jī)上直接運(yùn)行: 能創(chuàng)建 130,702 個(gè)線程。
2022-03-20 14:19:25.642 INFO 3112536 --- [ Thread-130703] com.example.opsdemo.IndexController : New thread #130702
Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00007efcebec6000, 12288, 0) failed; error='Cannot allocate memory' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 12288 bytes for committing reserved memory.
# An error report file with more information is saved as:
# /tmp/opsdemo/hs_err_pid3112536.log
2022-03-20 14:19:25.652 ERROR 3112536 --- [nio-8888-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.OutOfMemoryError: unable to create new native thread] with root cause
java.lang.OutOfMemoryError: unable to create new native thread
在該主機(jī)上以 pod 的方式部署容器運(yùn)行,能創(chuàng)建 16,334
2022-03-20 13:11:34.403 INFO 7 --- [ Thread-16335] com.example.opsdemo.IndexController : New thread #16334
2022-03-20 13:11:34.419 ERROR 7 --- [niohttp://www.dlmjj.cn/article/dhgcdop.html


咨詢
建站咨詢
