一个java程序是怎样运行起来的(2)

接上一篇---- 一个java程序是怎样运行起来的(1),当我们执行java Test后,程序会在控制台输出我们想要的结果,那么这个过程中经历了些什么呢?我们知道,java程序要想运行起来,必须建立在java虚拟机上。下面结合hotspot vm(sun实现的java虚拟机)简单看下执行java Test命令后,java虚拟机的处理过程。

 在执行java Test命令后,会去启动java虚拟机来执行程序,在启动java虚拟机之前有一些初始化的工作需要进行

1、创建java虚拟机环境前期的一些准备工作

hotspot vm的入口在 java.c 中,当执行java Test 时,首先进入到java.c的main方法

/*
 * Entry point.省略掉了部分代码
 */
int
main(int argc, char ** argv)
{
    char *jarfile = 0;
    char *classname = 0;
    char *s = 0;
    char *main_class = NULL;
    ............

    CreateExecutionEnvironment(&argc, &argv,
                               jrepath, sizeof(jrepath),
                               jvmpath, sizeof(jvmpath),
                               original_argv);

    printf("Using java runtime at: %s\n", jrepath);

    if (!LoadJavaVM(jvmpath, &ifn)) {
      exit(6);
    }

    /* Set default CLASSPATH */
    if ((s = getenv("CLASSPATH")) == 0) {
        s = ".";
    }
#ifndef JAVA_ARGS
    SetClassPath(s);
#endif

    /*
     *  Parse command line options; if the return value of
     *  ParseArguments is false, the program should exit.
     */
    if (!ParseArguments(&argc, &argv, &jarfile, &classname, &ret, jvmpath)) {
      exit(ret);
    }

    /* Override class path if -jar flag was specified */
    if (jarfile != 0) {
        SetClassPath(jarfile);
    }

    /* set the -Dsun.java.command pseudo property */
    SetJavaCommandLineProp(classname, jarfile, argc, argv);

    /* Set the -Dsun.java.launcher pseudo property */
    SetJavaLauncherProp();

    /* set the -Dsun.java.launcher.* platform properties */
    SetJavaLauncherPlatformProps();

    { /* Create a new thread to create JVM and invoke main method */
      .........

      return ContinueInNewThread(JavaMain, threadStackSize, (void*)&args);
    }
}

1.1、首先是创建执行执行环境,主要的过程在CreateExecutionEnvironment这个函数中,这个函数的实现在java_md.c文件

void
CreateExecutionEnvironment(int *_argcp,char ***_argvp,char jrepath[],jint so_jrepath,char jvmpath[],jint so_jvmpath,char **original_argv) {
  /*
   * 省略部分代码
   */

    char *execname = NULL;
    int original_argc = *_argcp;
    jboolean jvmpathExists;

	..............

  { char *arch = (char *) ARCH; /* like sparc or sparcv9 */
    char *p;

    if (!GetJREPath(jrepath, so_jrepath, arch, JNI_FALSE) ) {
      fprintf(stderr, "Error: could not find Java 2 Runtime Environment.\n");
      exit(2);
    }

    if (!GetJVMPath(jrepath, NULL, jvmpath, so_jvmpath, arch )) {
      fprintf(stderr, "Error: no JVM at `%s'.\n", jvmpath);
      exit(4);
    }
  }
}
这个函数主要干了两件事,一是寻找jre路径,在本人的机器上,这个jre路径是D:\java\jdk1.7.0_79\jre,二是寻找jvm路径,主要是找到jvm.dll的路径

1.2、加载java虚拟机,主要在函数LoadJavaVM中实现:

jboolean LoadJavaVM(const char *jvmpath, InvocationFunctions *ifn)
{
	................
    Dl_info dlinfo;
    void *libjvm;
    libjvm = dlopen(jvmpath, RTLD_NOW + RTLD_GLOBAL);
    ..................
    ifn->CreateJavaVM = (CreateJavaVM_t)
      dlsym(libjvm, "JNI_CreateJavaVM");
    if (ifn->CreateJavaVM == NULL)
        goto error;

    ifn->GetDefaultJavaVMInitArgs = (GetDefaultJavaVMInitArgs_t)
        dlsym(libjvm, "JNI_GetDefaultJavaVMInitArgs");
    if (ifn->GetDefaultJavaVMInitArgs == NULL)
      goto error;

    return JNI_TRUE;

}
首先加载jvm.dll,windows下使用WinApi函数LoadLibrary函数来加载jvm.dll,接下来指定了使用 "JNI_CreateJavaVM" 创建java虚拟机,这个函数在jni.cpp,以及"JNI_GetDefaultJavaVMInitArgs"函数用于获取默认的java虚拟机初始化参数。

1.3、设置jvm运行时需要用到的一些属性

	SetClassPath(xxxx)
 /* set the -Dsun.java.command pseudo property */
    SetJavaCommandLineProp(classname, jarfile, argc, argv);

    /* Set the -Dsun.java.launcher pseudo property */
    SetJavaLauncherProp();

    /* set the -Dsun.java.launcher.* platform properties */
    SetJavaLauncherPlatformProps();

1.4、创建一个新的线程去初始化jvm并执行java程序

{ /* Create a new thread to create JVM and invoke main method */
      struct JavaMainArgs args;

      args.argc = argc;
      args.argv = argv;
      args.jarfile = jarfile;
      args.classname = classname;
      args.ifn = ifn;

      return ContinueInNewThread(JavaMain, threadStackSize, (void*)&args);
    }
该线程指定了执行的函数为JavaMain,这个函数在java.c文件中.

到此,创建jvm前期的一些准备工作已经完成!!!

2、创建java虚拟机

创建java虚拟机的函数入口在java.c中:

int JNICALL JavaMain(void * _args)
{
    ..........
    if (!InitializeJVM(&vm, &env, &ifn)) {
        ReportErrorMessage("Could not create the Java virtual machine.",
                           JNI_TRUE);
        exit(1);
    }
}
static jboolean InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn)
{
    ........
    r = ifn->CreateJavaVM(pvm, (void **)penv, &args);
    JLI_MemFree(options);
    return r == JNI_OK;
}
还记得上面的ifn这个指针吧,ifn->CreateJavaVM(pvm, (void **)penv, &args);实际上是调用JNI_CreateJavaVM去完成虚拟机的初始化工作。

JNI_IMPORT_OR_EXPORT_ jint JNICALL JNI_CreateJavaVM(JavaVM **vm, void **penv, void *args) {
	........................
  result = Threads::create_vm((JavaVMInitArgs*) args, &can_try_again);
  ......................
  return result;
}

java虚拟机的创建是一个比较复杂的过程,细节实现比较多,下面我们来简单看下其中的主要步骤,参见 thread.cpp

1,if (!is_supported_jni_version(args->version)) return JNI_EVERSION;校验当前jvm是否支持该版本

2,ostream_init();初始化输出模块

3,Arguments::process_sun_java_launcher_properties(args);处理java命令行中指定了“-Dsun.java.launcher”属性,

4,os::init();初始化系统信息,如处理器个数,页文件大小等

5,Arguments::init_system_properties();初始化system properties,

// Initialize system properties key and value.
void Arguments::init_system_properties() {
	...........
  _java_ext_dirs = new SystemProperty("java.ext.dirs", NULL,  true);//初始化key--->value:"java.ext.dirs"---->null
  _java_endorsed_dirs = new SystemProperty("java.endorsed.dirs", NULL,  true);//"java.endorsed.dirs"---->null
  _sun_boot_library_path = new SystemProperty("sun.boot.library.path", NULL,  true);//"sun.boot.library.path"---->null
  _java_library_path = new SystemProperty("java.library.path", NULL,  true);//"java.library.path"--->null
  _java_home =  new SystemProperty("java.home", NULL,  true);//"java.home"---->null
  _sun_boot_class_path = new SystemProperty("sun.boot.class.path", NULL,  true);//"sun.boot.class.path"---->null
  _java_class_path = new SystemProperty("java.class.path", "",  true);//"java.class.path"---->""

  // 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();
}
void os::init_system_properties_values() {
  /* sysclasspath, java_home, dll_dir */
  {
	..............
      Arguments::set_java_home(home_path);//设置java_home,即上面的_java_home =  new SystemProperty("java.home", NULL,  true);"java.home"---->环境变量中配置的值
	.........
      Arguments::set_dll_dir(dll_path);//"sun.boot.library.path"----->当前机器的dll路径,在本人机器上为D:\java\jdk1.7.0_79\jre\bin
	  if (!set_boot_path('\\', ';'))//"sun.boot.class.path"---->classpath
          return;
  }
	..............
    Arguments::set_library_path(library_path);//"java.library.path"--->环境变量path的值,以及windows目录,system目录
    ............
    Arguments::set_ext_dirs(buf);//"java.ext.dirs"---->本机上为D:\java\jdk1.7.0_79\jre\lib\ext
	..................
    Arguments::set_endorsed_dirs(buf);//"java.endorsed.dirs"---->本机上为D:\java\jdk1.7.0_79\jre\lib\ext\endorsed
}
6,JDK_Version_init();初始化jdk版本信息

7,Arguments::init_version_specific_system_properties();更新jdk特定system properties

8,jint parse_result = Arguments::parse(args);解析参数

9,jint os_init_2_result = os::init_2();//设置 stack_size,线程优先级等

10,ThreadLocalStorage::init();初始化thread local storage,

11,ostream_init_log();初始化日志输出

12,vm_init_globals();初始化全局的数据结构并在堆上创建system classes

void vm_init_globals() {
  check_ThreadShadow();
  basic_types_init();//初始化基本类型,比如int所能表示的最大值与最小值以及一个int占多少个字节
  eventlog_init();//初始化eventlog
  mutex_init();//初始化一堆的锁,比如gc lock等
  chunkpool_init();//初始化hotspotvm 内存池chunkpool
  perfMemory_init();//初始化jvm性能统计
}
13,创建一个java线程

// Attach the main thread to this os thread
  JavaThread* main_thread = new JavaThread();
  main_thread->set_thread_state(_thread_in_vm);//线程在jvm中执行
  main_thread->record_stack_base_and_size();//记录线程的基址和大小
  main_thread->initialize_thread_local_storage();//初始化TLS
  main_thread->set_active_handles(JNIHandleBlock::allocate_block());//设置jni句柄
  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();
14,ObjectMonitor::Initialize() ;初始化java对象监视器,关键字synchronized就是通过对象监视器实现的

15,MemTracker::bootstrap_multi_thread();//当vm进入多线程模式时,create nmt lock for multi-thread execution

16,jint status = init_globals();//初始化全局模块
      -----management_init();//init java.lang.management API support,vm创建信息,线程信息,运行时信息,类加载信息

      -----bytecodes_init();//初始化jvm指令集

      -----classLoader_init();//初始化classLoader

void ClassLoader::initialize() {
  .........//初始化jvm性能统计的信息
  // lookup zip library entry points
  load_zip_library();
  // initialize search path
  setup_bootstrap_search_path();
  if (LazyBootClassLoader) {
    // set up meta index which makes boot classpath initialization lazier
    setup_meta_index();
  }
}
void ClassLoader::load_zip_library() {
  assert(ZipOpen == NULL, "should not load zip library twice");
  // First make sure native library is loaded
  os::native_java_library();//确保verify.dll,java.dll已加载
  // Load zip library
  char path[JVM_MAXPATHLEN];
  char ebuf[1024];
  os::dll_build_name(path, sizeof(path), Arguments::get_dll_dir(), "zip");
  void* handle = os::dll_load(path, ebuf, sizeof ebuf);//加载zip.dll
  if (handle == NULL) {
    vm_exit_during_initialization("Unable to load ZIP library", path);
  }
  // Lookup zip entry points
  ZipOpen      = CAST_TO_FN_PTR(ZipOpen_t, os::dll_lookup(handle, "ZIP_Open"));
  .................//获取zip.dll中有关函数的入口地址
  
  // Lookup canonicalize entry in libjava.dll
  void *javalib_handle = os::native_java_library();
  CanonicalizeEntry = CAST_TO_FN_PTR(canonicalize_fn_t, os::dll_lookup(javalib_handle, "Canonicalize"));//获取canonicalize入口地址
}
void ClassLoader::setup_bootstrap_search_path() {
  assert(_first_entry == NULL, "should not setup bootstrap class search path twice");
  char* sys_class_path = os::strdup(Arguments::get_sysclasspath());//获取system class path信息,在第5步中进行了初始化_sun_boot_class_path
  if (TraceClassLoading && Verbose) {
    tty->print_cr("[Bootstrap loader class path=%s]", sys_class_path);
  }

  int len = (int)strlen(sys_class_path);
  int end = 0;

  // Iterate over class path entries
  for (int start = 0; start < len; start = end) {
    while (sys_class_path[end] && sys_class_path[end] != os::path_separator()[0]) {//解析路径字符串
      end++;
    }
    char* path = NEW_C_HEAP_ARRAY(char, end-start+1, mtClass);
    strncpy(path, &sys_class_path[start], end-start);
    path[end-start] = '\0';
    update_class_path_entry_list(path, false);//将路径信息添加到ClassPathEntry中,ClassPathEntry是一个链表
    FREE_C_HEAP_ARRAY(char, path, mtClass);
    while (sys_class_path[end] == os::path_separator()[0]) {
      end++;
    }
  }
}
//此步骤后,等于告诉了jvm bootclassloader的搜索路径,后续用来加载这些路径下的class文件

       ----codeCache_init();//初始化代码缓存

       ----VM_Version_init();//初始化vm版本

       ----os_init_globals();//初始化os额外的全局信息,目前从代码上看应该是没有额外的处理

       ----stubRoutines_init1();//初始化stubRoutines,便于在c文件中调用java代码

       ----jint status = universe_init();//java堆初始化

jint universe_init() {//比较复杂
  ........
  GC_locker::lock();  // do not allow gc during bootstrapping
  JavaClasses::compute_hard_coded_offsets();//计算硬编码偏移,如Throwable中各个变量的offset,该方法计算了Throwable Class,java_lang_boxing_object,java_lang_ref_Reference,java_lang_ref_SoftReference Class,java_lang_ClassLoader,java_lang_System,java_lang_StackTraceElement中成员变量的offset.
	...................
  jint status = Universe::initialize_heap();//堆初始化
  if (status != JNI_OK) {
    return status;
  }
  
  // We have a heap so create the methodOop caches before
  // CompactingPermGenGen::initialize_oops() tries to populate them.
  Universe::_finalizer_register_cache = new LatestMethodOopCache();
  Universe::_loader_addClass_cache    = new LatestMethodOopCache();
  Universe::_pd_implies_cache         = new LatestMethodOopCache();
  Universe::_reflect_invoke_cache     = new ActiveMethodOopsCache();

    SymbolTable::create_table();//创建SymbolTable,大小360*1024
    StringTable::create_table();//创建StringTable,大小40
    ClassLoader::create_package_info_table();//创建 PackageHashtable,大小31

  return JNI_OK;
}
jint Universe::initialize_heap() {
	.....................//针对不同的gc收集器使用不同的_collectedHeap,主要有ParallelScavengeHeap,G1CollectedHeap,GenCollectedHeap,
	不同类型的collectedHeap有不同的收集策略,默认使用GenCollectedHeap,策略MarkSweepPolicy
  jint status = Universe::heap()->initialize();//GenCollectedHeap初始化,这个值得细细研究.....
  if (status != JNI_OK) {
    return status;
  }
  ................
  if (UseTLAB) {
    assert(Universe::heap()->supports_tlab_allocation(),
           "Should support thread-local allocation buffers");
    ThreadLocalAllocBuffer::startup_initialization();//初始化TLAB
  }
  return JNI_OK;
}

,     ----interpreter_init();//初始化模板解释器,将所有字节码的目标代码生成函数和参数保存在_template_table,每个字节码、程序和函数的调用都要进行计数,后面细读后在分析,先看个大概

     ----invocationCounter_init();//初始化计数

     ----marksweep_init();//初始化标记整理,用于gc

     ----accessFlags_init();//初始化访问标志,just checking size of flags

     ----templateTable_init();//每个字节码对应有相应的汇编指令,所有字节码的template封装成templateTable,里面包含每个字节码指令的具体机器码映射

     ----SharedRuntime::generate_stubs();//

     ----universe2_init();  // dependent on codeCache_init and stubRoutines_init1
     ----referenceProcessor_init();
     ---jni_handles_init();
     ---vmStructs_init();
     ---vtableStubs_init();
     ---InlineCacheBuffer_init();
     ---compilerOracle_init();
     ---compilationPolicy_init();
     ---compileBroker_init();
     ---VMRegImpl::set_regName();
     ---if (!universe_post_init()) {
        return JNI_ERR;
  }
     ---javaClasses_init();   // must happen after vtable initialization
     ---stubRoutines_init2(); // note: StubRoutines need 2-phase init

17,main_thread->cache_global_variables();//缓存全局变量

18,// Create the VMThread

19,//初始化jdk核心类,如java.lang.String,java.lang.Thread等

..............

在java虚拟机初始化完成后,接下就该执行实际的java程序了

============================我是分割线===================================================

hotspot vm是一个庞大的工程,涉及到的知识点太多。对于刚开始接触不久的我来说,是在是复杂。所以基于本人对jvm有限的理解(更多的可能是自己yy),尝试做一个简单的jvm实现,目前功能非常有限,正在慢慢积极的完善,github上地址:https://github.com/reverence/czvm




















你可能感兴趣的:(java基础)