threadStackSize参数表示线程执行时的栈空间,因为每个线程执行时都要有自己的私有栈空间做数据存储,所以这是必须的, 这个值可以自己设置,不设置的话,系统会自己默认给个值:
linux64位系统默认是1024k,32位系统默认是320k,这个可以看图4-1
另外,自己查看threadStackSize栈大小可以通过下列方式:
java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
int
ContinueInNewThread(InvocationFunctions* ifn, jlong threadStackSize,
int argc, char **argv,
int mode, char *what, int ret)
{
/*
* 如果用户没有指定 threadStackSize 大小,那就要去检查下VM自带的默认值。Hotspot本身不再支持jdk1.1版本,但可以通过init args结构返回其默认堆栈大小,调用 GetDefaultJavaVMInitArgs(对应的实际函数是JNI_GetDefaultJavaVMInitArgs),这个函数可以取到VM设置的默认的 threadStackSize 大小。这里为什么用不支持jdk1.1但是又用jdk1.1来做参数,我想原因是历史遗留,当然这个也不重要,我们只需要知道这里是可以拿到默认 threadStackSize 大小的就行。
*/
if (threadStackSize == 0) {
struct JDK1_1InitArgs args1_1;
memset((void*)&args1_1, 0, sizeof(args1_1));
args1_1.version = JNI_VERSION_1_1;
ifn->GetDefaultJavaVMInitArgs(&args1_1); /* ignore return value */
if (args1_1.javaStackSize > 0) {
threadStackSize = args1_1.javaStackSize;
}
}
{ /* 创建一个新的线程去构建JVM并调用主函数 */
JavaMainArgs args;
int rslt;
// 参数赋值
args.argc = argc;
args.argv = argv;
args.mode = mode;
args.what = what;
args.ifn = *ifn;
rslt = ContinueInNewThread0(JavaMain, threadStackSize, (void*)&args);
/*
* 如果调用者认为存在错误(ret就是调用者给过来的),我们只需返回错误,否则我们返回被调用者的值
*/
return (ret != 0) ? ret : rslt;
}
}
图4-1
这个函数就是通过系统调用API创建一个新的线程,执行continuation函数指针指向的函数。
JNICALL这里介绍一下,后续文章中会出现多次JNI开头的类型,比如JNIEnv、JNIHandle等,顾名思义,这些都跟JNI相关,那么怎么理解呢?先看下图
参考图-1下linux的用户应用的调用流程,图-2是java应用在jdk环境下调用流程,写过java的都知道,java应用调用c/c++的函数是要通过jni来实现的,那么不光是这种情况要用jni,就是java应用底层的c/c++函数调用jvm虚拟机(hotspot)内部的函数实现,也是通过jni来实现的,从上面来看,严格意义来讲,jvm只有黄色标注的那一小块,其他块调用它,都需要jni来实现,所以从这点看,jni的调用存在两个层面,一个是java层面调用c/c++,另一个是c/c++层面调用虚拟机的实现。有了这个前提后面再看见jni就清晰。
int
ContinueInNewThread0(int (JNICALL *continuation)(void *), jlong stack_size, void * args) {
int rslt;
#ifndef __solaris__ // 这里是ifndef,也就非solaris,这里也就是指Linux实现
pthread_t tid; // 线程id
pthread_attr_t attr; // 线程属性
// 通过调用系统调用api,初始化线程属性结构,系统调用api的实现细节,可以自己去找手册查看
pthread_attr_init(&attr);
/* 设置线程的分离状态
* detachstate:
* PTHREAD_CREATE_DETACHED 表示分离
* PTHREAD_CREATE_JOINABLE 表示结合
* 如果设置成分离状态,表示无需关注新创建的线程执行结果,由它自行完成,并在完成后由操作系统回收资源;
* 如果设置成非分离状态,表示父线程需要拿到创建的子线程的执行结果
*/
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
if (stack_size > 0) {
pthread_attr_setstacksize(&attr, stack_size); // 设置线程栈大小
}
// 通过调用系统调用api pthread_create 创建一个新的线程,并指定线程创建后需要执行的任务函数是continuation,返回0表示创建成功。continuation是一个函数指针,指向要执行的函数
if (pthread_create(&tid, &attr, (void *(*)(void*))continuation, (void*)args) == 0) {
void * tmp;
pthread_join(tid, &tmp); // 等待子线程执行结果,想想java中的join方法,意思是一样的
rslt = (int)tmp;
} else {
// 线程创建失败,就在当前线程下执行任务函数
rslt = continuation(args);
}
// 线程属性销毁
pthread_attr_destroy(&attr);
#else /* __solaris__ */ // solaris操作系统的,不管了
thread_t tid;
long flags = 0;
if (thr_create(NULL, stack_size, (void *(*)(void *))continuation, args, flags, &tid) == 0) {
void * tmp;
thr_join(tid, NULL, &tmp);
rslt = (int)tmp;
} else {
/* See above. Continue in current thread if thr_create() failed */
rslt = continuation(args);
}
#endif /* !__solaris__ */
return rslt;
}
章节4.1.1.1中ContinueInNewThread函数调用ContinueInNewThread0函数时,第一个参数传的是JavaMain函数,告知线程创建成功后,要执行的函数。这个函数在java.c源文件中
int JNICALL
JavaMain(void * _args)
{
// 参数赋值
JavaMainArgs *args = (JavaMainArgs *)_args;
int argc = args->argc;
char **argv = args->argv;
int mode = args->mode;
char *what = args->what;
InvocationFunctions ifn = args->ifn;
// 初始化局部变量值
JavaVM *vm = 0;
JNIEnv *env = 0;
jclass mainClass = NULL;
jclass appClass = NULL; // actual application class being launched
jmethodID mainID;
jobjectArray mainArgs;
int ret = 0;
jlong start = 0, end = 0;
// 这个函数在linux和windows实现中都是空白,也就不管了,把它看作成java里的一个钩子函数即可
RegisterThread();
/* 初始化虚拟机,具体细节后续章节都会讲 */
start = CounterGet();
if (!InitializeJVM(&vm, &env, &ifn)) {
// 初始化失败,打印日志,退出程序
JLI_ReportErrorMessage(JVM_ERROR1);
exit(1); // 退出程序
}
// 是否打印设置过的信息,就是日志打印,不重要,略过
if (showSettings != NULL) {
ShowSettings(env, showSettings);
CHECK_EXCEPTION_LEAVE(1);
}
// 打印版本信息,不重要,略过
if (printVersion || showVersion) {
PrintJavaVersion(env, showVersion);
CHECK_EXCEPTION_LEAVE(0);
if (printVersion) {
LEAVE();
}
}
// 也是一些信息的打印,不重要,略过
if (printXUsage || printUsage || what == 0 || mode == LM_UNKNOWN) {
PrintUsage(env, printXUsage);
CHECK_EXCEPTION_LEAVE(1);
LEAVE();
}
// 释放启动虚拟时加载的jvm.cfg中的配置项占用的空间,jvm.cfg的配置加载在章节3.1.1.1的CreateExecutionEnvironment函数中,不过文章中这块读取被我删除了,想了解更多的,可以直接看源码,jvm.cfg这个文件在未来的版本中可能是要被抛弃的
FreeKnownVMs(); /* after last possible PrintUsage() */
// 日志打印
if (JLI_IsTraceLauncher()) {
end = CounterGet();
JLI_TraceLauncher("%ld micro seconds to InitializeJVM\n",
(long)(jint)Counter2Micros(end-start));
}
// 日志打印
if (JLI_IsTraceLauncher()){
int i;
printf("%s is '%s'\n", launchModeNames[mode], what);
printf("App's argc is %d\n", argc);
for (i=0; i < argc; i++) {
printf(" argv[%2d] = '%s'\n", i, argv[i]);
}
}
ret = 1;
// 加载执行主入口类main class
mainClass = LoadMainClass(env, mode, what);
CHECK_EXCEPTION_NULL_LEAVE(mainClass);
// LoadMainClass函数求出了mainClass,其实appClass跟mainClass是相同的,所以,这一步拿得就是mainClass,后面会讲这个细节
appClass = GetApplicationClass(env);
NULL_CHECK_RETURN_VALUE(appClass, -1);
// 看成钩子函数就行,linux/windows都没做具体实现
PostJVMInit(env, appClass, vm);
CHECK_EXCEPTION_LEAVE(1);
// 取出主类mainClass的main函数地址,并赋值给mainID
mainID = (*env)->GetStaticMethodID(env, mainClass, "main",
"([Ljava/lang/String;)V");
CHECK_EXCEPTION_NULL_LEAVE(mainID);
/* 构建平台依赖的参数数组 */
mainArgs = CreateApplicationArgs(env, argv, argc);
CHECK_EXCEPTION_NULL_LEAVE(mainArgs);
/* 这里才真正调用 java对应的main方法. */
(*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);
/*
* 如果exit code不为0,那么main执行抛出了异常
* 至此,整个java应用执行完毕
*/
ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
LEAVE(); // 这是一个宏定义的函数,内部主要做一次资源回收的工作
}