【010】【JVM——类加载器】



JVM——类加载器

类与类加载器

对于任意一个类,都需要由加载它的类加载器和这个类本身一向确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。这里所指的“相等”,包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定等情况。

Java虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),它C++语言实现,是虚拟机自身的一部分:另一种就是所有其他的类加载器,这些类加载器都出Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader

从发开人员的角度来看,类加载器有下面3种。

(一)、启动类加载器 (BootstrapClassLoader):存放在<JAVA_HOM/lib>目录中的,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中(一般是java.lang下的类,和java.io下的类)。启动类加载器无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,直接使用null代替。

(二)、扩展类加载器(ExtensionClassloader):由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加器。

(三)、应用程序类加载器(ApplicationClassLoader)这个类加载器由sun.misc.Launcher$AppClassLoader来实现.由于这个类加载器是ClassLoader中的getSystemC lassLoader()方法的返回值,所以一般也称它为系统类加载器.负责加载用户类路径经( ClassPath )上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,就是程序中默认的类加载器。

类加载的器双亲委派模型如下图所示

《【010】【JVM——类加载器】》

双亲委派模型

类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。除了顶层的启动类加载器外,其余的类加载器都有父类加载器.

工作过程是:如果一个类加载器收到了类加载的请求,先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父加器无法加载这个类时,子载加载器才自己去加载。

破坏双亲委派模型

第一次“被破坏”发生在双亲委派模型出现之前。亲委派模型在JDK 1.2之后才被引入,类加载器和抽象类Java. lang. ClassLoader则在JDK 1 .0时代就已经存在,对已经存在的用户自定义类加载器的实现代码, Java设计者们引入双亲委派模型时做出了一些妥协。

第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的类加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被用户代码词用的API。但是基础类有时又要调用用户代码,如JNDI。此时就引入了线程上下文类加器。

第三次“被破坏”是由于用户对程序动态性的追求而导致的。代码热替换(HotSwap)、模块热部署(Hot Deployment)等。

Tomcat类加载器

Tomcat使用的是典型的双亲委派模型的类加载模式。其类加载架构如下图所示:

《【010】【JVM——类加载器】》

Tomcat目录结构中,有三组目录(”/common/*”/server/*”/shared/*”)可以存放Java类库,另外还可以加上Web应用程序自身的目录“/WEB- INF/*”,一共四组。

(一)、放置在/common目录中:类库可被Tomcat和所有的Web应用程序共同使用。这些类库由CommonClassLoader类加载器进行加载。

(二)、放置在/server目录中:类库可被Tomcat使用,对所有的Web应用程序都不可见。这些类库由CatalinaClassLoader类加载器进行加载。

(三)、放置在/shared目录中:类库可被所有的Web应用程序共同使用,但对Tomcat自己不可见。这些类库由SharedClassLoader类加载器进行加载。

(四)、放置在/WebApp/WEB- INF目录中:类库仅仅可以被此Web应用程序使用,对Tomcat和其他Web应用程序都不可见。这些类库由WebappClassLoader类加载器进行加载。

注意:对于每一个JSP文件,都对应一个JSP类加载器,由这个JSP类加载器去加载JSP文件生成的对应的class类文件。如果JSP文件被修改了,原先的那个类加载器被丢弃,使用一个新的JSP类加载器来进行加载。

对于Tomcat6及以后的版本,只有指定了tomcat/conf/catalina.properties配置文件的server.loadershare.loader项后才会真正建立CatalinaClassLoaderSharedClassLoader的实例,否则会用到这两个类加载器的地方都会用CommonClassLoader的实例来代替,默认情况server.loadershare.loader没有设置值。

OSGi类加载器

OSGi框架实现了一个优雅、完整和动态的组件模型。应用程序(称为bundle)无需重新引导可以被远程安装、启动、升级和卸载(其中Java包/类的管理被详细定义)。API中还定义了运行远程下载管理政策的生命周期管理。服务注册允许bundles去检测新服务和取消的服务,然后相应配合。

OSGi中的每个模块(称为Bundle)与普通的Java类库区别并不太大,两者一般都以JAR格式进行封装,并且内部存储的都是Java PackageClass。但是一个Bundle可以声明它所依赖的Java Package(通过Import-Package描述),也可以声明它允许导出发布的Java Package(通过Export-Package描述)。在OSGi里面,Bundle之间的依赖关系从传统的上层模块依赖底层模块转变为平级模块之间的依赖,而且类库的可见性能得到了非常精确的控制,一个模块里只有被Export过的Package才可能被外界访问,其他的PackageClass将会被隐藏起来。

OSGi诱人的特点,要归功于它灵活的类加载器架构。OSGiBundle类加载器之间只有规则,没有固定的委派关系。假设存在BundleABundleBBundleC三个模块,并且这三个Bundle定义的依赖关系为

(一)、Bundle A:声明发布了packageA,依赖了java.*的包;

(二)、BundleB声明依赖了packageApackageC,同时也依赖了java.*的包;

(三)、BundleC声明发布了packageC,依赖了packageA

它们的类加载器及父类加载器之间的关系:

《【010】【JVM——类加载器】》

OSGi类载器的加载规则:

(一)、以java.*开头的类,委派给父类加载器加载。

(二)、否则,委派列表名单内的类,委派给父类加载器加载。

(三)、否则,Import列表中的类,委派给Export这个类的Bundle的类加载器加载。

(四)、否则,查找当前BundleClasspath,使用自己的类加载器加载。

(五)、否则,查找是否在自己的Fragment Bundle中,如果是则委派给Fragment Bundle的类加载器加载。

(六)、否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器加载。

(七)、否则,类查找失败。

缺点:OSGi在提供强大功能的同时,也引入了额外的复杂度,带来了线程死锁和内存泄漏的风险。

点赞