前言

本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容可能存在疏漏,恳请读者不吝指正。

前情回顾

jdk\src\solaris\bin\java_md_solinux.cJVMInit()函数创建一个新的线程,在该线程执行时会调用jdk\src\share\bin\java.cJavaMain()函数中完成JVM初始化、加载MainClass、启动java应用程序处理。下面我们就跟着源码揭秘JVM创世过程中主线程是如何诞生的,揭开这层神秘面纱,探究这神秘面纱下面的真相。

创世剧本开篇

在 JVM 的“创世”过程中,主线程(Main Thread)的诞生是最具仪式感的环节。它不仅是一个操作系统的线程,更是连接 C++ 宿主环境与 Java 字节码世界的第一座桥梁。

JVM创世伊始(JVM初始化入口)

话不多说上源码jdk\src\share\bin\java.cJavaMain()函数源码如下:


int JNICALL JavaMain(void * _args)
{
    // 省略参数设置相关代码
    JavaMainArgs *args = (JavaMainArgs *)_args;
    InvocationFunctions ifn = args->ifn;

    JavaVM *vm = 0;
    JNIEnv *env = 0;

    /* Initialize the virtual machine */
    start = CounterGet();
    // 完成JVM初始化
    if (!InitializeJVM(&vm, &env, &ifn)) {
        JLI_ReportErrorMessage(JVM_ERROR1);
        exit(1);
    }
    // 省略部分代码

    mainClass = LoadMainClass(env, mode, what);
    // 省略部分代码

    // 通过JNI查找到java程序入口方法main(String[] args)的ID
    mainID = (*env)->GetStaticMethodID(env, mainClass, "main", "([Ljava/lang/String;)V");

    // 省略部分代码

    // 执行java程序入口方法main(String[] args)
    /* Invoke main method. */
    (*env)->CallStaticVoidMethod(env, mainClass, mainID, mainArgs);

    /*
     * The launcher's exit code (in the absence of calls to
     * System.exit) will be non-zero if main threw an exception.
     */
    ret = (*env)->ExceptionOccurred(env) == NULL ? 0 : 1;
    LEAVE();
}

static jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
{
    // 省略部分代码,只保留核心代码
    // 执行libjvm.so中的JNI_CreateJavaVM()函数
    r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
    JLI_MemFree(options);
    return r == JNI_OK;
}

在上述代码中,可以看出JavaMian()函数,首先会执行InitializeJVM()函数进行初始化JVM的逻辑。然后加载和执行Java应用程序的public static void main(String[] args)真正的启动Java应用程序。

hotspot\src\share\vm\prims\jni.cppJNI_CreateJavaVM()函数


_JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
  // 省略部分代码

  jint result = JNI_ERR;
  // 省略部分代码
  bool can_try_again = true;

  result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
  if (result == JNI_OK) {
    JavaThread *thread = JavaThread::current();
    /* thread is thread_in_vm here */
    *vm = (JavaVM *)(&main_vm);
    *(JNIEnv**)penv = thread->jni_environment();

    // 省略部分代码

    // 由于没有使用JVM_ENTRY,需要手动设置对应Java线程的状态。
    // Since this is not a JVM_ENTRY we have to set the thread state manually before leaving.
    ThreadStateTransition::transition_and_fence(thread, _thread_in_vm, _thread_in_native);
  } else {
    // 省略创建失败的处理
  }

  return result;
}

JVM_ENTRY简单说明

1、它的核心作用是为所有从 JNI (Java Native Interface) 调用进入 JVM 内部的 C++ 函数,提供一个统一的“入口”封装。
它做到了以下几点:

  • 规范化:确保所有从本地代码进入JVM的入口行为一致。
  • 安全性:正确管理线程状态,是JVM实现精确垃圾回收和线程同步的前提。
  • 可维护性:将通用的“胶水代码”集中在宏定义中,避免了在每个JVM内部函数中重复编写,使核心业务逻辑更清晰。

2、源码的 hotspot/src/share/vm/runtime/interfaceSupport.hpp 文件中具体如下:

// 定义在 interfaceSupport.hpp 中
#define JVM_ENTRY(result_type, header)                               \
extern "C" {                                                         \
  result_type JNICALL header {                                       \
    JavaThread* thread = JavaThread::thread_from_jni_environment(env); \
    ThreadInVMfromNative __tiv(thread);                              \
    debug_only(VMNativeEntryWrapper __vew;)                          \
    VM_ENTRY_BASE(result_type, header, thread)

// VM_ENTRY_BASE 的进一步定义
#define VM_ENTRY_BASE(result_type, header, thread)                   \
  TRACE_CALL(result_type, header)                                    \
  HandleMarkCleaner __hm(thread);                                    \
  Thread* THREAD = thread;                                           \
  os::verify_stack_alignment();                                      \
  /* 这里是原来 JVM_ENTRY 的函数体 */


宏的核心作用
通过上面的展开代码,可以清晰地看到 JVM_ENTRY 主要完成了以下几项关键工作:

  1. 获取当前线程: JavaThread::thread_from_jni_environment(env) 从传入的 JNIEnv* 中提取出当前线程的 JavaThread 对象。这是所有后续JVM操作的基础。
  2. 线程状态切换: ThreadInVMfromNative __tiv(thread); 这行代码是核心中的核心。它将当前线程的状态从“_thread_in_native”(正在执行本地代码)切换到“_thread_in_vm”(正在执行JVM代码)。这个状态切换对于JVM的安全点(Safepoint)机制至关重要,它标志着该线程进入了“Java虚拟机世界”,可以被JVM的全局操作(如垃圾回收)所识别和管理。
  3. 提供调试与追踪支持: debug_only(VMNativeEntryWrapper __vew;)TRACE_CALL(...) 等部分,用于在开发调试版本中执行额外的检查(如栈帧校验)和记录函数调用路径,帮助开发人员诊断问题。
  4. 设置异常处理上下文: Thread* THREAD = thread; 定义了一个名为 THREAD 的局部变量。这个变量与 TRAPSCHECK 等异常处理宏配合使用,使得JVM内部的函数能够方便地抛出和处理异常。
  5. 管理JNI局部引用: HandleMarkCleaner __hm(thread); 负责管理JNI局部引用的生命周期。它会在当前函数的作用域内创建一个清理点,确保函数返回时不会遗留不再使用的JNI句柄,防止句柄表溢出。
  6. 保证栈对齐: os::verify_stack_alignment(); 确保函数调用时栈帧是按标准对齐的,这是某些操作系统和硬件架构正常运行的必要条件

JVM创建的核心逻辑

通过上面的经历,我们叩开了JVM大门,接下来就继续后面的旅程。
请出今天的主角HotSpot 虚拟机启动的核心“剧本”HotSpot 虚拟机启动的核心“剧本”Threads::create_vm()方法。该方法代码很长将近400行,从无到有地初始化整个 Java 虚拟机环境。

主要功能:

1. 预初始化与参数处理

在任何实际工作开始前,JVM 需要弄清楚自己要干什么:

  • 版本校验:检查 JNI 版本是否受支持。
  • 参数解析 (Arguments::parse):处理你从命令行输入的 -Xmx-cp 等参数。
  • OS 基础初始化:调用 os::init()。这步非常关键,它会确定操作系统的页面大小、内存模型等底层信息。
2. 基础设施搭建
  • TLS 初始化 (ThreadLocalStorage::init):为线程本地存储分配空间, jni_environment_offset 的基础。
  • 全局数据初始化 (vm_init_globals):初始化全局数据结构。
  • Agent 加载:加载 -agentlib 指定的调试或性能分析插件。
3. 主线程的诞生

需要重点关注,代码在这里正式创建了 C++ 层的 JavaThread 对象:

JavaThread* main_thread = new JavaThread();
main_thread->set_thread_state(_thread_in_vm);
main_thread->initialize_thread_local_storage();
main_thread->set_active_handles(JNIHandleBlock::allocate_block());
// Enable guard page *after* os::create_main_thread(), otherwise it would
// crash Linux VM, see notes in os_linux.cpp.
main_thread->create_stack_guard_pages();  // Enable guard page *after* os::create_main_thread(), otherwise it would
// crash Linux VM, see notes in os_linux.cpp.
main_thread->create_stack_guard_pages();
  • 栈保护区 (create_stack_guard_pages):为防止栈溢出,给主线程设置保护页。
  • Handle 管理:分配 JNIHandleBlock,用于管理 JNI 的局部引用。
4. 核心子系统启动
  • 初始化全局模块 (init_globals):启动内存管理(GC)、字节码解释器、代码缓存(CodeCache)等。
  • 创建 VMThread (VMThread::create): 这是 JVM 内部最特殊的一个线程,它负责处理所有的“重量级”任务,如垃圾回收(GC)请求、线程进入安全点(Safepoint)等。
5. Java 世界的“布道”

此时,C++ 环境已经就绪,但 Java 的核心类(如 String, System, Class)还没加载。没有 Java 类,JVM 就无法执行任何字节码。 代码开始手动初始化这些“元老”:

  • 初始化 java.lang.String
  • 初始化 java.lang.System:这是为了让你可以调用 System.out 等。
  • 创建初始线程组:设置 main 线程所属的 ThreadGroup
  • 关联 Java 对象:调用 create_initial_thread,将 C++ 的 main_thread 和 Java 层的 java.lang.Thread 对象关联起来。
6. 后台服务与最终就绪
  • 启动信号调度器 (os::signal_init):负责处理 Ctrl+C 等系统信号。
  • 启动 JIT 编译器 (CompileBroker::compilation_init):让 JVM 具备将字节码编译成机器码的能力。
  • JVMTI 状态切换:将虚拟机状态切入“Live 状态”,此时 JNI 已经完全可用。
  • 设置初始化完成标记 (set_init_completed)

详细代码参见下面
hotspot\src\share\vm\runtime\thread.cpp


jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {

  extern void JDK_Version_init();

  // Check version
  if (!is_supported_jni_version(args->version)) return JNI_EVERSION;

  // Initialize the output stream module
  ostream_init();

  // Process java launcher properties.
  Arguments::process_sun_java_launcher_properties(args);

  // Initialize the os module before using TLS
  os::init();

  // Initialize system properties.
  Arguments::init_system_properties();

  // So that JDK version can be used as a discrimintor when parsing arguments
  JDK_Version_init();

  // Update/Initialize System properties after JDK version number is known
  Arguments::init_version_specific_system_properties();

  // Parse arguments
  jint parse_result = Arguments::parse(args);
  if (parse_result != JNI_OK) return parse_result;

  os::init_before_ergo();

  jint ergo_result = Arguments::apply_ergo();
  if (ergo_result != JNI_OK) return ergo_result;

  if (PauseAtStartup) {
    os::pause();
  }

#ifndef USDT2
  HS_DTRACE_PROBE(hotspot, vm__init__begin);
#else /* USDT2 */
  HOTSPOT_VM_INIT_BEGIN();
#endif /* USDT2 */

  // Record VM creation timing statistics
  TraceVmCreationTime create_vm_timer;
  create_vm_timer.start();

  // Timing (must come after argument parsing)
  TraceTime timer("Create VM", TraceStartupTime);

  // Initialize the os module after parsing the args
  jint os_init_2_result = os::init_2();
  if (os_init_2_result != JNI_OK) return os_init_2_result;

  jint adjust_after_os_result = Arguments::adjust_after_os();
  if (adjust_after_os_result != JNI_OK) return adjust_after_os_result;

  // intialize TLS
  ThreadLocalStorage::init();

  // Initialize output stream logging
  ostream_init_log();

  // Convert -Xrun to -agentlib: if there is no JVM_OnLoad
  // Must be before create_vm_init_agents()
  if (Arguments::init_libraries_at_startup()) {
    convert_vm_init_libraries_to_agents();
  }

  // Launch -agentlib/-agentpath and converted -Xrun agents
  if (Arguments::init_agents_at_startup()) {
    create_vm_init_agents();
  }

  // Initialize Threads state
  _thread_list = NULL;
  _number_of_threads = 0;
  _number_of_non_daemon_threads = 0;

  // Initialize global data structures and create system classes in heap
  vm_init_globals();

  // Attach the main thread to this os thread
  JavaThread* main_thread = new JavaThread();
  main_thread->set_thread_state(_thread_in_vm);
  // must do this before set_active_handles and initialize_thread_local_storage
  // Note: on solaris initialize_thread_local_storage() will (indirectly)
  // change the stack size recorded here to one based on the java thread
  // stacksize. This adjusted size is what is used to figure the placement
  // of the guard pages.
  main_thread->record_stack_base_and_size();
  main_thread->initialize_thread_local_storage();

  main_thread->set_active_handles(JNIHandleBlock::allocate_block());

  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;
  }

  // Enable guard page *after* os::create_main_thread(), otherwise it would
  // crash Linux VM, see notes in os_linux.cpp.
  main_thread->create_stack_guard_pages();

  // Initialize Java-Level synchronization subsystem
  ObjectMonitor::Initialize() ;

  // Initialize global modules
  jint status = init_globals();
  if (status != JNI_OK) {
    delete main_thread;
    *canTryAgain = false; // don't let caller call JNI_CreateJavaVM again
    return status;
  }

  // Should be done after the heap is fully created
  main_thread->cache_global_variables();

  HandleMark hm;

  { MutexLocker mu(Threads_lock);
    Threads::add(main_thread);
  }

  // Any JVMTI raw monitors entered in onload 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();
    Thread* vmthread = VMThread::vm_thread();

    if (!os::create_thread(vmthread, os::vm_thread))
      vm_exit_during_initialization("Cannot create VM thread. Out of system resources.");

    // Wait for the VM thread to become ready, and VMThread::run to initialize
    // Monitors can have spurious returns, must always check another state flag
    {
      MutexLocker ml(Notify_lock);
      os::start_thread(vmthread);
      while (vmthread->active_handles() == NULL) {
        Notify_lock->wait();
      }
    }
  }

  assert (Universe::is_fully_initialized(), "not initialized");
  if (VerifyDuringStartup) {
    // Make sure we're starting with a clean slate.
    VM_Verify verify_op;
    VMThread::execute(&verify_op);
  }

  EXCEPTION_MARK;

  // At this point, the Universe is initialized, but we have not executed
  // any byte code.  Now is a good time (the only time) to dump out the
  // internal state of the JVM for sharing.
  if (DumpSharedSpaces) {
    MetaspaceShared::preload_and_dump(CHECK_0);
    ShouldNotReachHere();
  }

  // Always call even when there are not JVMTI environments yet, since environments
  // may be attached late and JVMTI must track phases of VM execution
  JvmtiExport::enter_start_phase();

  // Notify JVMTI agents that VM has started (JNI is up) - nop if no agents.
  JvmtiExport::post_vm_start();

  {
    TraceTime timer("Initialize java.lang classes", TraceStartupTime);

    if (EagerXrunInit && Arguments::init_libraries_at_startup()) {
      create_vm_init_libraries();
    }

    initialize_class(vmSymbols::java_lang_String(), CHECK_0);

    // Initialize java_lang.System (needed before creating the thread)
    initialize_class(vmSymbols::java_lang_System(), CHECK_0);
    initialize_class(vmSymbols::java_lang_ThreadGroup(), CHECK_0);
    Handle thread_group = create_initial_thread_group(CHECK_0);
    Universe::set_main_thread_group(thread_group());
    initialize_class(vmSymbols::java_lang_Thread(), CHECK_0);
    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,
                                        java_lang_Thread::RUNNABLE);

    // 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);
    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);
  }

  // See        : bugid 4211085.
  // Background : the static initializer of java.lang.Compiler tries to read
  //              property"java.compiler" and read & write property "java.vm.info".
  //              When a security manager is installed through the command line
  //              option "-Djava.security.manager", the above properties are not
  //              readable and the static initializer for java.lang.Compiler fails
  //              resulting in a NoClassDefFoundError.  This can happen in any
  //              user code which calls methods in java.lang.Compiler.
  // Hack :       the hack is to pre-load and initialize this class, so that only
  //              system domains are on the stack when the properties are read.
  //              Currently even the AWT code has calls to methods in java.lang.Compiler.
  //              On the classic VM, java.lang.Compiler is loaded very early to load the JIT.
  // Future Fix : the best fix is to grant everyone permissions to read "java.compiler" and
  //              read and write"java.vm.info" in the default policy file. See bugid 4211383
  //              Once that is done, we should remove this hack.
  initialize_class(vmSymbols::java_lang_Compiler(), CHECK_0);

  // More hackery - the static initializer of java.lang.Compiler adds the string "nojit" to
  // the java.vm.info property if no jit gets loaded through java.lang.Compiler (the hotspot
  // compiler does not get loaded through java.lang.Compiler).  "java -version" with the
  // hotspot vm says "nojit" all the time which is confusing.  So, we reset it here.
  // This should also be taken out as soon as 4211383 gets fixed.
  reset_vm_info_property(CHECK_0);

  quicken_jni_functions();

  // Must be run after init_ft which initializes ft_enabled
  if (TRACE_INITIALIZE() != JNI_OK) {
    vm_exit_during_initialization("Failed to initialize tracing backend");
  }

  // Set flag that basic initialization has completed. Used by exceptions and various
  // debug stuff, that does not work until all basic classes have been initialized.
  set_init_completed();

  Metaspace::post_initialize();

#ifndef USDT2
  HS_DTRACE_PROBE(hotspot, vm__init__end);
#else /* USDT2 */
  HOTSPOT_VM_INIT_END();
#endif /* USDT2 */

  // record VM initialization completion time
#if INCLUDE_MANAGEMENT
  Management::record_vm_init_completed();
#endif // INCLUDE_MANAGEMENT

  // Compute system loader. Note that this has to occur after set_init_completed, since
  // valid exceptions may be thrown in the process.
  // Note that we do not use CHECK_0 here since we are inside an EXCEPTION_MARK and
  // set_init_completed has just been called, causing exceptions not to be shortcut
  // anymore. We call vm_exit_during_initialization directly instead.
  SystemDictionary::compute_java_system_loader(THREAD);
  if (HAS_PENDING_EXCEPTION) {
    vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
  }

#if INCLUDE_ALL_GCS
  // Support for ConcurrentMarkSweep. This should be cleaned up
  // and better encapsulated. The ugly nested if test would go away
  // once things are properly refactored. XXX YSR
  if (UseConcMarkSweepGC || UseG1GC) {
    if (UseConcMarkSweepGC) {
      ConcurrentMarkSweepThread::makeSurrogateLockerThread(THREAD);
    } else {
      ConcurrentMarkThread::makeSurrogateLockerThread(THREAD);
    }
    if (HAS_PENDING_EXCEPTION) {
      vm_exit_during_initialization(Handle(THREAD, PENDING_EXCEPTION));
    }
  }
#endif // INCLUDE_ALL_GCS

  // Always call even when there are not JVMTI environments yet, since environments
  // may be attached late and JVMTI must track phases of VM execution
  JvmtiExport::enter_live_phase();

  // Signal Dispatcher needs to be started before VMInit event is posted
  os::signal_init();

  // Start Attach Listener if +StartAttachListener or it can't be started lazily
  if (!DisableAttachMechanism) {
    AttachListener::vm_start();
    if (StartAttachListener || AttachListener::init_at_startup()) {
      AttachListener::init();
    }
  }

  // Launch -Xrun agents
  // Must be done in the JVMTI live phase so that for backward compatibility the JDWP
  // back-end can launch with -Xdebug -Xrunjdwp.
  if (!EagerXrunInit && Arguments::init_libraries_at_startup()) {
    create_vm_init_libraries();
  }

  // Notify JVMTI agents that VM initialization is complete - nop if no agents.
  JvmtiExport::post_vm_initialized();

  if (TRACE_START() != JNI_OK) {
    vm_exit_during_initialization("Failed to start tracing backend.");
  }

  if (CleanChunkPoolAsync) {
    Chunk::start_chunk_pool_cleaner_task();
  }

  // initialize compiler(s)
#if defined(COMPILER1) || defined(COMPILER2) || defined(SHARK)
  CompileBroker::compilation_init();
#endif

  if (EnableInvokeDynamic) {
    // Pre-initialize some JSR292 core classes to avoid deadlock during class loading.
    // It is done after compilers are initialized, because otherwise compilations of
    // signature polymorphic MH intrinsics can be missed
    // (see SystemDictionary::find_method_handle_intrinsic).
    initialize_class(vmSymbols::java_lang_invoke_MethodHandle(), CHECK_0);
    initialize_class(vmSymbols::java_lang_invoke_MemberName(), CHECK_0);
    initialize_class(vmSymbols::java_lang_invoke_MethodHandleNatives(), CHECK_0);
  }

#if INCLUDE_MANAGEMENT
  Management::initialize(THREAD);
#endif // INCLUDE_MANAGEMENT

  if (HAS_PENDING_EXCEPTION) {
    // management agent fails to start possibly due to
    // configuration problem and is responsible for printing
    // stack trace if appropriate. Simply exit VM.
    vm_exit(1);
  }

  if (Arguments::has_profile())       FlatProfiler::engage(main_thread, true);
  if (MemProfiling)                   MemProfiler::engage();
  StatSampler::engage();
  if (CheckJNICalls)                  JniPeriodicChecker::engage();

  BiasedLocking::init();

#if INCLUDE_RTM_OPT
  RTMLockingCounters::init();
#endif

  if (JDK_Version::current().post_vm_init_hook_enabled()) {
    call_postVMInitHook(THREAD);
    // The Java side of PostVMInitHook.run must deal with all
    // exceptions and provide means of diagnosis.
    if (HAS_PENDING_EXCEPTION) {
      CLEAR_PENDING_EXCEPTION;
    }
  }

  {
      MutexLockerEx ml(PeriodicTask_lock, Mutex::_no_safepoint_check_flag);
      // Make sure the watcher thread can be started by WatcherThread::start()
      // or by dynamic enrollment.
      WatcherThread::make_startable();
      // Start up the WatcherThread if there are any periodic tasks
      // NOTE:  All PeriodicTasks should be registered by now. If they
      //   aren't, late joiners might appear to start slowly (we might
      //   take a while to process their first tick).
      if (PeriodicTask::num_tasks() > 0) {
          WatcherThread::start();
      }
  }

  // Give os specific code one last chance to start
  os::init_3();

  create_vm_timer.end();
#ifdef ASSERT
  _vm_complete = true;
#endif
  return JNI_OK;
}

如何主线程诞生

在 JVM 的“创世”过程中,主线程(Main Thread)的诞生是最具仪式感的环节。它不仅是一个操作系统的线程,更是连接 C++ 宿主环境与 Java 字节码世界的第一座桥梁

Threads::create_vm() 方法代码中,主线程的诞生可以分为五个关键步骤:


1. 躯壳的初步构建 (C++ JavaThread 的分配)

首先,JVM 在 C++ 堆上为“主线程”分配内存并初始化其基础结构。

JavaThread* main_thread = new JavaThread();
main_thread->set_thread_state(_thread_in_vm);
  • 身份登记:此时它还是一个纯粹的 C++ 对象。构造函数会初始化 _jni_environment(JNI 环境指针),让它具备调用 Native 方法的能力。
  • 状态切换:状态被设为 _thread_in_vm。这意味着它现在正在“由于 JVM 内部事务而忙碌”,告诉 GC(垃圾回收器):“我现在正在操作敏感数据,别碰我!”
2. 领土的安全保障 (栈基址与保护页)

主线程必须知道自己的“边界”在哪里,否则一旦发生深度递归导致栈溢出,整个 JVM 都会崩溃。

main_thread->record_stack_base_and_size();
main_thread->create_stack_guard_pages();
  • 边界记录:JVM 向操作系统询问当前线程栈的起始地址和大小。
  • 红区与黄区create_stack_guard_pages 会在栈的最末端设置几页不可访问的内存(Guard Pages)。当程序试图访问这里时,OS 会抛出异常,JVM 捕获该异常并将其转换为 Java 层的 StackOverflowError
3. 解决“先有鸡还是先有蛋” (核心类初始化)

这是一个悖论:创建 Java 层的 Thread 对象需要调用 Java 构造函数,但运行 Java 代码又需要已经存在一个 Thread 环境。

为了打破僵局,JVM 采取了“手动硬编码加载”的方式:

  1. 加载 StringSystem:这是所有 Java 操作的基石。
  2. 加载 ThreadGroup:线程必须有所属的组。
  3. 加载 Thread:通过 initialize_class 强制让 JVM 知道 java.lang.Thread 的结构。
4. 灵魂注入 (C++ 对象与 Java 对象的绑定)

这是主线程真正诞生的瞬间。JVM 会在 Java 堆中手动“捏”出一个 java.lang.Thread 对象,并将它与刚才创建的 C++ JavaThread 对象关联。

Handle thread_group = create_initial_thread_group(CHECK_0);
Universe::set_main_thread_group(thread_group());

oop thread_object = create_initial_thread(thread_group, main_thread, CHECK_0);
main_thread->set_threadObj(thread_object);
  • oop (Ordinary Object Pointer):这就是 Java 堆里那个对象的引用。
  • 双向绑定:C++ 端的 JavaThread 持有 Java 端的 Thread 引用;反之,Java 端的 Thread 对象的 eetop 字段(在旧版本中)或通过偏移量也指向 C++ 的 JavaThread
jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
    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, java_lang_Thread::RUNNABLE);
}

static oop create_initial_thread(Handle thread_group, JavaThread* thread, TRAPS) {
  Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK_NULL);
  instanceKlassHandle klass (THREAD, k);
  instanceHandle thread_oop = klass->allocate_instance_handle(CHECK_NULL);

  java_lang_Thread::set_thread(thread_oop(), thread);
  java_lang_Thread::set_priority(thread_oop(), NormPriority);
  thread->set_threadObj(thread_oop());

  Handle string = java_lang_String::create_from_str("main", CHECK_NULL);

  JavaValue result(T_VOID);
  JavaCalls::call_special(&result, thread_oop,
                                   klass,
                                   vmSymbols::object_initializer_name(),
                                   vmSymbols::threadgroup_string_void_signature(),
                                   thread_group,
                                   string,
                                   CHECK_NULL);
  return thread_oop();
}

jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
    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, java_lang_Thread::RUNNABLE);
}

static oop create_initial_thread(Handle thread_group, JavaThread* thread, TRAPS) {
  Klass* k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_Thread(), true, CHECK_NULL);
  instanceKlassHandle klass (THREAD, k);
  instanceHandle thread_oop = klass->allocate_instance_handle(CHECK_NULL);

  java_lang_Thread::set_thread(thread_oop(), thread);
  java_lang_Thread::set_priority(thread_oop(), NormPriority);
  thread->set_threadObj(thread_oop());

  Handle string = java_lang_String::create_from_str("main", CHECK_NULL);

  JavaValue result(T_VOID);
  JavaCalls::call_special(&result, thread_oop,
                                   klass,
                                   vmSymbols::object_initializer_name(),
                                   vmSymbols::threadgroup_string_void_signature(),
                                   thread_group,
                                   string,
                                   CHECK_NULL);
  return thread_oop();
}

5. 宣告独立 (进入 Runnable 状态)

最后,主线程需要告诉全世界它已经准备好干活了。

java_lang_Thread::set_thread_status(thread_object, java_lang_Thread::RUNNABLE);
  • 状态转换:将 Java 对象的线程状态标记为 RUNNABLE
  • JNI 就绪:调用 quicken_jni_functions(),确保后续所有的 JNI 调用都走最快路径。

总结:主线程的特殊性

主线程与普通的 new Thread().start() 创建的线程最大的区别在于:

  • 它是“被追认”的:普通线程是先有 Java 对象,再由 JVM 创建 OS 线程;主线程是先由 OS 启动(通过启动器),再由 JVM 补齐它的 C++ 结构和 Java 镜像。
  • 它是“特权级”的:它是唯一一个在 Universe(类元数据环境)还没完全建好时就开始活动的线程。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐