windows系统使用c++实现一个小型jvm(三)------------jvm的启动细节1

本文主要是介绍windows系统使用c++实现一个小型jvm(三)------------jvm的启动细节1,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  今天上午,主要将昨天下午运行机制中,jvm的launch方法的内容详细讲述一下。 在vm的launch中,有如下方法块:

....
HANDLE cur_handle = (HANDLE)(_beginthreadex(NULL, 0, scapegoat, &p, 0, NULL));this->tid = GetThreadId(cur_handle);		// save to the vm_thread.if (!inited) {		// if this is the main thread which create the first init --> thread[0], then wait.//todo: 阻塞执行 tid线程,tid执行完后才往后执行WaitForSingleObject(cur_handle,INFINITE);
....

  其中第一行代码开辟的这个线程,便是java中的mian线程,也就是java中的主线程。 它接收命令的参数去执行任务。  其中,scapegoat方法 的代码如下:

//线程的任务
unsigned scapegoat (void *pp) {temp *real = (temp *)pp;
//	if (real->cur_thread_obj != nullptr) {		// so the ThreadTable::get_thread_obj may be nullptr.		// I add all thread into Table due to gc should stop all threads.ThreadTable::add_a_thread(GetCurrentThreadId(), real->cur_thread_obj, real->thread);		// the cur_thread_obj is from `java/lang/Thread.start0()`.
//	}if (real->should_be_stop_first) {		// if this thread is a child thread created by `start0`: should stop it first because of gc's race.// it will be hung up at the `global pthread_cond`. and will be wake up by `signal_all_thread()`.wait_cur_thread_and_set_bit(&real->the_first_wait_executed, real->thread);}real->thread->start(*real->arg);return 0;
};

  通过代码不难察觉,它首先将当前的线程加入了jvm的线程表中,进行管理(注意,此时jvm线程表中,实际上管理的线程有两个了,一个是init_thread,它是本地线程的抽象,另一个就是 mian线程,也就是当前加入的这个线程)。  接着,当前线程(mian线程) 将会调用 start()方法,将命令行的参数一并传递。 start的源码如下:

void vm_thread::start(list<Oop *> & arg)
{if (automan_jvm::inited() == false) {assert(method == nullptr);			// if this is the init thread, method will be nullptr. this thread will get `main()` automatically.assert(arg.size() == 0);automan_jvm::inited() = true;			// important!//todo: 这里是执行 main 方法  ,重要vm_thread::init_and_do_main();		// init global variables and execute `main()` function.} else {// [x] if this is not the thread[0], detach itself is okay because no one will pthread_join it.//todo: 这里分离子线程
//        CloseHandle(tid);
//        pthread_detach(pthread_self());assert(this->vm_stack.size() == 0);	// checkassert(arg.size() == 1);				// run() only has one argument `this`.this->vm_stack.push_back(StackFrame(method, nullptr, nullptr, arg, this));this->execute();automan_jvm::num_lock().lock();{automan_jvm::thread_num() --;assert(automan_jvm::thread_num() >= 0);}automan_jvm::num_lock().unlock();}WaitForSingleObject(_all_thread_wait_mutex,INFINITE);this->state = Death;ReleaseMutex(_all_thread_wait_mutex);
}

   它会根据jvm是否初始化而判定当前线程的start是去引导和启动main方法,还是一般的线程。 注意我们在学习java的时候,实例化线程我们需要重写run(){}方法,这个run方法里面写的实际上是线程的任务,而线程的启动,是由一个start0本地方法,即jvm调用的。 调用之后,会来到这里这个代码块。  它将走else下面这个代码逻辑,里面实际上就是执行了run方法。   run方法执行完后,会将当前的线程状态改为 death.   之后这个线程便会在特定的时期被gc给回收掉。

   扯远了,我们还是看看main线程的操作吧。 它将会调用 vm_thread::init_and_do_main()方法。 这个方法就比较长,我将分块展示。

1.初始化Class,用于类的映射

java_lang_class::init();		// must init !!!auto class_klass = BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/Class");java_lang_class::fixup_mirrors();	// only [basic types] + java.lang.Class + java.lang.Object

  首先,调用java_lang_class::init()方法,它的作用是将 使用标识符与 (核心)类进行隐射,代码如下:

void java_lang_class::init() {		// must execute this method before jvm!!!auto & delay_mirrors = get_single_delay_mirrors();// basic types.delay_mirrors.push(L"I");delay_mirrors.push(L"Z");delay_mirrors.push(L"B");delay_mirrors.push(L"C");delay_mirrors.push(L"S");delay_mirrors.push(L"F");delay_mirrors.push(L"J");delay_mirrors.push(L"D");delay_mirrors.push(L"V");	// void...delay_mirrors.push(L"[I");delay_mirrors.push(L"[Z");delay_mirrors.push(L"[B");delay_mirrors.push(L"[C");delay_mirrors.push(L"[S");delay_mirrors.push(L"[F");delay_mirrors.push(L"[J");delay_mirrors.push(L"[D");// set statestate() = Inited;
}

  然后,使用BootstrapClassLoader去加载 Class类,注意注意,我们看下BoostrapClassLoader的源码:

class BootStrapClassLoader : public ClassLoader {
private:JarLister jl;
private:BootStrapClassLoader() {}BootStrapClassLoader(const BootStrapClassLoader &);BootStrapClassLoader& operator= (const BootStrapClassLoader &);~BootStrapClassLoader() {}
public:static BootStrapClassLoader & get_bootstrap() {static BootStrapClassLoader bootstrap;return bootstrap;}	// singletonKlass *loadClass(const wstring & classname, ByteStream * = nullptr, MirrorOop * = nullptr,bool = false, InstanceKlass * = nullptr, ObjArrayOop * = nullptr) override;void print() override;void cleanup() override;
};

   从接口中,我们能够判断两条消息:

      1.get_bootstrap调用会得到一个单例对象; 

      2. BootstrapClassLoader有一个成员变量JarLister,首次调用时,会触发它的构造方法,我们去看看它的构造方法:

2.初始化BootstrapClassloader以及加载Class:

//todo: 修改 rjd 为windows 的路径
JarLister::JarLister() : rjd(L"")
{pwd = utf8_to_wstring(getProgramDir());rjd = RtJarDirectory(pwd);wstring rtjar_folder;
#if (defined (__APPLE__))rtjar_folder = utf8_to_wstring(pt.get<std::string>("path.mac"));
#elif (defined (__linux__))rtjar_folder = utf8_to_wstring(pt.get<std::string>("path.linux"));
#else//todo: 这里配置 windows的 rt路径rtjar_folder = utf8_to_wstring("C:\\Program Files\\Java\\jdk1.8.0_161\\jre\\lib\\");
#endifrtjar_pos =L"\""+ rtjar_folder + L"rt.jar"+L"\"";// copy lib/currency.data to ./lib/currency.data ......wstringstream ss;int status = system(wstring_to_utf8(ss.str()).c_str());if (status == -1) {  	// http://blog.csdn.net/cheyo/article/details/6595955 [shell 命令是否执行成功的判定]std::cerr << "system error!" << endl;}bool success = this->getjarlist(rtjar_pos);if (!success)	exit(-1);ifstream f(wstring_to_utf8(this->rtlist), std::ios_base::in);std::string s;while(!f.eof()) {f >> s;		// 这里有一个细节。因为最后一行仅仅有个回车,所以会读入空,也就是 s 还是原来的 s,即最后一个名字被读入了两遍。使用其他的方法对效率不好,因此在 add_file 中解决了。如果检测到有,忽略。if (!Filter::filt(utf8_to_wstring(s))) {this->rjd.add_file(StringSplitter(utf8_to_wstring(s)));}}
}

   通过代码可以分析,它做了这样的事情: 

        1. 或许到当前环境的 rt.jar,这个文件时jvm的核心jar包,我配置的是我本机环境的rt.jar,它的版本号是: jdk_1.8_161。同时,因为原来这里使用了配置的方式,但是需要以来boost,我就给替换了,直接手写死的。  

        2.调用 getjartlist方法,该方法马上详述。

        3.调用rjd的add_file方法。 也将会详述。

   下面是getjarlist方法:

/*===---------------- JarLister --------------------*/
bool JarLister::getjarlist(const wstring & rtjar_pos) const
{wstringstream cmd;cmd << L"jar tf " << rtjar_pos << L" > " << this->rtlist;int status =  system(wstring_to_utf8(cmd.str()).c_str());if (status == -1) {exit(-1);}// TODO: judge whether mkdir is exist?if (0==access(wstring_to_utf8(uncompressed_dir).c_str(),F_OK)) {	// 如果存在return true;}cmd.str(L"");cmd << L"mkdir " << uncompressed_dir;status = system(wstring_to_utf8(cmd.str()).c_str());if (status == -1) {exit(-1);}cmd.str(L"");std::wcout << "unzipping rt.jar from: [" << rtjar_pos << "] ... please wait.\n";cmd << L"unzip " << rtjar_pos << L" -d " << uncompressed_dir ;status = system(wstring_to_utf8(cmd.str()).c_str());if (status == -1) {  	// http://blog.csdn.net/cheyo/article/details/6595955 [shell 命令是否执行成功的判定]std::cerr << "system error!" << endl;exit(-1);} else {if (status) {if (0 ==status) {std::wcout << "unzipping succeed.\n";return true;}else {std::cerr << "Your rt.jar file is not right!" << endl;}} else {std::cerr << "other fault reasons!" << endl;}}return false;
}

    它做了这样的事情:

           1.通过jar tf 将rt.jar保存的所有类 保存至某一特定文件中;

           2.将rt.jar解压至某一个特定文件夹中;(注意,这个文件夹将会作为是否解压的标准,在同一进程中,最多只可能被加压一次。 我当前版本加压出来有 2999个类,如果解压信息输出到控制台的话,还是要费点时间。但是我想在发行版中,一个环境下的jvm,应该只被解压一次)。

   接着是rtjarDirectory的addfile()方法:

void RtJarDirectory::add_file(StringSplitter && ss)
{if (ss.counter() == 0) {		// 仅仅在第一次的时候做检查,看文件到底存不存在if (this->find_file(std::move(ss)) == true) return;else ss.counter() = 0;}const wstring& target = ss.result()[ss.counter()];if (ss.counter() == ss.result().size() - 1) {	// next will be the target, add.subdir->insert(make_shared<RtJarDirectory>(target));} else {	// dir.auto next_dir = findFolderInThis(target);ss.counter() += 1;if (next_dir != nullptr) {(*next_dir).add_file(std::move(ss));	// delegate to the next level dir.} else {	// no next_dir, we should create.// this level's `subdir` can't be nullptr :)subdir->insert(make_shared<RtJarDirectory>(target));next_dir = findFolderInThis(target);assert(next_dir != nullptr);(*next_dir).add_file(std::move(ss));}}
}

   它会将 保存的所有的类,通过根据全限定名称(包名+类名)的形式,进行分割,最终将 类名(去除了包名)的名称 保存进一个智能指针。  这个结构颇为复杂,我没太看懂,它的定义是这样的:

 shared_ptr<set<shared_ptr<RtJarDirectory>,shared_RtJarDirectory_compare>> subdir; //sub directory

  我暂且先理解为它保存了所有的类名称在内存中吧,注意到我获取的类的条目有一万多:

  BootstrapClassLoader初始化完成后,就该去loadClass了,以加载Class为例:

// add lock simplyLockGuard lg(system_classmap_lock);assert(jl.find_file(L"java/lang/Object.class")==1);wstring target = classname + L".class";if (jl.find_file(target)) {if (system_classmap.find(target) != system_classmap.end()) {	// has been loadedreturn system_classmap[target];} else {	// load// parse a ClassFile (load)ifstream f(wstring_to_utf8(jl.get_sun_dir() + L"/" + target).c_str(), std::ios::binary);if(!f.is_open()) {std::wcerr << "wrong! --- at BootStrapClassLoader::loadClass" << std::endl;exit(-1);}
#ifdef DEBUGsync_wcout{} << "===----------------- begin parsing (" << target << ") 's ClassFile in BootstrapClassLoader..." << std::endl;
#endifClassFile *cf = new ClassFile;ClassFile_Pool::put(cf);f >> *cf;
#ifdef DEBUGsync_wcout{} << "===----------------- parsing (" << target << ") 's ClassFile end." << std::endl;
#endif// convert to a MetaClass (link)InstanceKlass *newklass = new InstanceKlass(cf, nullptr);system_classmap.insert(make_pair(target, newklass));
#ifdef KLASS_DEBUGBootStrapClassLoader::get_bootstrap().print();MyClassLoader::get_loader().print();
#endifreturn newklass;}//todo:equals to starts with}
.........

   上面为loadClass代码片段,可以看到第二行,它首先判断了java.lang.Object.class,通过前面的那个智能指针,显然Object是在里面的。(但是,此时Object并未加载。)这也印证了我前天文章中所说的,Object的唯一性是必须要首先保证的。 同时,加载成功时,会首先 实例一个ClassFile放入 类池,这个类对象保存的是  字节码二进制流!!! 接着,实例化一个  实例类对象,将该类对象保存进入  system_classmap。 

3.设置Object,以及基本类型的单例映像:

  该步骤是通过  Class完成的。 

java_lang_class::fixup_mirrors();	// only [basic types] + java.lang.Class + java.lang.Object

  该方法将  8个基本类型,和void 以Mirror实例的形式放入了一个缓存。

......
switch (name[0]) {case L'I':case L'Z':case L'B':case L'C':case L'S':case L'F':case L'J':case L'D':case L'V':{	// include `void`.// insert into.MirrorOop *basic_type_mirror = ((MirrorKlass *)klass)->new_mirror(nullptr, nullptr);basic_type_mirror->set_extra(name);			// set the name `I`, `J` if it's a primitve type.get_single_basic_type_mirrors().insert(make_pair(name, basic_type_mirror));break;}default:{assert(false);}}
......

4.加载String及Thread:

 // load String.classauto string_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/String"));// 1. create a [half-completed] Thread obj, using the ThreadGroup obj.(for currentThread(), this must be create first!!)auto thread_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/Thread"));
InstanceOop *init_thread = thread_klass->new_instance();BytecodeEngine::initial_client(thread_klass, *this);		// first <clinit>!// inject!!//todo: 注意这里的 threadid 的来源,可能要修改init_thread->set_field_value(THREAD L":eetop:J", new LongOop((uint64_t)GetCurrentThreadId()));//todo: 这里的线程优先级还没有绑定到 thread句柄上, 通过 setThreadPriorityinit_thread->set_field_value(THREAD L":priority:I", new IntOop(5));//todo: 这里通过 openthread 根据当前线程的id 获取到线程句柄  注意,当前线程的句柄不能关闭ThreadTable::add_a_thread(GetCurrentThreadId(), init_thread, this);

   加载完Thread之后,会生成一个Thread的实例对象。之后会进行类的初始化,注意这是jvm中第一次类的初始化,它会一直向上初始化直到Object。

   设置相关属性后,放入jvm的线程表中。 注意了,此时有在线程表中,就有了两个个线程了。 但是注意这个线程跟第二个线程的id号是一致的,同时,它并不是真正意义上的线程,只是一个抽象。  在线程表的插入中有如下代码:

 if (get_thread_table().insert(make_pair(tid, make_tuple(get_thread_table().size(), _thread, t))).second == false) {	// 如果原先已经插入了的话,那么就复用原先的 thread_no.number = std::get<0>(get_thread_table()[tid]);}

     也就是说,the_whole_world的线程表中的线程数仍然是两个。 

5.加载并实例化ThreadGroup:

 // 2. create a [System] ThreadGroup obj.auto threadgroup_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/ThreadGroup"));InstanceOop *init_threadgroup = threadgroup_klass->new_instance();BytecodeEngine::initial_client(threadgroup_klass, *this);		// first <clinit>!{std::list<Oop *> list;list.push_back(init_threadgroup);	// $0 = this// execute method: java/lang/ThreadGroup.<init>:()V --> private Method!!Method *target_method = threadgroup_klass->get_this_class_method(L"<init>:()V");assert(target_method != nullptr);this->add_frame_and_execute(target_method, list);}
// 3. INCOMPLETELY create a [Main] ThreadGroup obj.InstanceOop *main_threadgroup = threadgroup_klass->new_instance();{init_thread->set_field_value(THREAD L":group:Ljava/lang/ThreadGroup;", main_threadgroup);}assert(this->vm_stack.size() == 0);BytecodeEngine::initial_client(((InstanceKlass *)class_klass), *this);((InstanceKlass *)class_klass)->set_static_field_value(L"useCaches:Z", new IntOop(false));

    注意,这里除了 类初始化之外,还初始化了一个 ThreadGroup对象。 实例化的threadGroup为主线程组。 

6.加载并初始化System:

// 3. load System classauto system_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/System"));system_klass->set_state(Klass::KlassState::Initializing);
//		BytecodeEngine::initial_clinit(system_klass, *this);auto InputStream_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/io/InputStream"));BytecodeEngine::initial_client(InputStream_klass, *this);auto PrintStream_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/io/PrintStream"));BytecodeEngine::initial_client(PrintStream_klass, *this);auto SecurityManager_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/SecurityManager"));BytecodeEngine::initial_client(SecurityManager_klass, *this);

    System用于设置相关的属性,并且完成 当前程序的标准输入流,输出流,错误流的绑定。 windows上,每个程序的标准输入输出和错误流就是控制台。 

7.加载并实例化Perf 和 LauncherHelper:

  auto Perf_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"sun/misc/Perf"));Perf_klass->set_state(Klass::KlassState::Initializing);				// ban Perf.auto PerfCounter_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"sun/misc/PerfCounter"));PerfCounter_klass->set_state(Klass::KlassState::Initializing);		// ban PerfCounter.auto launcher_helper_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"sun/launcher/LauncherHelper"));BytecodeEngine::initial_client(launcher_helper_klass, *this);Method *load_main_method = launcher_helper_klass->get_this_class_method(L"checkAndLoadMain:(ZILjava/lang/String;)Ljava/lang/Class;");

  Perf作用暂时不清楚,LauncherHelper会用于引导 Launcher然后使用 AppClassLoader去加载用户类。

8.加载动态调用的类:

 // load some useful klass...{auto methodtype_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/invoke/MethodType"));BytecodeEngine::initial_client(methodtype_klass, *this);auto methodhandle_klass = ((InstanceKlass *)BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/invoke/MethodHandle"));BytecodeEngine::initial_client(methodhandle_klass, *this);}

9.执行main:

launch流程结束。


 没想到内容还是有些多,有点招架不住啊。   中间细节还有: system的初始化,以及java的双亲类加载机制,并没有写。  下一篇吧,还涉及到文件描述符啥啥啥的。

  苦胆都要给我干出来了,加油! 必须吃透。

这篇关于windows系统使用c++实现一个小型jvm(三)------------jvm的启动细节1的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/835343

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境