jvm15版本源码阅读之宏开始初始化到init_globals之前
- 1 宏之后到init_globals之前的初始化方法
- 1.1 SafepointMechanism::initialize
- 1.2 ostream_init_log
- 1.3 Arguments::init_libraries_at_startup
- 1.4 Arguments::init_agents_at_startup
- 1.5 vm_init_globals
- 1.6 JavaThread* main_thread = new JavaThread()
- 1.7 main_thread->set_thread_state
- 1.8 main_thread->initialize_thread_current
- 1.9 main_thread->record_stack_base_and_size
- 1.10 main_thread->register_thread_stack_with_NMT
- 1.11 main_thread->set_active_handles
- 1.12 main_thread->set_as_starting_thread
- 1.13 main_thread->create_stack_guard_pages
- 1.14 ObjectMonitor::Initialize
- 1.15 init_globals
- 2 小结
上一篇分析了在HOTSPOT_VM_INIT_BEGIN之前的初始化部分,这里开始从此宏之后的初始化部分.此部分内容会很多,涉及多个java的核心概念.
1 宏之后到init_globals之前的初始化方法
1.1 SafepointMechanism::initialize
这是安全点的初始化,主要和gc有关,具体和oopMap紧密相连.初始化的主要目的是设置Polling page,此类内存页也是诱使线程进入安全点的手段之一,如下所示
可见,设置了一个是系统默认页大小两倍的polling page,并设置为保护模式下的不可读属性,因此当有线程来访问此页时会报错,这个报错信号被jvm捕获后就可以阻塞线程,使其进入安全点的范畴.
Java的安全点和安全区机制是一个核心机制,之后会安排单独解析.
1.2 ostream_init_log
这个是在ostream_init之后进行的,是初始化输出流的log格式等.
1.3 Arguments::init_libraries_at_startup
为了能把-Xrun参数值转换成agentlib的库文件,这个初始化方法是判断agentlib文件列表是否为空.
1.4 Arguments::init_agents_at_startup
同上面的一样,这里是判断agent的列表是否为空,也是为了转换-Xrun的参数.
1.5 vm_init_globals
这个方法是初始化一些全局的数据结构和加载系统类.
1 threadshadow初始化,该类是处理线程的exception,是所有线程类的父类.线程的exception是从外部挂上去的,这个挂载就是threadshadow类,所以就像是影子一样如影随从.
2 basic_types初始化,基本类型的初始化,判断类型和大小是否是正确
3 eventlog的初始化,全部事件有消息,违例,多重定义,类未加载和破环优化的消息.
4 互斥锁初始化,使用宏定义来预先定义了所有的互斥锁类型
5 oop存储初始化,如下所示
共有6种类型的存储,jni全局引用,vm全局引用,jni弱引用,vm弱引用,字符串表的弱引用以及解析方法表弱引用.
6 小块内存初始化
7 永久区内存初始化
8 挂起的线程初始化
1.6 JavaThread* main_thread = new JavaThread()
创建java线程.jvm中只有两种线程,一种是JavaThread,另一种是NonJavaThread.
其构造方法如下:
首先,其继承自Thread类,进入该类,可见
其关联了一个os的thread,也就是系统的thread,在unix系统上就是posix的thread库,该库调用了系统调用来创建线程,所以JavaThread是内核线程的描述操作类,本质上是内核线程.
再回到下面的initialize方法,进入该方法,可见
这里只是截取了一部分代码,但是重要的初始化设置是ThreadSafepointState和SafepointMechanism的设置,可见每个java线程都支持安全点.
1.7 main_thread->set_thread_state
设置线程状态,这里为_thread_in_vm,即vm内运行线程
1.8 main_thread->initialize_thread_current
初始化当前线程,此处的当前是针对全局来说的,因为该方法调用了ThreadLocalStorage类,而该类每次只能存一个线程的key和指针.
首先判断ThreadLocalStorage存储的线程为空,然后设置了这个唯一的线程为main_thread.
1.9 main_thread->record_stack_base_and_size
记录该栈的栈顶和大小,进入此方法
主要就是获取栈顶,进入os::current_stack_base方法
核心方法current_stack_region,进入此方法,首先看到的是注释,如下
Java的线程栈布局分为三部分,从上往下看,第一部分是glibc的守护页,此处是修改字节码用的,第二部分是vm的守护内存页,第三部分是正常的栈区.
在之前的初始化过程中已经最先把guard page的部分先压入栈中了,包括red,yellow和reserve部分的内存页.
此图的标识比较迷惑,很明显,此处java线程的栈底是P1,但是图中标识为P2.代码中也给出了stack_base是lowaddress加上size的和,所以这里stak_base其实就是栈顶.可见java线程的栈的设计是向下生长的,在计算出当前的栈底之后,在其上(图中所示)加入了glibc区.
再进入方法内部
红框内的三个方法都是系统库的方法,分别是获取当前栈指,栈顶和大小,而目前我们依然还在0号线程内,所以此时获取的都是0号线程内的栈信息.
1.10 main_thread->register_thread_stack_with_NMT
此方法是在MemTracker类中记载了栈的信息,也就是前面给出的信息.
1.11 main_thread->set_active_handles
Handles这里只是用来描述线程正在做的事,这里把线程设置为了阻塞.
1.12 main_thread->set_as_starting_thread
设置为方法执行线程,进入该方法
执行了os::create_main_thread,进入该方法
执行了create_attached_thread方法,该方法就是把java线程的描述挂载到os的线程上,进入该方法
重要的就是创建了OSThread,也就是os的线程,至此java线程就和os线程对应起来了.
1.13 main_thread->create_stack_guard_pages
创建守护页,此处守护页仅指glibc部分,根据之前java线程的栈布局,仅在栈底之上加入此部分即可.这里就是用了mprotect方法来在正常的栈之上创建了glibc的守护页.
1.14 ObjectMonitor::Initialize
这个是和java synchronized的用法相关,是监视区的初始化,使用的都是宏,不难推测最终调用的是系统的mutex互斥锁.
1.15 init_globals
这个是最核心的初始化部分,涉及的内容有
1 management_init();
2 bytecodes_init();
3 classLoader_init1();
4 compilationPolicy_init();
5 codeCache_init();
6 VM_Version_init();
7 stubRoutines_init1();
8 universe_init();
9 gc_barrier_stubs_init();
10 interpreter_init_stub();
11 accessFlags_init();
12 InterfaceSupport_init();
13 SharedRuntime::generate_stubs();
14 universe2_init();
15 javaClasses_init();
16 interpreter_init_code();
17 invocationCounter_init();
18 referenceProcessor_init();
19 jni_handles_init();
20 vmStructs_init();
21 vtableStubs_init();
22 InlineCacheBuffer_init();
23 compilerOracle_init();
24 dependencyContext_init();
25 compileBroker_init();
26 JVMCI::initialize_globals();
27 universe_post_init();
28 stubRoutines_init2();
29 MethodHandles::generate_adapters();
这部分会在后续章节中分析.
2 小结
至此,在init_globals之前,main_thread在vm代码层面已经创建了,并且和os的一个线程已经对接上了,但是还没有启动,更重要的是,main_thread所记录的栈区信息依然是0号线程当前的栈区信息,也就是说目前所有的执行还在0号线程内,只是把目前0号线程的栈区布局改为了java的布局,也就是添加了守护页区.
从宏开始后,这里分析了一些初始化方法,但是还没有进入到最核心的初始化方法中,之后会继续分析这些最核心的初始化部分.同时涉及到了java的安全点及线程模型的一些内容,这些内容打算在分析完全貌后给出一个整理.