接上一篇---- 一个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();
{ /* 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;
}
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;
}
----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