# JVM Xss参数与Linux 栈大小关系
Xss参数设置
在JVM中,我们可以指定-Xss来设置JVM线程栈大小,该参数描述如下。在Java中存在着不同的类型的线程,而对于Xss来说,指的就是Java线程栈的大小,所以我们这里关心ThreadStackSize即可。
product_pd(intx, ThreadStackSize, "Thread Stack Size (in Kbytes)") // java 线程栈大小
product_pd(intx, VMThreadStackSize, "Non-Java Thread Stack Size (in Kbytes)") // vmthread线程栈大小
product_pd(intx, CompilerThreadStackSize,"Compiler Thread Stack Size (in Kbytes)") // 编译线程栈大小
// x86 平台上,上述参数定义如下,0表示使用操作系统默认的线程栈大小
define_pd_global(intx, ThreadStackSize, 1024);
define_pd_global(intx, VMThreadStackSize, 1024);
define_pd_global(intx, CompilerThreadStackSize, 0);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
以下代码为Thread的初始化和启动代码,我们看到在Thread类中包含stackSize栈大小属性,通常我们设置其为0,让虚拟机来设置它的大小。
public class Thread implements Runnable {
private long stackSize; // 设置当前线程栈的大小,注意:只是给JVM一个提示,JVM可以选择使用或者忽略该参数
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0); // 使用默认线程栈大小为0
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
private native void start0(); // JNI 启动当前线程
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
我们这里主要关心start0 JNI方法即可,因为线程将会通过其创建OS的线程,然后设置其参数。通过源码我们看到:在JVM层面Java的Thread将由C++的JavaThread类来表示,同时将stackSize的值传入到JavaThread的构造器中创建线程,该值默认为0。
JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread))
JavaThread *native_thread = NULL; // 保存表示Thread的JavaThread C++对象
...
if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) { // 检测线程当前状态
throw_illegal_thread_state = true;
} else {
jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread)); // 获取Thread类中stackSize变量值
size_t sz = size > 0 ? (size_t) size : 0; // 若为0或者负数,那么取默认值0
native_thread = new JavaThread(&thread_entry, sz); // 创建JavaThread对象,thread_entry为启动函数入口,线程创建后将会调用该函数指针的代码,这里了解即可,注意:我们在分析Xss的作用
...
}
}
...
JVM_END
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
这里我们继续跟进JavaThread的创建过程,读者注意:不需要看懂C++语法,跟着注释走即可。通过源码我们得知:
- JavaThread仅仅表示JVM中的线程数据,而对于实际操作来说,需要创建操作系统级别的线程(LWP轻量级进程)来实现,所以这里使用os::create_thread来创建OS的线程
- OSThread C++对象用于表示操作系统线程的属性
- 我们使用pthread线程库来创建线程,Java线程在64位机上的默认值为1M,而对与Java线程来说,还可以 通过JavaThread::stack_size_at_create()来改变默认值
JavaThread::JavaThread(ThreadFunction entry_point, size_t stack_sz) :Thread(){
...
os::create_thread(this, thr_type, stack_sz);
...
}
bool os::create_thread(Thread* thread, ThreadType thr_type, size_t stack_size) {
OSThread* osthread = new OSThread(NULL, NULL); // 创建OSThread,表示一个操作系统级别的线程属性
...
// pthread线程库中用于设置线程属性的结构体,这里将其初始化
pthread_attr_t attr;
pthread_attr_init(&attr);
if (os::Linux::supports_variable_stack_size()) { // 判断OS是否支持可变栈大小(OS可以对栈进行扩容),现在的Linux内核都支持该属性,所以看该分支即可
if (stack_size == 0) { // 若栈大小为0,那么使用操作系统默认的栈大小作为默认值(size_t s = (thr_type == os::compiler_thread ? 4 * M : 1 * M),编译线程默认为4M,Java线程默认为1M)
stack_size = os::Linux::default_stack_size(thr_type);
switch (thr_type) {
case os::java_thread: // 这里我们关注Java线程即可,上面我们看到设置了默认值1M,但这里,我们可以使用stack_size_at_create方法来看看Xss设置的值,该方法将返回:_stack_size_at_create的值
stack_size = JavaThread::stack_size_at_create();
break;
case os::compiler_thread:
...
case os::vm_thread:
case os::pgc_thread:
case os::cgc_thread:
case os::watcher_thread:
...
}
}
// 将计算好的栈大小放入pthread_attr中
stack_size = MAX2(stack_size, os::Linux::min_stack_allowed);
pthread_attr_setstacksize(&attr, stack_size);
} else {
// 若不支持可变栈大小,那么让 pthread_create() 使用其默认固定值
}
...
int ret = pthread_create(&tid, &attr, (void* (*)(void*)) java_start, thread); // 调用pthread线程库创建线程,java_start为线程启动后执行的函数入口
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
那么,现在问题的关键就是_stack_size_at_create的值由谁来设置的。我们来看过程:由ThreadStackSize计算的栈大小,然后将其对齐到最近的操作系统页大小,设置即可。
size_t threadStackSizeInBytes = ThreadStackSize * K;
JavaThread::set_stack_size_at_create(round_to(threadStackSizeInBytes,vm_page_size()));
那么又来一个问题:Xss什么时候被转换设置的呢?来看代码。这下很明了了,Xss的值最终设置为ThreadStackSize变量即可。
if (match_option(option, "-Xss", &tail)) {
julong long_ThreadStackSize = 0;
ArgsRange errcode = parse_memory_size(tail, &long_ThreadStackSize, 1000);
if (errcode != arg_in_range) {
jio_fprintf(defaultStream::error_stream(),
"Invalid thread stack size: %s\n", option->optionString);
describe_range_error(errcode);
return JNI_EINVAL;
}
// 将xss后面设置的值设置为ThreadStackSize
FLAG_SET_CMDLINE(intx, ThreadStackSize,
round_to((int)long_ThreadStackSize, K) / K);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
小结
至此,我们看到在Java层面Xss最终设置为ThreadStackSize全局变量,随后,栈的大小将会通过pthread线程库的pthread_attr_t结构传入到pthread线程库中创建线程。
Pthread_Create 栈设置
通过pthread的线程创建源码我们看到:若调用方没有在pthread_attr_t中设置线程栈大小,那么将会取默认值,该值默认为rlimit中设置的值,比如:8M,若设置了线程栈,那么将取该线程栈大小。
int __pthread_create_2_1 (pthread_t *newthread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg){
const struct pthread_attr *iattr = (struct pthread_attr *) attr;
...
int err = ALLOCATE_STACK (iattr, &pd); // 分配线程栈
retval = create_thread (pd, iattr, &stopped_start,STACK_VARIABLES_ARGS, &thread_ran); // 创建线程
...
}
# define ALLOCATE_STACK(attr, pd) allocate_stack (attr, pd, &stackaddr) // 创建线程栈
static int allocate_stack (const struct pthread_attr *attr, struct pthread **pdp, ALLOCATE_STACK_PARMS) {
if (attr->stacksize != 0) // 线程栈不为0,那么取设置过后的线程栈大小
size = attr->stacksize;
else
size = __default_pthread_attr.stacksize; // 若为0,那么取默认值
...
// 检查栈大小,若栈太小,那么返回EINVAL
guardsize = (attr->guardsize + pagesize_m1) & ~pagesize_m1;
if (guardsize < attr->guardsize || size + guardsize < guardsize)
return EINVAL;
size += guardsize;
if (__builtin_expect (size < ((guardsize + __static_tls_size + MINIMAL_REST_STACK + pagesize_m1)
& ~pagesize_m1),0))
/* The stack is too small (or the guard too large). */
return EINVAL;
...
// 通过mmap创建线程栈(读者考虑下:如果线程栈设置过大怎么办?超过了rlimit,毕竟我们这里没有检测,答案必然是在内核中检测咯:if (address - vma->vm_start > current->rlim[RLIMIT_STACK].rlim_cur) return -ENOMEM )
mem = __mmap (NULL, size, (guardsize == 0) ? prot : PROT_NONE,MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0);
}
// 初始化默认线程属性
void __pthread_initialize_minimal_internal (void){
struct rlimit limit; // 获取OS的限制值
if (__getrlimit (RLIMIT_STACK, &limit) != 0 || limit.rlim_cur == RLIM_INFINITY)
limit.rlim_cur = ARCH_STACK_DEFAULT_SIZE; // 没有限制,那么取默认值:(2 * 1024 * 1024) 2MB
else if (limit.rlim_cur < PTHREAD_STACK_MIN)
limit.rlim_cur = PTHREAD_STACK_MIN; // 限制过小,那么取最小值:PTHREAD_STACK_MIN 16384 K
...
__default_pthread_attr.stacksize = limit.rlim_cur; // 设置pthread线程的默认栈大小
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
总结
Xss和rlimit RLIMIT_STACK的关系:
- Xss为JVM自身的设置属性(对于JVM而言,栈也是按需分配的,只不过最大为Xss指定值)
- RLIMIT_STACK为Linux对于线程栈的最大限制(对于Linux而言,线程栈是可以自动浮动扩容的,最大值为RLIMIT_STACK)
- 所以得出结论:Xss是在操作系统的rlimit上的进一步约束