clr和jre的运行机制的初步总结
--------------------------------------------------------------------------------
概念比较:
java c#
byte code il(字节码,中间语言)
jvm.dll mscrolib.dll,mscrojit.dll(虚拟机)
jre clr(运行环境)
jdk .net framework(开发框架)
package assembly(类库,程序集)
一、关于类库的版本管理问题
java和c#代码运行要依靠其运行环境(jre,clr)和运行环境带的基础类库(c#称为配件或者程序集assembly),此外还会有一些第三方的类库或者自己开发的类库。如果运行环境版本不一致,或者引用的类库版本不一致都会带来程序不能正常运行。比如一个java程序是在jdk1.2上开发,如果在jre1.4上运行,一般情况下可以向下兼容,但也有例外,有些gui程序在jdk1.4上面运行结果很可能会不同。
jre的版本管理
java的解决办法是每个程序自己携带一套jre。
我的机器上已经被安装了好多套jre和jdk了(jdk包括了同版本的jre,此外还包括有编译器和其它工具),它们分别是:
bea weblogic server 7.0 自带一套 jdk1.3.1_02
我下载了一套最新的jdk1.4.1_02
jbuilder9自带一套jkd1.4.1_02
oracle8.1.7自带一套jre1.1.7
ration rose自带一套jdk1.3
dreamweaver自带一套jdk1.3
6套jre,每套jre都被各自安装到不同的目录,不会互相影响。当在控制台执行java.exe,操作系统寻找jre的方式如下:
先找当前目录下有没有jre
再找父目录下有没有jre
接着在path路径中找jre
注册表hkey_local_machinesoftwarejavasoftjava runtime environment 查看currentversion的键值指向哪个jre
最常用的是在path路径中找jre,一般情况下,自己的程序运行之前都会先在批处理文件里面临时设置path,把自己用的jre放到path路径最前面,所以肯定会运行自己带的jre,不会造成版本混乱。
.net framework的版本管理
.net framework被固定安装在c:winntmicrosoft.netframeworkv版本号目录下,并且在同一台机器只能安装一套,要安装1.1版本的.net framework,就必须先删除1.0的。听说刚发行的.net framework1.1已经对1.0做了很多改进,甚至基础类库的层次也有所变动。看来在旧版本的.net framework开发的程序将来往新版本上面迁移的时候少不了修改程序代码。
jre的基础类库
jre自带的基础类库主要是jrelibt.jar这个文件,包括了java2平台标准版的所有类库。和jre的版本一致。
.net framekwork的核心类库
.net framekwork的核心类库被放置在c:winntassemblygac目录下,按照不同的名称空间放在不同目录中,不像jre打成了一个包。并且可以同时存在不同的版本,例如:
某类库1.0版本 c:winntassemblygac名称1.0名称.dll
某类库1.1版本 c:winntassemblygac名称1.1名称.dll
这样做,虽然很灵活,可以随时把类库更新到最新的状态,但是很容易带来版本管理的复杂度,造成版本不一致。
jre类库的查找方法和版本管理
jre中由classloader负责查找和加载程序引用到的类库,基础类库classloader会到rt.jar中自动加载,其它的类库,classloader在环境变量classpath指定的路径中搜索,按照先来先到的原则,放在classpath前面的类库先被搜到,java程序启动之前建议先把path和classpath环境变量设好,os通过path来找jre,确定基础类库rt.jar的位置,jre的classloader通过classpath找其它类库。但有时候会出现这样的情况,希望替换基础类库中的类库,那么也可以简单的通过-djava.endrosed.path=...参数传递给java.exe,于是classloader会先于基础类库使用java.endrosed.path参数指定路径的类库。因此java的版本管理是非常简单有效的,也许很原始,不过很好用,简单就不容易出错。(所以我很奇怪eric ramond为什么批评java的类库管理机制,他还居然批评java的接口,令人怀疑他对java的了解程度)
.net framework的类库管理机制
.net framework的类库管理机制相当强大和复杂,分为私有类库和共享类库。
私有类库就放在exe程序当前路径下,或其相对路径中,只有当前程序可见。
共享类库需要在gac(global assembly cache)中注册,注册过程比较复杂,首先要用工具生成公开/私有密钥对,然后结合密钥和类库版本号连编,最后使用工具注册到gac中好以后,会被放在"c:winntassemblygac类库的名称空间版本号"目录下,不同的类库版本在注册的时候会按照版本号分开放置:
某类库1.0版本 c:winntassemblygac名称1.0名称.dll
某类库1.1版本 c:winntassemblygac名称1.1名称.dll
也就是可以同时存在一个类库的n个版本,至于在程序中用哪个版本,在程序的配置文件中声明,clr会根据声明来调用相应的版本的类库。我觉得.net实现方法未免太复杂了一些,将所有共享类库都塞到一个系统目录下,并且同一个类库还有n个版本,将来.net第三方开发的类库逐渐丰富起来以后,.net类库的gac也会越来越庞大,会不会也搞得和windows注册表一样难以维护?软件发布到服务器上的时候,类库要再注册一次,服务器会逐渐形成一个庞大的树状的gac,gac里面存放着组件的n个版本。试想经过一段时间之后,c:winntassemblygac目录会越来越庞大,有的组件甚至有n个版本都放在那里,你又不敢随便删除,不知道是不是有程序需要使用,我不明白ms为什么要把这么简单的事情搞到这么复杂?
综上所述,java的版本管理方式简单而有效,c#的版本管理方式功能强大,不过是不是太复杂了?会不会搞成第二个注册表一样的东西?
二、虚拟机启动和加载类库的方式
java的虚拟机启动和加载类库
在console执行java.exe xxx命令以后,如前所述的寻找jre,os找到jre目录,根据java.exe的传递参数,选择加载server版的jvm.dll还是client版的jvm.dll,然后加载jvm.dll,把控制权交给jvm.dll。
接下来,jvm.dll进行初始化,分配内存等等动作,然后在classpath路径中寻找class,找到class以后,寻找class中的程序入口点main函数,然后从main函数执行程序,在执行过程中,使用classloader动态加载一系列引用到的类。当调用到native方法时,jvm.dll告诉os在jrein目录下寻找某某dll文件,调入内存,于是实现了jni调用。
.net的虚拟机的启动推测
我对.net的虚拟机的启动过程还一知半解,自己写了一些例程,并且用内存工具来检测观察,推测.net的运行机制,先来抛砖引玉,请熟悉windows平台编程的朋友指教。.net有3个目录中的文件在执行的时候会被加载
1、c:winntmicrosoft.netframeworkv版本号
该目录下的mscorlib.dll,mscorrsn.dll,mscorwsk.dll,mscorjit.dll是核心dll,大概是运行虚拟机的必要文件,其中mscrolib.dll是入口点。此外,该目录下还有一些.net的system名称空间的il类库,与c:winntassemblygac相应目录下的il类库完全一样,这些是最核心的基础类库。.net的编译器,检查器等等工具软件也在该目录,推测system名称空间的核心类库之所以在这个目录下copy一份是因为作为.net的编译器等工具的私有类库之用。
2、c:winntassemblygac
该目录下放置.net共享类库,如前所述
3、c:winntassemblyativeimages_.net版本号
在该目录下也有一些以system名称空间开头的核心类库,推测是ms为了加快clr的执行效率把核心类库进行本地化,编译为native image的同名dll。可以观察到该目录下的同名dll文件,比gac目录下的同名dll文件体积大,可能是因为link底层dll库的缘故。
某核心类库 c:winntassemblyativeimages_.net版本号名称空间.net版本号_散列码名称.dll
另外值得注意的地方是有两个mscorlib.dll
1、c:winntmicrosoft.netframeworkv版本号mscrolib.dll (1.88mb)
2、c:winntassemblynativeimages1_v版本号mscorlib版本号__散列码mscrolib.dll (3.07mb)
mscrolib.dll (1.88mb)还是一个il码的版本,所以映射了一个native的版本的mscrolib.dll (3.07mb),来加快clr的速度。
当il的exe程序被双击执行时,os loader读入程序,识别出是il,根据il内部的引用定义,加载mscorlib.dll,而mscorlib.dll也是il,内部引用c:winntsystem32mscoree.dll,于是再加载mscoree.dll,然后把控制权交给mscoree.dll,mscoree.dll接着加载mscrorsn.dll,mscrowsk.dll,mscrojit.dll,为了加快mscorlib.dll的调用,加载mscorlib.dll的native image版本,然后由mscorlib.dll接管控制权(不知道这两个mscorlib.dll是如何来上管il,下连native code的?)最后寻找il码程序的入口点main函数,开始执行程序,在执行过程中,使用class loader动态加载一系列引用到的类,在当前路径下,在共享类库的gac中查找等等。
这里和jvm.dll不同的一点是,jvm.dll加载的基础类库和加载其它类库方式完全一样,全部都是字节码的class。而mscrolib.dll加载以system名称开头的核心类库的时候,使用了“不正当竞争手法”。mscrolib.dll从gac中加载共享核心类库之后,又c:winntassemblyativeimages_.net版本号名称空间 目录下加载了核心类库的native版本,这样一来,自然clr运行起来要快多了。特别是图形图像类库全部都有native映射版本,所以clr上运行gui焉能不快?
对比clr和jre的加载过程,比较不同的地方是mscorlib.dll和system核心类库都有一个native image,可能这是clr运行速度比较快的一个主要原因吧。
分析完以后有一个特别明显的感受,java的底层运行机制设计的特别简单,而.net的底层运行机制设计的特别复杂。但是在企业层刚好相反,j2ee设计的特别复杂,而.net却设计的特别简单,真是有意思!
java的底层机制设计虽然简单,但是很健壮,.net设计使得它的clr速度快,类库管理功能强大,但是不是比java更优秀,还要等以后慢慢看了。
注:
我查了一下《.net essential》这本书,上面提到这样的说法。
ms更新了windows各个版本的os loader程序,使得os loader可以识别.net pe格式的exe文件,当执行windows native pe格式的exe文件的时候,os loader按照以往的方式加载系统dll。如果是.net pe格式的exe文件,os loader加载mscorlib.dll,然后把控制权交给mscorlib.dll。
闽公网安备 35060202000074号