这篇文章接着上午记录下。
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){ // staticInstanceOop *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。
【文章转自荷兰服务器 http://www.558idc.com/helan.html 欢迎留下您的宝贵建议】