《HotSpot实战》—— 2.3 系统初始化
本节书摘来异步社区《HotSpot实战》一书中的第2章,第2.3节,作者:陈涛,更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.3 系统初始化前面提到,系统初始化过程是JVM启动过程中的重要组成部分。初始化过程涉及到绝大多数的HotSpot内核模块,因此,了解这个过程对于理解HotSpot整体架构具有重要意义。图2-14描述了系统初始化的完整过程。
系统初始化的具体步骤如下所示。
(1)初始化输出流模块;
(2)配置Launcher属性
(3)初始化OS模块;
(4)配置系统属性;
(5)程序参数和虚拟机选项解析;
(6)根据传入的参数继续初始化操作系统模块;
(7)配置GC日志输出流模块;
(8)加载代理(agent)库;
(9)初始化线程队列;
(10)初始化TLS模块;
(11)调用vm_init_globals()函数初始化全局数据结构;
(12)创建主线程并加入线程队列;
(13)创建虚拟机线程;
(14)初始化JDK核心类,如java.lang.String、java.util.HashMap、java.lang.System、java.lang.ThreadGroup、java.lang.Thread、java.lang.OutOfMemoryError等;
(15)初始化系统类加载器模块,并初始化系统字典;
(16)启动SLT线程,即“SurrogateLockerThread”线程;
(17)启动“Signal Dispatcher”线程;
(18)启动“Attach Listener”线程;
(19)初始化即时编译器;
(20)初始化Chunk模块;
(21)初始化Management模块,启动“Service Thread”线程;
(22)启动“Watcher Thread”线程。
接下来,我们将对其中几个重要的步骤做详细的讲解。
2.3.1 配置OS模块OS模块的初始化包括两个环节。
init()函数:第一次初始化的时机是在TLS前,全局参数传入之前。 init_2()函数:第二次初始化的实际是在args解析后,全局参数传入之后。1.init()函数
第一次初始化能够完成一些固定的配置,主要包括以下几项内容。
设置页大小。 设置处理器数量。 初始化proc,打开“/proc/$pid”。 设置获得物理内存大小,保存在全局变量os::Linux::_physical_memory中; 获得原生主线程的句柄:获得指向原生线程的指针,并将其保存在全局变量os::Linux::_main_thread中。 系统时钟初始化:选用CLOCK_MONOTONIC类型时钟。从动态链接库“librt.so.1”或“librt.so”中将时钟函数clock_gettime装载进来,并保存在全局变量os::Linux::_clock_gettime中。注意 Linux的时钟与计时器。CLOCK_MONOTONIC提供相对时间,它的时间值是通过jiffies值来计算的,jiffies取决于系统的频率,单位是Hz,是周期的倒数,一般表示为一秒钟中断产生的次数。该时钟不受系统时钟源的影响,较为稳定,只受jiffies值的影响。按照POSIX规范,与CLOCK_MONOTONIC相对的另外一种时钟类型是CLOCK_REALTIME,这是系统实时时钟(RTC)。这是一个硬件时钟,用来持久存放系统时间,系统关闭后靠主板上的微型电池保持计时。系统启动时,内核通过读取RTC来初始化Wall Time,并存放在xtime变量中,即xtime是从cmos电路中取得的时间,一般是从某一历史时刻开始到现在的时间,也就是为了取得我们操作系统上显示的日期,它的精度是微秒。
2.init_2()函数
OS模块还有一部分配置是允许外部参数进行控制的。当解析完全局参数后,就可以根据配置参数进行配置,具体包括以下几项内容。
练习7
阅读源代码并调试跟踪,了解信号初始化过程,并思考HotSpot中初始化的信号对系统的意义。
(提示:见SR_initialize())
练习8
阅读源代码并调试跟踪,了解线程优先级策略的初始化过程。
(提示:见prio_init())。
2.3.2 配置系统属性配置虚拟机运行时的系统属性。首先介绍的是关于Launcher的属性,包括:“-Dsun.java.launcher”和“-Dsun.java.launcher.pid”。
接下来,要介绍的是一些与操作系统相关的系统属性,如表2-3所示。
2.3.3 加载系统库
在对虚拟机配置选项进行解析的阶段,Arguments模块根据虚拟机选项-agentlib或-agentpath,将需要加载的本地代理库逐一加入到代理库列表(AgentLibraryList)中。在加载代理库阶段,虚拟机将按照代理库列表中的库名,根据操作系统的库搜索规则,利用OS模块查找库并加载到虚拟机进程地址空间中。例如,若按照命令“java -agentlib:hprof”来启动应用程序的话,将加载JDK中代理库hprof,它的库文件名为libhprof.so或hprof.dll。
加载库操作需要在Java线程创建前完成,这样才能保证在Java线程需要调用时能够正确地找到本地库函数。
通过JVMTI接口,允许程序员开发自定义代理库,并通过选项-agentlib或–agentpath加载到虚拟机中。必须注意的是,由于agent代码将在虚拟机进程空间中运行,因此你的agent代码需要保证以下几点:多线程安全、可重入性、避免内存泄露或空指针、符合JVMTI和JNI规则等。如果你不小心触犯了这些准则,那么很有可能导致“out of memory”错误或虚拟机崩溃(JVM Crash,见第4章),这就是为什么我们在做JVM Crash分析时,需要考虑系统库或自定义库bug因素的原因。
除了JDK中代理库和自定义代理库,虚拟机还将加载本地库,如libc或ld库。为应用程序定位本地库可以通过两种方式:将库复制到应用程序的共享库路径下;或按照特定操作系统平台指定规则加载,如Solaris/Linux平台上根据环境变量LD_LIBRARY_PATH,而在Windows平台上根据环境变量PATH来定位本地库。
在系统初始化过程中,当代理库被加载进虚拟机进程后,虚拟机将在库中查找函数符号JVM_OnLoad或Agent_Onload并调用该函数,实现代理库与虚拟机的连接。
2.3.4 启动线程 1.线程状态和类型在JDK中定义了6种线程状态。 NEW:新创建但尚未启动的线程处于这种状态。通过new关键字创建了java.lang.Thread类(或其子类)的对象。 BLOCKED:线程受阻塞并等待某个监视器对象锁。当线程执行synchronized方法或代码块,但未获得相应对象锁时处于这种状态。 RUNNABLE:正在 Java 虚拟机中执行的线程处于这种状态。有三种情形,一种情形是Thread类的对象调用了start()函数,这时的线程就等待时间片轮转到自己,以便获得CPU;另一种情形是线程在处于RUNNABLE状态时并没有运行完自己的run()函数,时间片用完之后回到RUNNABLE状态;还有一种情形就是处于BLOCKED状态的线程结束了当前的BLOCKED状态之后重新回到RUNNABLE状态。 TERMINATED:已退出的线程处于这种状态。 TIMED_WAITING:等待另一个线程来执行取决于指定等待时间的操作。 WAITING:无限期地等待另一个线程来执行某一特定操作。 在JVM层面,HotSpot内部定义了线程的5种基本状态。 _thread_new,表示刚启动,正处在初始化过程中。 _thread_in_native,表示运行本地代码。 _thread_in_vm,表示在VM中运行。 _thread_in_Java,表示运行Java代码。 _thread_blocked,表示阻塞。
为了支持内部状态转换,还补充定义了其他几种过渡状态:__trans,其中thread_state_type分别表示上述5种基本状态类型。
在HotSpot中,定义了如清单2-28所示的几种线程类型,其类图如2-15所示。
清单2-28
来源:hotspot/src/share/vm/runtime/os.hpp
描述:线程类型
1 enum ThreadType { 2 vm_thread, // VM线程 3 cgc_thread, // 并发GC线程 4 pgc_thread, // 并行GC线程 5 java_thread, // Java线程 6 compiler_thread, // 编译器线程 7 watcher_thread, // watcher线程 8 os_thread // OS线程 9 };
图2-15 线程类型
2.创建主线程
主线程(main thread)是执行应用程序的“public static void main (String[] args)”方法的线程。对应OS线程ID为1的即为名为“main”为主线程,如图2-16所示。
系统初始化时,虚拟机首先创建的线程就是主线程。具体的创建过程如清单2-29所示。
清单2-29
来源:hotspot/src/share/vm/runtime/thread.cpp - Threads::creat_vm()
描述:创建main thread
1 JavaThread* main_thread = new JavaThread(); 2 main_thread- set_thread_state(_thread_in_vm); 3 main_thread- record_stack_base_and_size(); 4 main_thread- initialize_thread_local_storage(); 5 main_thread- set_active_handles(JNIHandleBlock::allocate_block()); 6 if (!main_thread- set_as_starting_thread()) { 7 vm_shutdown_during_initialization("Failed necessary internal allocation. Out of swap space"); 8 delete main_thread; 9 *canTryAgain = false; // dont let caller call JNI_CreateJavaVM again 10 return JNI_ENOMEM; 12 main_thread- create_stack_guard_pages();
首先,第1行代码中,JVM创建一个JavaThread类型的线程变量(刚创建时状态为_thread_new)。紧接着,第2行将线程状态设置为_thread_in_vm,表明该线程正处于在JVM中执行的状态。接下来,第3行记录线程栈的基址和大小;第4行,初始化线程本地存储区(TLS);第5行为线程设置JNI句柄;在第6~11行中,将通过OS模块创建原始线程,即OS主线程,并设置为可运行状态。接下来,在第12行中初始化主线程栈。
现在,main_thread实际上是一个JVM内部线程,其状态为JVM内部定义的线程状态_thread_in_vm。接下来需要创建java.lang.Thread线程:
13 initialize_class(vmSymbols::java_lang_System(), CHECK_0); 14 initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0); 15 Handle thread_group = create_initial_thread_group(CHECK_0); 16 Universe::set_main_thread_group(thread_group()); 17 initialize_class(vmSymbols::java_lang_Thread(), CHECK_0); 18 oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0); 19 main_thread- set_threadObj(thread_object); 20 java_lang_Thread::set_thread_status(thread_object, java_lang_Thread::RUNNABLE);
当Java层main线程创建完成后,就将其状态设置为RUNNABLE,开始运行,这样,一个Java主线程就开始运行了。
3.创建VMThread
VMThread是在JVM内部执行VMOperation的线程。VMOperation实现了JVM内部的核心操作,为其他运行时模块以及外部程序接口服务,在HotSpot中占有重要地位。
清单2-30描述了VMThread线程的创建过程。当VMThread线程创建成功后,在整个运行期间不断等待、接受并执行指定的VMOperation。
清单2-30
来源:hotspot/src/share/vm/runtime/thread.cpp - Threads::creat_vm()
描述:创建VMThread
1 // Create the VMThread 2 { TraceTime timer("Start VMThread", TraceStartupTime); 3 VMThread::create(); 4 Thread* vmthread = VMThread::vm_thread(); 5 if (!os::create_thread(vmthread, os::vm_thread)) 6 vm_exit_during_initialization("Cannot create VM thread. Out of system resources."); 7 // Wait for the VM thread to become ready, and VMThread::run to initialize 8 // Monitors can have spurious returns, must always check another state flag 10 MutexLocker ml(Notify_lock); 11 os::start_thread(vmthread); 12 while (vmthread- active_handles() == NULL) { 13 Notify_lock- wait(); 16 }
4.创建守护线程
守护线程包括“Signal Dispatcher”(该线程需要在“VMInit”事件发生前启动,详见os::signal_init()函数)、“Attach Listener”、“Watcher Thread”等。
在清单2-31中,vm_init_globals()函数实现了对全局性数据结构的初始化。
清单2-31
来源:hotspot/src/share/vm/runtime/initr.cpp
描述:初始化全局数据结构
1 void vm_init_globals() { 2 check_ThreadShadow(); 3 basic_types_init(); 4 eventlog_init(); 5 mutex_init(); 6 chunkpool_init(); 7 perfMemory_init(); 8 }
初始化的过程包括以下几个环节。
初始化Java基本类型系统。 分配全局事件缓存区,初始化事件队列。 初始化全局锁,如iCMS_lock、FullGCCount_lock、CMark_lock、SystemDictionary_lock、SymbolTable_lock等,表2-1列举了在一些主要模块中会涉及的锁,其中有部分所可以由VM选项UseConcMarkSweepGC和UseG1GC控制是否开启。 初始化ChunkPool,ChunkPool包括3个静态pool链表:_large_pool、_medium_pool和_small_pool。其实,这是HotSpot实现的内存池:系统全局中不会执行malloc/free操作,这样就能够有效避免malloc/free的抖动影响。内存池是系统设计的常用手段。 初始化JVM性能统计数据(Perf Data)区,可由VM选项UsePerfData控制是否开启。若开启VM选项PerfTraceMemOps,可在初始化时打印该空间的分配信息。 2.3.6 init_globals函数:初始化全局模块如清单2-32所示,init_globals()函数实现了对全局模块的初始化。
清单2-32
来源:hotspot/src/share/vm/runtime/init.cpp
描述:初始化全局数据结构
1 jint init_globals() { 2 HandleMark hm; 3 management_init(); 4 bytecodes_init(); 5 classLoader_init(); 6 codeCache_init(); 7 VM_Version_init(); 8 stubRoutines_init1(); 9 jint status = universe_init(); // dependent on codeCache_init and stubRoutines_init 10 if (status != JNI_OK) 11 return status; 12 interpreter_init(); // before any methods loaded 13 invocationCounter_init(); // before any methods loaded 14 marksweep_init(); 15 accessFlags_init(); 16 templateTable_init(); 17 InterfaceSupport_init(); 18 SharedRuntime::generate_stubs(); 19 universe2_init(); // dependent on codeCache_init and stubRoutines_init 20 referenceProcessor_init(); 21 jni_handles_init(); 22 vtableStubs_init(); 23 InlineCacheBuffer_init(); 24 compilerOracle_init(); 25 compilationPolicy_init(); 26 VMRegImpl::set_regName(); 27 if (!universe_post_init()) { 28 return JNI_ERR; 30 javaClasses_init(); // must happen after vtable initialization 31 stubRoutines_init2(); // note: StubRoutines need 2-phase init 32 if (VerifyBeforeGC !UseTLAB 33 Universe::heap()- total_collections() = VerifyGCStartAt) { 34 Universe::heap()- prepare_for_verify(); 35 Universe::verify(); // make sure were starting with a clean slate 37 if (PrintFlagsFinal) { 38 CommandLineFlags::printFlags(); 40 return JNI_OK; 41 }
这些模块构成了HotSpot整体功能的基础,也是本书后续章节所要探讨的核心内容。接下来,我们将对其中几个较为关键的内核模块做一个概念性的了解。
1.JMX:Management模块
在HotSpot工程结构中,我们了解到Services模块为JVM提供JMX等功能,JMX功能又可划分为如下4个主要模块,如图2-17所示。
Management模块:启动名为“Service Thread”的守护线程(如清单2-33所示),注意在较早的版本中该守护线程名为“Low Memory Detector”。若系统开启了选项-XX:ManagementServer,则加载并创建sun.management.Agent类,执行其startAgent()方法启动JMX Server。 RuntimeService模块:提供运行时模块的性能监控和管理服务,如applicationTime、jvmCapabilities等。 ThreadService模块:提供线程和内部同步系统的性能监控和管理服务,包括维护线程列表、线程相关的性能统计、线程快照、线程堆栈跟踪和线程转储等功能。 ClassLoadingService:提供类加载模块的性能监控和管理服务。
清单2-33
"Service Thread" daemon prio=6 tid=0x000000000b062000 nid=0x7274 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE
在JVM初始化时,会相继对这4个模块进行初始化,如清单2-34所示。
清单2-34
来源:hotspot/src/share/vm/runtime/init.cpp
描述:初始化Management模块
1 void management_init() { 2 Management::init(); 3 ThreadService::init(); 4 RuntimeService::init(); 5 ClassLoadingService::init(); 6 }
2.Code Cache
Code Cache是指代码高速缓存,主要用来生成和存储本地代码。这些代码片段包括已编译好的Java方法和RuntimeStubs等。
通过VM选项CodeCacheExpansionSize、InitialCodeCacheSize和ReservedCodeCacheSize可以配置该空间大小。
此外,若在Windows 64位平台上开启SHE机制1(即通过VM选项UseVectoredExceptions关闭Vectored Exceptions机制2,默认关闭),则需要向OS模块注册SHE。
3.StubRoutines
StubRoutines位于运行时模块。该模块的初始化分为两个阶段,第一阶段初始化(stubRoutines_init1),将创建一个名为“StubRoutines (1)”的BufferBlob,并未其分配CodeBuffer存储空间,并初始化StubRoutines。在第二阶段(stubRoutines_init2)中,创建名为“StubRoutines (2)”的BufferBlob,并为其分配CodeBuffer存储空间。并生成所有stubs并初始化entry points。
4.Universe
Universe模块将按照两个阶段进行初始化。第一阶段,根据VM选项配置的GC策略及算法,选择垃圾收集器和堆的种类,初始化堆。根据VM选项UseCompressedOops进行相关配置。若VM选项UseTLAB开启TLAB,则初始化TLAB缓存区。第二阶段,将对共享空间进行配置以及初始化vmSymbols和SystemDictionary等全局数据结构。
5.解释器
位于解释器模块。初始化解释器(interpreter),并注册StubQueue。可开启VM选项TraceBytecodes跟踪。
6.模板表
同样位于解释器模块。初始化模板表模块,将创建模版解释器使用的模板表,更多解释器内容请参考本书第7章。
7.stubs
位于运行时模块。在系统启动时,创建供各个运行时组件共享的stubs模块,诸如“wrong_method_stub”、“ic_miss_stub”、“resolve_opt_virtual_call”、“resolve_virtual_call”、“resolve_static_call”等。
除了上述模块,init_globals还将对下面这些模块进行初始化:字节码模块Bytecodes、类加载器模块ClassLoader、虚拟机版本模块,以及ReferenceProcessor、JNIHandles、VtableStubs、InlineCacheBuffer、VMRegImpl、JavaClasses等模块。这些模块的作用和实现,在本书后续章节将会陆续看到。
JVM--JVM回收机制图解整理 - 新生的对象直接分配到- 新生代(Eden) - S0是我们的Eden区出现无法存储某些对象的时候或者存储满了只有,整理Eden区就会存放到S0 - S1和S0的作用是一样的,但是他是针对S0的 - 当我们对象经历过15次GC之后,他就会被移入老年区(Old)
如何找到native方法对应的Hotspot源码 哈喽,我是子牙。十余年技术生涯,一路披荆斩棘从技术小白到技术总监到JVM专家到创业。技术栈如汇编、C语言、C++、Windows内核、Linux内核。特别喜欢研究虚拟机底层实现,对JVM有深入研究。分享的文章偏硬核,很硬的那种。
异步社区 异步社区(www.epubit.com)是人民邮电出版社旗下IT专业图书旗舰社区,也是国内领先的IT专业图书社区,致力于优质学习内容的出版和分享,实现了纸书电子书的同步上架,于2015年8月上线运营。公众号【异步图书】,每日赠送异步新书。
相关文章
- cmd修改系统时间
- 电商系统架构——系统鸟瞰图
- 微信公众号 - 禁用 H5 网页长按图片时弹出的菜单(转发给朋友 / 保存到手机 / 收藏 / 搜一搜),苹果安卓系统都可暴力 100% 完美禁用菜单,任何前端框架、任何浏览器都适用的解决方案
- bilibili高并发实时弹幕系统的实战之路
- Linux环境下系统安装JDK
- 《系统架构:复杂系统的产品设计与开发》——第1章,第1.4节本书结构
- 秒杀系统架构分析与实战
- Android系统服务-简介
- 在ubuntu系统中,python依赖存放的路径
- 《Arduino家居安全系统构建实战》——1.1 家居安全的基础设施
- 《Arduino家居安全系统构建实战》——1.3 我们的第一个模型(C#版本)
- Linux 系统的运行级别(runlevel)
- uname - 显示输出系统信息
- 国产系统UOS上的视频监控系统
- 《Power Designer系统分析与建模实战》——2.3 餐饮在线点评系统的需求模型
- vue实战入门进阶篇三:vue+elementui实现网站后台-系统框架搭建
- windows系统下安装gym运行atari游戏报错:ale_interface/ale_c.dll OSError
- 电商商品秒杀系统架构分析与实战
- Unity UI系统-NGUI-基本组件(一)
- Unity即时战略/塔防项目实战(一)——构造网格建造系统
- (十)Unity5.0新特性------新UI系统实战
- 飞信系统4月29飞信机器人解决方案无法升级日期后使用
- windows server 2016远程桌面进去,英文系统修改语言