前言
在3. 初窥全貌 - main方法执行全流程-CSDN博客,我们了解了一个Java类的main函数在执行java命令 到 最终被执行的全路径, 但是那里面最重要的三步
1.创建vm;
2.加载main类;
3.执行main方法;
我们并没有展开学习,这一章,我们从创建VM开始学习,这是整个系列里最重的函数。
流程很多,但是别担心,这一章是介绍流程的,属于走马观花,有个全局认识即可,真正的细节都会在后续不同的章节里列出来。
1. 整体流程 create_vm()
网上摘录的流程(来源见参考链接)。
有的不是很重要的部分,我也没细看,在后续也不会列出,但是无关大局,不要在意。
- is_supported_jni_version检查是否支持JNI的版本是否是JNI_VERSION_1_1,JNI_VERSION_1_2,JNI_VERSION_1_4,JNI_VERSION_1_6,JNI_VERSION_1_8。
- ostream_init初始化输出流模块。
- process_sun_java_launcher_properties处理java启动的配置。处理-Dsun.java.launcher和-Dsun.java.launcher.pid=参数
- os::init初始化os的模块,依赖对应的操作系统(例如:Liunx系统调用的是(os_liunx.cpp中init函数)。
- init_system_properties首先初始化系统配置参数,例如:java.class.path、java.home等参数。
- JDK_Version_init初始化jdk的版本,主要有主版本号,次版本号等.
- Arguments::parse 解析启动参数。
- os::init_before_ergo() 主要large page的初始化,Liunx系统初始华large_page_size大小是2M。
- Arguments::apply_ergo() 应用参数,主要设置堆的物理大小以及初始化MetaSpace flags 和 alignments.
- PauseAtStartup参数为true,则调用os::pause(),os系统一直通过poll轮询等待JVM启动完成.这个参数默认是false,
- HOTSPOT_VM_INIT_BEGIN JVM启动
- 启动追踪JVM记录时间统计对象TraceVmCreationTime的start方法.
- os::init_2() 解析完JVM参数后调用操作系统的第二阶段初始化,。
- Arguments::adjust_after_os()进行os参数初始化后,进行参数的调整。
- ThreadLocalStorage::init() 对ThreadLocalStorage初始化。
- MemTracker::bootstrap_single_thread()单线程启动。
- ostream_init_log方法主要初始化 -Xloggc:参数中日志文件的创建。
- init_agents_at_startup方法是判断有没有-Xrun参数,将其转成-agentLibraryList中,将其转成-agentlib参数一样处理,主要是兼容之前jdk版本。
- create_vm_init_agents 创建虚拟机的包含参数-agentlib: -agentpath参数执行的agent的库以及-Xrun转换来的,并调用agent动态链接库中Agent_OnLoad方法进行初始化。
- 初始化线程的状态,_thread_list置空,_number_of_threads线程数量为0, _number_of_non_daemon_threads非守护线程数量为0.
- vm_init_globals 初始化全局的数据结构,并且在堆中创建系统的class。
- 创建JavaThread主线程对象,并设置状态为_thread_in_vm。并且record_stack_base_and_size记录栈基址和大小,initialize_thread_local_storage初始化线程本地存储,set_active_handles设置active的JNIHandler.set_as_starting_thread将自己作为启动main线程,create_stack_guard_pages受保护的栈页。
- ObjectMonitor::Initialize() 初始化JVM的对象监控,主要之后同步系统使用.
- MemTracker::bootstrap_multi_thread() JVM此时进入多线程启动阶段,
- init_globals方法是初始化全局模块,主要包括字节码、classloader、stubroutines、os、codeCache、interpreter、jnihandles、javaClasses等模块的初始化.
- cache_global_variables缓存全局变量,jdk1.8中是空的实现.
- 加一个线程的MutexLocker锁,然后执行thrads::add方法将主线程加入线程list列表中,主要是统计线程数量。
- JvmtiExport::transition_pending_onload_raw_monitors()是JVMTI的接口回调.
- MemTracker::start() 启动native的内容记录。
- VMThread::create() 创建虚拟机线程
- os::create_thread(vmthread, os::vm_thread)创建OSThread对象.
- 加MutexLocker锁,启动VM的线程,直到启动完成
- VerifyDuringStartup判断是否启动中验证。默认是false.
- DumpSharedSpaces是否导出共享空间状态,默认是fallse,
- 执行JVMTI的两个回调函数,enter_start_phase和post_vm_start可以监听到JVM启动事件。
- 加载并初始化java.lang.String和java.lang.System。java.lang.OutOfMemoryError、java.lang.NullPointerException等系统类.
- 创建线程组,并创建初始化线程并设置主线程的Thread的InstanceClass对象。
- 设置标志位_init_completed=true, 记录hotspot启动结束.
- SystemDictionary::compute_java_system_loader(THREAD);调用Classloader类中getSystemClassLoader获取系统类加载器。
- 执行JVMTI回调enter_live_phase函数
- AttachListener::init()是attacheListener的初始化.
- os::signal_init()将线程加入线程组中,并初始化java处理操作系统的信号的数据结构.
- BiasedLocking::init() 偏向锁的初始化
- JvmtiExport::post_vm_initialized执行JVM初始化事件的回调.
- 判断EnableInvokeDynamic是否开启invokeDynamic指令,默认是开启的,同时初始化java.lang.invoke包的MethodHandle、MemberName、MethodHandleNatives类。
- WatcherThread::start() 启动观察线程.
- os::init_3() JVM启动结束后操作系统的初始化.启动 MemNotifyThread线程监控低内存的
- create_vm_timer.end() JVM启动记录结束。
- 设置_vm_complete等于true,并返回JNI_OK
2. 重要步骤
2. ostream_init() 初始化输出流模块
具体细节见后续单独章节。
4. os::init os() os模块初始化
包括如下一些步骤:
- 设置内存页大小
- 初始化系统信息(处理器、物理内存等)
- 初始化操作系统信息
- 获取原生主线程的句柄
- 初始化系统时钟
具体细节见后续单独章节。
5. init_system_properties()
初始化系统配置参数,例如:java.class.path、java.home等参数,封装成SystemProperty, 放到一个全局链表_system_properties中。
同时在os::init_system_properties_values() 中 (linux下实现)
设置动态链接库目录 Arguments::set_dll_dir(buf);
设置 JAVA_HOM Arguments::set_java_home(buf)
设置引导路径分隔符 set_boot_path('/', ':') - Arguments::set_sysclasspath(sysclasspath);
设置本地库的搜索路径 Arguments::set_library_path(ld_library_path);
设置扩展目录 Arguments::set_ext_dirs(buf);
设置认可目录 Arguments::set_endorsed_dirs(buf);
设置系统jar包搜索路径
hotspot/src/share/vm/runtime/arguments.cpp
SystemProperty类继承自CHeapObj
// Initialize system properties key and value.
void Arguments::init_system_properties() {
// 初始化系统配置参数,例如:java.class.path、java.home等参数, 封装成SystemProperty
// _system_properties 可以算作一个全局变量
// 而 SystemProperty里面自带一个对一个SystemProperty的引用,所以构成了一个链表
PropertyList_add(&_system_properties, new SystemProperty("java.vm.specification.name",
"Java Virtual Machine Specification", false));
PropertyList_add(&_system_properties, new SystemProperty("java.vm.version", VM_Version::vm_release(), false));
PropertyList_add(&_system_properties, new SystemProperty("java.vm.name", VM_Version::vm_name(), false));
PropertyList_add(&_system_properties, new SystemProperty("java.vm.info", VM_Version::vm_info_string(), true));
// following are JVMTI agent writeable properties.
// Properties values are set to NULL and they are
// os specific they are initialized in os::init_system_properties_values().
_java_ext_dirs = new SystemProperty("java.ext.dirs", NULL, true);
_java_endorsed_dirs = new SystemProperty("java.endorsed.dirs", NULL, true);
_sun_boot_library_path = new SystemProperty("sun.boot.library.path", NULL, true);
_java_library_path = new SystemProperty("java.library.path", NULL, true);
_java_home = new SystemProperty("java.home", NULL, true);
_sun_boot_class_path = new SystemProperty("sun.boot.class.path", NULL, true);
_java_class_path = new SystemProperty("java.class.path", "", true);
// Add to System Property list.
PropertyList_add(&_system_properties, _java_ext_dirs);
PropertyList_add(&_system_properties, _java_endorsed_dirs);
PropertyList_add(&_system_properties, _sun_boot_library_path);
PropertyList_add(&_system_properties, _java_library_path);
PropertyList_add(&_system_properties, _java_home);
PropertyList_add(&_system_properties, _java_class_path);
PropertyList_add(&_system_properties, _sun_boot_class_path);
// Set OS specific system properties values
os::init_system_properties_values();
}
6. JDK_Version_init()
加载libverify.so、 libjava.so,初始化jdk的主、次版本号
void JDK_Version::initialize() {
jdk_version_info info;
assert(!_current.is_valid(), "Don't initialize twice");
// 加载 libverify.so、libjava.so、( 定义__OpenBSD__时,加载libnet.so)
void *lib_handle = os::native_java_library();
jdk_version_info_fn_t func = CAST_TO_FN_PTR(jdk_version_info_fn_t,
os::dll_lookup(lib_handle, "JDK_GetVersionInfo0"));
if (func == NULL) {
// JDK older than 1.6
_current._partially_initialized = true;
} else {
(*func)(&info, sizeof(info));
int major = JDK_VERSION_MAJOR(info.jdk_version);
int minor = JDK_VERSION_MINOR(info.jdk_version);
int micro = JDK_VERSION_MICRO(info.jdk_version);
int build = JDK_VERSION_BUILD(info.jdk_version);
if (major == 1 && minor > 4) {
// We represent "1.5.0" as "5.0", but 1.4.2 as itself.
major = minor;
minor = micro;
micro = 0;
}
// 使用解析后的版本信息初始化 _current 成员,包括主要版本、次要版本、微版本、更新版本、特殊更新版本、构建号以及其他相关的标志。
_current = JDK_Version(major, minor, micro, info.update_version,
info.special_update_version, build,
info.thread_park_blocker == 1,
info.post_vm_init_hook_enabled == 1,
info.pending_list_uses_discovered_field == 1);
}
os::native_java_library()
主要作用是加载几个函数库:libverify.so、libjava.so、libnet.so, 很多重要的实现都以动态链接库的形式封装起来。
void* os::native_java_library() {
if (_native_java_library == NULL) {
char buffer[JVM_MAXPATHLEN];
char ebuf[1024];
// 加载 libverify.so,并装入内存
if (dll_build_name(buffer, sizeof(buffer), Arguments::get_dll_dir(), "verify")) {
dll_load(buffer, ebuf, sizeof(ebuf));
}
// 加载 libjava.so ,并装入内存,
if (dll_build_name(buffer, sizeof(buffer), Arguments::get_dll_dir(), "java")) {
// _native_java_library 指向加载后的函数库指针,方便后续符号解析时使用
_native_java_library = dll_load(buffer, ebuf, sizeof(ebuf));
}
if (_native_java_library == NULL) {
vm_exit_during_initialization("Unable to load native library", ebuf);
}
#if defined(__OpenBSD__)
// 加载 libnet.so,并装入内存
if (dll_build_name(buffer, sizeof(buffer), Arguments::get_dll_dir(), "net")) {
dll_load(buffer, ebuf, sizeof(ebuf));
}
#endif
}
static jboolean onl oaded = JNI_FALSE;
if (onLoaded) {
// We may have to wait to fire onl oad until TLS is initialized.
if (ThreadLocalStorage::is_initialized()) {
const char *onLoadSymbols[] = JNI_ONLOAD_SYMBOLS;
// 下面一段代码就是把 解析 libjava.so 中的JNI_OnLoad函数,并执行
JNI_OnLoad_t JNI_OnLoad = CAST_TO_FN_PTR( JNI_OnLoad_t, dll_lookup(_native_java_library, onl oadSymbols[0]));
if (JNI_OnLoad != NULL) {
JavaThread* thread = JavaThread::current();
ThreadToNativeFromVM ttn(thread);
HandleMark hm(thread);
jint ver = (*JNI_OnLoad)(&main_vm, NULL);
onl oaded = JNI_TRUE;
if (!Threads::is_supported_jni_version_including_1_1(ver)) {
vm_exit_during_initialization("Unsupported JNI version");
}
}
}
}
// 最终返回的是libjava.so的函数库指针
return _native_java_library;
}
7. Arguments::parse() 参数解析
处理之前解析出来的JavaVMOption,给各种全局参数设值,设置了一些对象对齐值, 供后续不同的模块使用。
具体细节见后续单独章节。
8. os::init_before_ergo() 确定处理器核数和指令集
hotspot/src/share/vm/runtime/os.cpp
void os::init_before_ergo() {
// 通过系统调用api,拿到有效的cpu核数
initialize_initial_active_processor_count();
// 大页初始化,默认是没有实现
large_page_init();
// 确定平台特性,即所使用的平台架构指令集, linux这里啥都没做
VM_Version::init_before_ergo();
}
9. Arguments::apply_ergo
主要设置堆的物理大小以及初始化MetaSpace flags 和 alignments.
13. os::init_2()
主要针对内存、栈、线程等与os模块密切相关的部分进行初始化。
包括如下一些步骤:
- 分配polling_page,
- 没有开放使用内存屏障的情况下,分配mem_serialize_page
- 分配共享内存,设置大页内存
- 初始化内核信号,安装信号处理函数JVM_handle_linux_signal
- 对线程栈进行一系列配置,比如设置线程栈基址、栈大小
- 检测glibc版本,选择使用NPTL 还是 LinuxThreads模型
- NUMA相关配置
- 文件描述符数量相关配置,改为系统允许的最大大小
- 初始化用于线程创建的锁
- 初始化线程优先级策略
具体细节见后续单独章节。
15. intialize TLS()
ThreadLocalStorage::init()
void ThreadLocalStorage::init() {
assert(!is_initialized(),"More than one attempt to initialize threadLocalStorage");
pd_init(); // x86下空实现
// ThreadLocalStorage::_thread_index 静态变量
set_thread_index(os::allocate_thread_local_storage());
generate_code_for_get_thread(); // x86下空实现
}
static void restore_thread_pointer(void* p) {
Thread* thread = (Thread*) p;
os::thread_local_storage_at_put(ThreadLocalStorage::thread_index(), thread);
}
int os::allocate_thread_local_storage() {
pthread_key_t key;
int rslt = pthread_key_create(&key, restore_thread_pointer);
assert(rslt == 0, "cannot allocate thread local storage");
return (int)key;
}
在多线程的环境下,进程内的所有线程共享进程的数据空间。因此全局变量为所有线程共享。
出于程序设计的需要,在程序设计中有时需要保存线程自己的全局变量,这种特殊的变量仅在线程内部有效。
试想如果你的一个线程里面嵌套调用了很多函数,而你又想在这些函数之间使用一个公共的变量,如常见的errno,它返回标准的错误码。
errno不应该是一个局部变量。几乎每个函数都应该可以访问他,但他又不能作为是一个全局变量。
所以我们需要声明的这个全局变量只属于我们当前这个实例线程(同一个void *(*start_routine)(void *)可以实例化很多线程),其他的线程访问不到,故引入线程特定数据TSD(TSD thread specific data)来解决, 也叫 TLS thread local storage。
它的实现涉及高级编程语言、编译器和链接器的支持。
TSD采用了一键多值的技术,即一个键对应多个值。访问数据时都是通过键值来访问,好像是对一个变量进行访问,其实是在访问不同的数据。
目前使用TLS的方法有多种,POSIX的pthread.h提供了一组API来实现此功能
#include <pthread.h> int pthread_key_create(pthread_key_t *key, void (*destructor)(void*)); // Returns 0 on success, or a positive error number on error // 为线程局部数据创建一个新键,并通过key指向新创建的键缓冲区 int pthread_key_delete(pthread_key_t key); // Returns 0 on success, or a positive error number on error int pthread_setspecific(pthread_key_t key, const void *value); // Returns 0 on success, or a positive error number on error void* pthread_getspecific(pthread_key_t key); // Returns pointer, or NULL if no thread-specific data is assciated with key
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
第一个参数为指向一个键值的指针,
第二个参数指明了一个destructor函数,如果这个参数不为空,那么当每个线程结束时,系统将调用这个函数来释放绑定在这个键上的内存块。
key一旦被创建,所有线程都可以访问它,但各线程可根据自己的需要往key中填入不同的值,这就相当于提供了一个同名而不同值的全局变量,一键多值。
一键多值靠的是一个关键数据结构数组即TSD池,创建一个TSD就相当于将结构数组中的某一项设置为“in_use”,并将其索引返回给*key,然后设置清理函数。
参考:
21. vm_init_globals()
全局数据结构初始化
hotspot/src/share/vm/runtime/init.cpp
void vm_init_globals() {
// 验证ThreadShadow的实现,主要是验证线程对象的_pending_exception属性位置,
check_ThreadShadow();
// 基础类型初始化
basic_types_init();
// 各种事件日志初始化,不影响主流程,先忽略
eventlog_init();
// 各种互斥锁的初始化,不影响主流程,先忽略
mutex_init();
// 内存(块)池初始化,可以复用之前申请的chunk,避免频繁去系统malloc(即delete后不通过系统释放,而是放到chunkpool里复用)
chunkpool_init();
/*
* 热点性能数据存储内存初始化。热点性能数据的收集通过-XX:+UsePerfData控制开通与否,默认是开通的。
* 启用 PerfData 后,HotSpot JVM 会在目录下为每个 JVM 进程创建一个文件。通过工具扫描此目录
* 以查找正在运行的 JVM。如果禁用 PerfData,这些工具将不会自动发现JVM
* jstat 是依赖于热点性能数据的标准 JDK 工具之一。 不适用于禁用了性能数据的 JVM。
* 细节上,这块也可以忽略
*/
perfMemory_init();
}
basic_types_init()
hotspot/src/share/vm/utilities/globalDefinitions.cpp
void basic_types_init() {
// 下面的操作就是验证各个类型的在32位和64位机器上的内存占用位数
#ifdef ASSERT
#ifdef _LP64
assert(min_intx == (intx)CONST64(0x8000000000000000), "correct constant");
assert(max_intx == CONST64(0x7FFFFFFFFFFFFFFF), "correct constant");
assert(max_uintx == CONST64(0xFFFFFFFFFFFFFFFF), "correct constant");
assert( 8 == sizeof( intx), "wrong size for basic type");
assert( 8 == sizeof( jobject), "wrong size for basic type");
#else
assert(min_intx == (intx)0x80000000, "correct constant");
assert(max_intx == 0x7FFFFFFF, "correct constant");
assert(max_uintx == 0xFFFFFFFF, "correct constant");
assert( 4 == sizeof( intx), "wrong size for basic type");
assert( 4 == sizeof( jobject), "wrong size for basic type");
#endif
assert( (~max_juint) == 0, "max_juint has all its bits");
assert( (~max_uintx) == 0, "max_uintx has all its bits");
assert( (~max_julong) == 0, "max_julong has all its bits");
assert( 1 == sizeof( jbyte), "wrong size for basic type");
assert( 2 == sizeof( jchar), "wrong size for basic type");
assert( 2 == sizeof( jshort), "wrong size for basic type");
assert( 4 == sizeof( juint), "wrong size for basic type");
assert( 4 == sizeof( jint), "wrong size for basic type");
assert( 1 == sizeof( jboolean), "wrong size for basic type");
assert( 8 == sizeof( jlong), "wrong size for basic type");
assert( 4 == sizeof( jfloat), "wrong size for basic type");
assert( 8 == sizeof( jdouble), "wrong size for basic type");
assert( 1 == sizeof( u1), "wrong size for basic type");
assert( 2 == sizeof( u2), "wrong size for basic type");
assert( 4 == sizeof( u4), "wrong size for basic type");
// 验证各个基础类型代表的字符,比如B代表byte,C代表char,依此类推
int num_type_chars = 0;
for (int i = 0; i < 99; i++) {
if (type2char((BasicType)i) != 0) {
assert(char2type(type2char((BasicType)i)) == i, "proper inverses");
num_type_chars++;
}
}
// 类型个数只能是11个,8个基础类型 + 1个void类型 + 1个object类型 + 1个array类型
assert(num_type_chars == 11, "must have tested the right number of mappings");
assert(char2type(0) == T_ILLEGAL, "correct illegality");
{
for (int i = T_BOOLEAN; i <= T_CONFLICT; i++) {
BasicType vt = (BasicType)i;
BasicType ft = type2field[vt];
switch (vt) {
// the following types might plausibly show up in memory layouts:
case T_BOOLEAN:
case T_BYTE:
case T_CHAR:
case T_SHORT:
case T_INT:
case T_FLOAT:
case T_DOUBLE:
case T_LONG:
case T_OBJECT:
case T_ADDRESS: // random raw pointer
case T_METADATA: // metadata pointer
case T_NARROWOOP: // compressed pointer
case T_NARROWKLASS: // compressed klass pointer
case T_CONFLICT: // might as well support a bottom type
case T_VOID: // padding or other unaddressed word
// layout type must map to itself
assert(vt == ft, "");
break;
default:
// non-layout type must map to a (different) layout type
assert(vt != ft, "");
assert(ft == type2field[ft], "");
}
// every type must map to same-sized layout type:
assert(type2size[vt] == type2size[ft], "");
}
}
// These are assumed, e.g., when filling HeapWords with juints.
assert(is_power_of_2(sizeof(juint)), "juint must be power of 2");
assert(is_power_of_2(HeapWordSize), "HeapWordSize must be power of 2");
assert((size_t)HeapWordSize >= sizeof(juint), "HeapWord should be at least as large as juint");
assert(sizeof(NULL) == sizeof(char*), "NULL must be same size as pointer");
#endif
// 设置Java与OS的线程优先级对应关系,
// 默认都是-1(JavaPriority1_To_OSPriority到JavaPriority10_To_OSPriority初始值都是-1),
// 也就是没有优先级
if( JavaPriority1_To_OSPriority != -1 )
os::java_to_os_priority[1] = JavaPriority1_To_OSPriority;
if( JavaPriority2_To_OSPriority != -1 )
os::java_to_os_priority[2] = JavaPriority2_To_OSPriority;
if( JavaPriority3_To_OSPriority != -1 )
os::java_to_os_priority[3] = JavaPriority3_To_OSPriority;
if( JavaPriority4_To_OSPriority != -1 )
os::java_to_os_priority[4] = JavaPriority4_To_OSPriority;
if( JavaPriority5_To_OSPriority != -1 )
os::java_to_os_priority[5] = JavaPriority5_To_OSPriority;
if( JavaPriority6_To_OSPriority != -1 )
os::java_to_os_priority[6] = JavaPriority6_To_OSPriority;
if( JavaPriority7_To_OSPriority != -1 )
os::java_to_os_priority[7] = JavaPriority7_To_OSPriority;
if( JavaPriority8_To_OSPriority != -1 )
os::java_to_os_priority[8] = JavaPriority8_To_OSPriority;
if( JavaPriority9_To_OSPriority != -1 )
os::java_to_os_priority[9] = JavaPriority9_To_OSPriority;
if(JavaPriority10_To_OSPriority != -1 )
os::java_to_os_priority[10] = JavaPriority10_To_OSPriority;
// Set the size of basic types here (after argument parsing but before stub generation).
if (UseCompressedOops) { // 压缩指针
// Size info for oops within java objects is fixed
heapOopSize = jintSize; // 堆中oop对象指针, 4
LogBytesPerHeapOop = LogBytesPerInt; // oop对象指针字节的幂次方,2
LogBitsPerHeapOop = LogBitsPerInt; // oop对象指针位数的幂次方,5
BytesPerHeapOop = BytesPerInt; // oop对象指针字节数, 4
BitsPerHeapOop = BitsPerInt; // oop对象指针位数,32
} else {
heapOopSize = oopSize; // 32位机器占4字节,64位机器占8字节
LogBytesPerHeapOop = LogBytesPerWord;// 32位机器为2,64位机器为3
LogBitsPerHeapOop = LogBitsPerWord; // 32位机器为5,64位机器为6
BytesPerHeapOop = BytesPerWord; // 32位机器为4字节,64位机器为8字节,
BitsPerHeapOop = BitsPerWord; // 32位机器为32位,64位机器为64位
}
// object和array在jvm中的表现都是指针,所以就是 heapOopSize 的值
_type2aelembytes[T_OBJECT] = heapOopSize;
_type2aelembytes[T_ARRAY] = heapOopSize;
}
enum BasicType {
T_BOOLEAN = 4,
T_CHAR = 5,
T_FLOAT = 6,
T_DOUBLE = 7,
T_BYTE = 8,
T_SHORT = 9,
T_INT = 10,
T_LONG = 11,
T_OBJECT = 12,
T_ARRAY = 13,
T_VOID = 14,
T_ADDRESS = 15,
T_NARROWOOP = 16,
T_METADATA = 17,
T_NARROWKLASS = 18,
T_CONFLICT = 19, // for stack value type with conflicting contents
T_ILLEGAL = 99
};
mutex_init()
在这里,你可以看到vm中所需要的各种锁。
void mutex_init() {
def(tty_lock , Mutex , event, true ); // allow to lock in VM
def(CGC_lock , Monitor, special, true ); // coordinate between fore- and background GC
def(STS_lock , Monitor, leaf, true );
if (UseConcMarkSweepGC) {
def(iCMS_lock , Monitor, special, true ); // CMS incremental mode start/stop notification
}
if (UseConcMarkSweepGC || UseG1GC) {
def(FullGCCount_lock , Monitor, leaf, true ); // in support of ExplicitGCInvokesConcurrent
}
if (UseG1GC) {
def(CMark_lock , Monitor, nonleaf, true ); // coordinate concurrent mark thread
def(CMRegionStack_lock , Mutex, leaf, true );
...............
}
def(ParGCRareEvent_lock , Mutex , leaf , true );
def(DerivedPointerTableGC_lock , Mutex, leaf, true );
...............
#ifndef PRODUCT
def(FullGCALot_lock , Mutex , leaf, false); // a lock to make FullGCALot MT safe
#endif
def(BeforeExit_lock , Monitor, leaf, true );
def(PerfDataMemAlloc_lock , Mutex , leaf, true ); // used for allocating PerfData memory for performance data
def(PerfDataManager_lock , Mutex , leaf, true ); // used for synchronized access to PerfDataManager resources
...............
// CMS_modUnionTable_lock leaf
// CMS_bitMap_lock leaf + 1
// CMS_freeList_lock leaf + 2
def(Safepoint_lock , Monitor, safepoint, true ); // locks SnippetCache_lock/Threads_lock
def(Threads_lock , Monitor, barrier, true );
#if INCLUDE_JFR
def(JfrMsg_lock , Monitor, leaf, true);
。。。。。。
#ifndef SUPPORTS_NATIVE_CX8
def(JfrCounters_lock , Mutex, special, false);
#endif
#endif
#ifndef SUPPORTS_NATIVE_CX8
def(UnsafeJlong_lock , Mutex, special, false);
#endif
}
chunkpool_init()
内存(块)池初始化,可以复用之前申请的内存块(chunk),避免频繁去系统malloc(即delete后不通过系统释放,而是放到chunkpool里复用)
所谓的ChunkPool 就是一个Chunk的链表(Chunk通过_next链接),这里的初始化,生成了四种size的chunkpool,并为每个pool生成了第一个chunk.
hotspot/src/share/vm/memory/allocation.cpp
void chunkpool_init() {
ChunkPool::initialize();
}
22. main_thread() 为“main线程”补个证
线程的属性
main_thread的创建
hotspot/src/share/vm/runtime/thread.cpp create_vm() 中创建main_thread 部分
hotspot/src/share/vm/runtime/thread.cpp
/*
绑定主线程到os级线程(在此处,也就是当前线程),JavaThread是继承自Thread,
注意,这里的JavaThread只是虚拟机层面的Java级线程,并不是我们Java编码层面的线程,
这个概念要区分来,但是Java编码层面的线程Thread,最终在虚拟机底层就是用JavaThread来表示的,
从这点看,两者又是一样,抛开编码层面,就可以总结为:JavaThread就是一个Java线程的表示。
*/
JavaThread* main_thread = new JavaThread();
// 设置线程状态,thread_state状态跟Java层面线程的状态(new、running等)不是一个概念,
// 这个值主要标注线程当前运行状况,或者说在哪个层面运行,比如Java层面、虚拟机层面、native层面
main_thread->set_thread_state(_thread_in_vm);
// 设置栈底、栈大小、栈溢出边界位置
main_thread->record_stack_base_and_size();
// 初始化线程本地存储
main_thread->initialize_thread_local_storage();
// 分配JNI句柄存储块,并设置关联到当前线程
main_thread->set_active_handles(JNIHandleBlock::allocate_block());
// 将当前线程 与 一个OSThread绑定到一块
if (!main_thread->set_as_starting_thread()) {
// 主线程绑定失败,虚拟机退出
vm_shutdown_during_initialization(
"Failed necessary internal allocation. Out of swap space");
delete main_thread;
*canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
return JNI_ENOMEM;
}
// 创建线程栈保护区(将保护区那几页设为)
main_thread->create_stack_guard_pages();
25. init_globals 全局模块初始化
hotspot/src/share/vm/runtime/init.cpp
jint init_globals() {
HandleMark hm;
// 管理模块初始化,包括时间统计、各种指标计数、性能数据统计、运行时数据统计和监控、类加载服务情况(加载类数量、加载类失败数量、加载字节数等)
management_init();
// 字节码初始化。Hotspot执行字节码时,大部分是解释执行,这就需要将字节码转换成机器码来执行,执行前需要知道字节码是什么格式、类型、占用长度、结果类型等信息,这一步就是做这些初始化工作的
bytecodes_init();
// 类加载器初始化
classLoader_init();
// 代码缓存空间初始化。虚拟机为了提升执行绩效,会把一些热点指令代码提前编译为机器码并缓存起来,这一步就是做这个操作的
codeCache_init();
// 虚拟机版本号初始化
VM_Version_init();
// os的扩展初始化,jdk8版本的hotspot里,这个函数没做任何具体实现
os_init_globals();
// JVM虚拟机调用Java程序时需要借助stub存根来处理,大家做rpc/webservice就了解stub存根的意义,就是对远程调用函数的一个声明/映射,
// 这里的stubRoutines的意思与这个差不多,这个比较关键,后面章节会重点细讲
stubRoutines_init1();
// universe 翻译就是万物的意思,这一步初始化,主要是对元空间内存、各种符号表、字符表、Java堆内存空间相关的初始化
jint status = universe_init();
if (status != JNI_OK)
return status;
// 解释器初始化
interpreter_init();
// 方法调用计数初始化
invocationCounter_init();
// 标记清除初始化,GC相关的,后面讲GC时再讲
marksweep_init();
// 这个仅仅是校验AccessFlags的大小,里面是一个assert断言
accessFlags_init();
// 模板表 TemplateTable 的初始化,TemplateTable中保存了各个字节码的执行模板(目标代码生成函数和参数)
templateTable_init();
// 这个函数没做什么,就是通过系统调用srand设置了随机数的随机种子,方便rand()调用时根据种子生成一个伪随机数
InterfaceSupport_init();
// 生成运行时的一些stub函数,例如错误方法处理的stub、静态解析处理的stub等
SharedRuntime::generate_stubs();
// universe_init的进一步初始化,里面内容很多,主要概括为对基础数据类型、数组类型、对象类型创建对应的Klass对象、bootstrap类加载器的初始化等
universe2_init();
// 引用处理 referenceProcessor 初始化
referenceProcessor_init();
// JNI句柄分配空间块并初始化
jni_handles_init();
#if INCLUDE_VM_STRUCTS
vmStructs_init(); // vm内部数据结构的初始化
#endif // INCLUDE_VM_STRUCTS
// 初始化vtable数组的大小,并预分配数组空间,vtable(虚函数表)是c++中对虚函数表的存储和表示
vtableStubs_init();
// 代码缓冲区初始化
InlineCacheBuffer_init();
// oracle编译器初始化
compilerOracle_init();
// 代理编译初始化,主要做两件事:1.选择编译器;2.如何进行编译
compilationPolicy_init();
// 编译日志记录
compileBroker_init();
// 初始化寄存器数组,给每个寄存器初始化名字,汇编代码需要
VMRegImpl::set_regName();
// 初始化universe后的逻辑操作,包括加载基础类、构建报错信息、安全检查、加载器、引用管理等,用过spring的就知道里面有很多post后置逻辑操作
if (!universe_post_init()) {
return JNI_ERR;
}
// 系统及java class的初始化
javaClasses_init();
// stubRoutines的第二阶段初始化
stubRoutines_init2();
#if INCLUDE_NMT
NMT_stack_walkable = true;
#endif // INCLUDE_NMT
// 打印完成标志
if (PrintFlagsFinal) {
CommandLineFlags::printFlags(tty, false);
}
// 返回初始化成功标志
return JNI_OK;
}
30. 创建VMThread
在执行命令 java [options] xxx.class param1 param2 ... paramn 时,实际上Linux是fork()出了一个新的进程(Java进程)来执行后续操作,并且在执行的过程中又会创建其他的线程(Java线程、VM线程、GC线程等)。
在Java执行的过程中,是需要Java线程与VM双向交互的,例如:在线程的启停时需要执行一些必要的任务(比如保存安全点),以及垃圾收集期间线程的协调和同步,所以Hotspot JVM需要管理这些线程。
但是线程又是由操作系统创建的,不是JVM创建的,因此在Hotspot中,每个Java线程都会映射到一个操作系统线程,同时需要跟踪和管理这些线程,确保Hotspot内部的状态与线程本身状态同步。
这也是之前提到的 Thread(java层) - osThread(vm层) - 内核线程(内核级别)三层体系。
vm中会有很多的线程, 而VMThread线程则只会有一个,它的作用是啥,后续我们再来补上。
{ MutexLocker mu(Threads_lock);
// 这里继续之前主线程的处理,最终要把主线程 main_thread 加入到线程链(表)中
Threads::add(main_thread);
}
// Any JVMTI raw monitors entered in onl oad will transition into
// real raw monitor. VM is setup enough here for raw monitor enter.
JvmtiExport::transition_pending_onload_raw_monitors();
// Create the VMThread
{
TraceTime timer("Start VMThread", TraceStartupTime);
VMThread::create(); // 创建VMThread 对象
Thread* vmthread = VMThread::vm_thread(); // 拿到上面创建的 VMThread 对象
if (!os::create_thread(vmthread, os::vm_thread))
vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");
// vmthread 创建完后,等待运行
{
MutexLocker ml(Notify_lock);
// 父子线程handshaking
os::start_thread(vmthread);
while (vmthread->active_handles() == NULL) {
Notify_lock->wait();
}
}
}
assert (Universe::is_fully_initialized(), "not initialized");
if (VerifyDuringStartup) { // 默认是 false
// Make sure we're starting with a clean slate.
VM_Verify verify_op;
VMThread::execute(&verify_op); // 执行验证操作
}
36. 初始化核心类
java.lang.String 、 java.lang.System 、 java.lang.ThreadGroup 等
{
TraceTime timer("Initialize java.lang classes", TraceStartupTime);
if (EagerXrunInit && Arguments::init_libraries_at_startup()) {
create_vm_init_libraries();
}
// 初始化String类
initialize_class(vmSymbols::java_lang_String(), CHECK_0);
// Initialize java_lang.System (needed before creating the thread)
// 初始化System类、ThreadGroup类
initialize_class(vmSymbols::java_lang_System(), CHECK_0);
initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0);
// 创建一个名为“main"的ThreadGroup oop,其parent字段为 一个名为"system"的ThreadGroup
Handle thread_group = create_initial_thread_group(CHECK_0);
// 有一个全局变量 oop _main_thread_group, 为其赋值
Universe::set_main_thread_group(thread_group());
// 初始化Thread类
initialize_class(vmSymbols::java_lang_Thread(), CHECK_0);
// 为main_thread创建一个Java层面的Thread对象
oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
main_thread->set_threadObj(thread_object);
// Set thread status to running since main thread has been started and running.
java_lang_Thread::set_thread_status(thread_object, ava_lang_Thread::RUNNABLE);
// 初始化Class类
// The VM creates & returns objects of this class. Make sure it's initialized.
initialize_class(vmSymbols::java_lang_Class(), CHECK_0);
// The VM preresolves methods to these classes. Make sure that they get initialized
initialize_class(vmSymbols::java_lang_reflect_Method(), CHECK_0);
initialize_class(vmSymbols::java_lang_ref_Finalizer(), CHECK_0);
// 调用System类的 initializeSystemClass()
call_initializeSystemClass(CHECK_0);
// get the Java runtime name after java.lang.System is initialized
JDK_Version::set_runtime_name(get_java_runtime_name(THREAD));
JDK_Version::set_runtime_version(get_java_runtime_version(THREAD));
// an instance of OutOfMemory exception has been allocated earlier
initialize_class(vmSymbols::java_lang_OutOfMemoryError(), CHECK_0);
initialize_class(vmSymbols::java_lang_NullPointerException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ClassCastException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArrayStoreException(), CHECK_0);
initialize_class(vmSymbols::java_lang_ArithmeticException(), CHECK_0);
initialize_class(vmSymbols::java_lang_StackOverflowError(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalMonitorStateException(), CHECK_0);
initialize_class(vmSymbols::java_lang_IllegalArgumentException(), CHECK_0);
}
42. os::signal_init()
将线程加入线程组中,并初始化java处理操作系统的信号的数据结构.
43. BiasedLocking::init() 偏向锁的初始化
void BiasedLocking::init() {
if (UseBiasedLocking) {
// 如果设置了启动延迟时间(BiasedLockingStartupDelay),
// 则创建一个定时任务(EnableBiasedLockingTask),在程序运行一段时间后启动,以减轻启动时偏向锁撤销所带来的性能影响。
// 这是因为在 JVM 启动过程中,由于偏向锁撤销而频繁触发安全点,可能会导致性能下降。
if (BiasedLockingStartupDelay > 0) { // BiasedLockingStartupDelay 默认时间是 4000 毫秒,
EnableBiasedLockingTask* task = new EnableBiasedLockingTask(BiasedLockingStartupDelay);
task->enroll();
} else { // 如果没有设置启动延迟时间,直接执行偏向锁启用操作(VM_EnableBiasedLocking)
VM_EnableBiasedLocking op(false);
VMThread::execute(&op); // 交由VMThread执行
}
}
}
总结
本章中大部分内容都没有展开详细说明,因为想在一篇或者几篇文章中把细节展开是不可能的。
同时,这里仍处在走马观花的阶段,有个印象就可以了。
在后续的章节里,我们会慢慢深挖细节,不用着急。
附录
宏 _JNI_IMPORT_OR_EXPORT_
__attribute__((visibility("default")))
gcc编译参数
-fvisibility=default / internal / hidden / protected
上述表示:
gcc在编译动态库的时候visibility有四个选项,只有使用default和protected选项编译时,编译出来的动态库的符号是可以被外界调用的;
而编译时使用internal和hidden选项时,如果函数内没有:__attribute ((visibility("default")))声明,动态库使隐藏的不可被外界调用。
查看物理CPU个数
# 查看物理CPU个数
cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l
# 查看每个物理CPU中core的个数(即核数)
cat /proc/cpuinfo| grep "cpu cores"| uniq
Debug模式
xdebug简单来说就是远程的debug模式,通过开启JVM的debug模式来达到远程断点(debug)的效果,线上代码本地可以进行debug运行查看
要让远程服务器运行的代码支持远程调试,则服务端启动的时候必须加上特定的 JVM 参数,这些参数是:
不同的JDK版本需要设置不同的配置:
JDK 9 or later
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:9999
JDK 5-8
-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9999
JDK 1.4.x
-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9999
JDK 1.3.x or earlier
-Xnoagent -Djava.compiler=NONE -Xdebug
-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=9999
这里的9999则是服务端开放的端口,后期客户端IDEA需要连接当前端口进行远程交互和调试。
但是我们需要注意的是,这个9999端口在服务端一定要放开防火墙或者安全组;
具体端口看项目需求;运行服务端jar包程序则如下(JDK 5-8版本):
java -jar -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9999 ./SwaggerDemo-0.0.1-SNAPSHOT.jar > app.log &
JVM 的 -X参数是非标准选项,在不同的版本的 JVM 中,参数可能不同,可通过 java -X查看参数。
-Xdebug:通知JVM工作在debug模式下;
-Xnoagent 禁用默认sun.tools.debug调试器。
-Djava.compiler=NONE 禁止 JIT 编译器的加载。
-Xrunjdwp:通知JVM使用(java debug wire protocol)来运行调试环境;加载JDWP的JPDA参考执行实例
transport:监听Socket端口连接方式(也可以dt_shmem共享内存方式,但限于windows机器,并且服务提供端和调试端只能位于同一台机);
server:server=y表示当前是调试服务端,=n表示当前是调试客户端
suspend:suspend=n表示启动时不中断,一般用于设置主动连接;suspend=y表示启动时就进入调试模式,一般用于被动连接
address=prot 表示远程debug所开放的端口号
对象头
栈保护页
了解保护页,先从几个问题开始吧
1. 为什么线程栈有栈帧了,还要有保护页?
答:在操作系统中内存可以看成是一个大数组,这就有一个问题,线程之间可能会互相踩了别人的内存空间,所以栈空间也存在这个问题。
为了防止栈溢出时破坏栈之外的数据结构,语言运行时会保留最大栈上限limit所在的一片区域,这就是保护页(Guard Page),也可叫哨兵值(Sentry)。当函数返回时检查保护页的值,如果被修改,说明已到达最大栈上限,此时就要输出错误并终止程序。
2. Java栈溢出后,保护页的作用?
答:Java也有栈溢出,发生时会抛出StackOverflowError,输出调用栈和代码行数。这些过程都需要额外执行很多方法,但是发生栈溢出就意味着不能继续执行方法了(因为方法执行需要栈空间)。
为了解决这个问题,HotSpot虚拟机在C++语言运行时提供的保护页之外会使用create_stack_guard_pages()创建额外的保护页来支持栈溢出错误处理,如下所示。
// Java thread:
//
// Low memory addresses
// +------------------------+
// | |\ JavaThread created by VM does not have glibc
// | glibc guard page | - guard, attached Java thread usually has
// | |/ 1 page glibc guard.
// P1 +------------------------+ Thread::stack_base() - Thread::stack_size()
// | |\
// | HotSpot Guard Pages | - red and yellow pages
// | |/
// +------------------------+ JavaThread::stack_yellow_zone_base()
// | |\
// | Normal Stack | -
// | |/
// P2 +------------------------+ Thread::stack_base()
//
// Non-Java thread:
//
// Low memory addresses
// +------------------------+
// | |\
// | glibc guard page | - usually 1 page
// | |/
// P1 +------------------------+ Thread::stack_base() - Thread::stack_size()
// | |\
// | Normal Stack | -
// | |/
// P2 +------------------------+ Thread::stack_base()
//
// ** P1 (aka bottom) and size ( P2 = P1 - size) are the address and stack size returned from
// pthread_attr_getstack()
3.保护页有几种类型及各类型的作用?
答:线程栈的最大上限处会保留三块保护页(Guard Page)支持栈溢出,分别是Reserved Page、Yellow Page、Red Page。
主要内容分析如下:
Reserved Page:Reserved Page旨在为一些关键段(Critical Section)方法保存外栈空间,让有@ jdk.internal.vm.annotation.ReservedStackAccess注解的方法能完成执行(如lock与unlock之间的代码),防止关键段方法中的对象出现不一致的状态。
当执行关键段方法时分配的栈顶触及Reserved Page,则虚拟机会将Reserved Page标记为正常栈空间,供关键段方法完成执行,然后再抛出StackOVerflowError。Reserved Page的大小由-XX:StackReservedPages指定。
Yellow Page:如果执行Java代码时分配的栈顶触及YellowPage,则虚拟机会抛出StackOverflowError,然后将Yellow Page标为正常栈空间,让抛异常的代码有栈可用。
Yellow Page的数量由参数-XX:StackYellowPages=指定,最后Yellow Page占用的空间是page数量*page大小(page的大小一般是4KB,如果开启-XX:+UseLargePages且操作系统支持large page特性,page的大小可达到4MB,但是一般都不用)。
Red Page:如果执行Java代码时分配的栈顶触及Red Page,则虚拟机会创建错误日志hs_err_pid.log,然后关闭虚拟机。同样,为了让创建日志的代码执行,虚拟机会将Red Page标为正常栈空间。RedPage的大小由-XX:StackRedPages指定。
Shadow Page:前面区域都是执行Java代码出现栈溢出的错误处理。虚拟机还可能执行native方法或者虚拟机本身需要执行的方法,这些方法的栈大小不像Java代码一样能确定(编译器能确定但是虚拟机不能),
如果开启虚拟机参数-XX:+UseStackBanging,JVM会分配一块足够大的Shadow Page执行,如果RSP(栈顶指针)超出Shadow Page区则抛出StackOverflowError。
这里所说的栈异常处理的逻辑在JVM的信号处理函数中(后续有专门章节描述)
C++各种变量可见性、生命周期
变量类型 | 何处定义 | 修饰属性 | 多执行单元共享or独享 | 生命周期 | 可见性 |
全局变量 | 在函数外 | auto | 共享 | 整个程序 | 全局可见 |
全局线程变量 | 在函数外 | auto | 独享 | 整个程序 | 全局可见 |
全局静态变量 | 在函数外 | static | 共享 | 整个程序 | 同一.c文件可见 |
全局静态线程变量 | 在函数外 | static | 独享 | 整个程序 | 同一.c文件可见 |
局部变量 | 在函数内 | auto | 独享 | 只在函数开始执行到结束这段时间 | 函数内 |
静态局部变量 | 在函数内 | static | 共享 | 整个程序 | 函数内可见 |
静态局部线程变量 | 在函数内 | static | 独享 | 整个程序 | 函数内可见 |
参考
/proc/pid/stat字段说明_/proc/id/stat-CSDN博客
链接、装载与库 --- 运行库 | Technology Blog (markrepo.github.io)
使用IDEA远程Debug调试(详细) - 蚂蚁小哥 - 博客园 (cnblogs.com)
JVM.dll装载过程与源代码分析_jvm.dll源码-CSDN博客
Hotspot 内存管理之CodeCache 源码解析_could not reserve enough space for code cache-CSDN博客
C++多态虚函数表详解(多重继承、多继承情况)_一个类有几个虚函数表-CSDN博客
Hotspot Java对象创建和TLAB源码解析_threadlocalallocbuffer::clear_before_allocation-CSDN博客
标签:初始化,java,thread,create,vm,VM,init,线程,os From: https://blog.csdn.net/capaabbcc/article/details/142427951