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

  这篇文章接着上午记录下。 

1.标准输入输出流是怎么来的?

   在写java程序的时候,我们经常输出控制台信息,调用的如下代码: System.out.Println();  在这里,我将解释这个的由来。

   jvm在初始化时,必须先加载FileDescriptor,FileDescriptor有三个静态成员: 

  它们会调用本地FileDescriptor的SetI方法:

void JVM_FD_Set(list<Oop *>& _stack){
    //todo: 这里是初始化FileDescriptors时,会将标准输入输出,以及错误流 进行与FileOutputStream绑定
    IntOop *fd = (IntOop *)_stack.front();	_stack.pop_front();
    HANDLE ret;
    if(fd->value==0){//标准输入流
        ret= GetStdHandle(STD_INPUT_HANDLE);
    }else if(fd->value==1){//标准输出流
        ret= GetStdHandle(STD_OUTPUT_HANDLE);
    }else{//标准错误流
        ret= GetStdHandle(STD_ERROR_HANDLE);
    }
    long addr = HandleToLong(ret);//ret为指针类型,该指针类型指向long值,*为取出该值
    _stack.push_back(new LongOop(addr));
}

  windows中,每个程序的标准输入输出流和错误流,为当前程序的控制台。  这里实际上就是控制台程序的句柄给设置到相应的对象中。  之后再初始化System的时候,会有如下的代码片段: 

   而实际上这个setIn0,setOut0,setErr0,就是将输入输出流句柄,给设置到 System.in,或者System.out,System.error属性上去,其代码如下:

void JVM_SetOut0(list<Oop *> & _stack){		// static
    InstanceOop *printstream = (InstanceOop *)_stack.front();	_stack.pop_front();
    auto system = BootStrapClassLoader::get_bootstrap().loadClass(L"java/lang/System");
    assert(system != nullptr);
    ((InstanceKlass *)system)->set_static_field_value(L"out:Ljava/io/PrintStream;", printstream);
}

  另外,从这个流程中,我想起来大概两年前写 看java源码的 流 部分的时候,曾经总结过,说: java中真正更够读写的就两个流,一个是FileInputStream/FileOutputStream ,另一个是ArrayIntputStream/ArrayOutputStream。   知识诚不欺我呀哈哈哈哈。 

2.java的双亲类加载机制: 

   说到这个古老的话题,那可得追溯到我写文章开始。 那一年春节,我决定看看源码,一上来就猛的 想把那个双亲加载机制给搞明白。 事实上,一直没怎么搞明白,唯一的作用是给了自己学习的动力。  但是今天我想借着这个机会,是可以完全搞懂的。

   上午说到,jvm在加载客户类之前,会启动一个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;");
    // new a String.
    wstring ss = automan_jvm::main_class_name();
    InstanceOop *main_klass = (InstanceOop *)java_lang_string::intern(automan_jvm::main_class_name());

    this->vm_stack.push_back(StackFrame(load_main_method, nullptr, nullptr, {new IntOop(true), new IntOop(1), main_klass}, this));
    //todo: 到这里应该是java层面的类加载器开始生效!!!
    MirrorOop *main_class_mirror = (MirrorOop *)this->execute();

     接着去看看 checkAndLoadMain方法:

  之后通过ClassLoader的loadClass方法,通过查找虚拟表,调用Launcher的AppClassLoader的loadClass方法,再调用之前,将会首先进行AppClassClassloader的初始化(这个初始化过程蛮复杂的):

  花了较多的精力去看这块的源码,主要原因基于以下几点: 

     1.windows中由于文件系统路径分隔符的原因,我在调试时就遇到了一个坑,即我明明需要使用 文件系统去加载,但是它却使用了JarLoader去加载。   现在回过头来,知道了其原因: 首先在LauncherHelper中会根据模式选择加载器,其次会判断java.class.path下面的所有配置路径,如果为文件夹,则会匹配为 fileLoader,如果为文件,则会使用默认的loader,而实际上默认的loader,其最终采用的还是Jarloader的方式加载的。(我的问题就出在这!)。

     2.AppClassLoader与ExtClassLoader都继承自URLClasspath,因此必须弄明白URLClasspath是在什么时机初始化的,以及相应的参数都是什么。  关于URLClasspath的作用,网上帖子挺多。 它的几个关键方法: loadClass,findClass,defineClass 可以用于验证双亲委派机制的运行流程。 

  我在调试时,分别给loadClass 和  defineClass添加了锚点, 最终实际上是可以印证双亲委派模型的。  由于当时未截图,因此这里就不再继续操作了。(因为这个过程实在是有些痛苦~,感兴趣的小伙伴可以亲自调试一下)。

  另外强调一点就是,以前没有认识到URLClassLoader在java中的重要性,以及URLClassLoader与URLClasspath的关系,这是一个十分有趣的问题。   

   此外,在AppclassLoader加载类的过程中,它的流将会以匿名内部类的形式给出: 


  就到这吧,原计划写的很详细的,但是真写了太细了吧,速度太慢,自己又是个急性子。  whatever,后面赶紧把gc写了要开始投入新一轮的学习,同时得计划找工作了55555。 

发布了340 篇原创文章 · 获赞 159 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/qq_36285943/article/details/104731785