考察对类加载的理解(答案篇)

| Comments

独立进程篇

首先需要知道类加载器是怎么回事? 在Java里边,类加载器就是用来加载类的,然后才是执行代码。
Java里边默认有启动类加载器(boot),扩展类加载器(ext),和应用类加载器(app)。
其中boot就是用来加载最开始的虚拟机和最基本的java类,ext是用来加载一些扩展类,默认是在jre/lib/ext目录下的。
最后一个才是你真正会用到的。cp参数就是用来指定应用类加载器找类的地方,java.ext.dirs就是用来指定 扩展类加载器找类的地方。 这三种类加载器是有层级关系的,类似于继承关系(父子关系)。

另外一个需要知道的概念是,类加载器在找类的时候,默认是采用双亲委派机制的。
例如应用类加载器加载类的时候,会先叫ext去加载,ext就会叫boot去加载,只有加载不到才尝试自己去加载。
这种做法是有个重要的因素,就是为了安全性考虑。

最后需要知道的是,TestServlet.class.getClassLoader()获取到的时候,加载TestServlet类所使用的实际的类加载器。

回到上面的问题。

第一个很简单,指定了应用类加载器加载的路径,所以main方法能找到,TestServlet也能找到,config.properties也能被应用类加载器找到。 启动是正常的。

第二个问题,TestServlet是被扩展类加载器加载的,所以通过它来找config.properties会找不到(在应用类加载器中才能被加载到)。

第三个问题,调整目录后,这个时候扩展类加载器的加载路径上也有config.properties,所以启动也会正常。

现在调整了代码,换成了Thread.currentThread().getContextClassLoader()的实现。
这个是有一点不一样的,上下文类加载器默认就是应用类加载器(如果通过代码进行修改的话)。 上下文类加载器还是一个很有用的技术, 可以用在JDBC这种SPI(Service Provider Interface)接口与实现分离的技术上,有兴趣可以去找找资料。

在这种情况下,后面的三个命令都是能够正常的,因为config.properties能够被正常识别的。

再说一点,用eclipse可以运行的程序,用命令行不一定可以,这点必须要认准最后的启动参数,通过这个来确认。 我们这边写独立进程的时候,贪方便喜欢用java.ext.dirs这个虚拟机参数,但这个有时候会有奇怪的问题。 大家要注意区分这个参数和cp参数的区别,这样就分析有思路,找问题很happy。

Web应用服务器篇

像tomcat这种应用服务器,本身也是一个java程序,但它可以把我们放上去的各个web应用隔离开来。 你没法调用其他web应用中的类,看上去好像是完全不相干的。这种技巧就充分利用了自定义类加载器的功能。 像tomcat会对每个应用单独定义一个类加载器(继承应用类加载器),并且修改双亲委派机制。 而是采用先从应用中的lib目录、classes目录尝试加载,没找到才到上面去找。(像was这种就跟复杂了,加载顺序也是可选的)

所以通常我们会有共享库的概念,在tomcat中对应的就是tomcat_home/lib这个目录(老版本的话还有更多目录可选)。 把一些第三方库放到这里,可以减少加载类的数量,从而减少内存占用。

这里要说明的是,类可以被不同的类加载器加载,虽然是在同一个jvm里边,但是是被当成不同的类(唯一标识是类加载器+类名)。

现在回到问题。虽然一开始就有人告诉我们,servlet是属于单例运行的。但是在这里有点小变化。

第一个问题,这个应该最常规的做法了,appa和appb是不相干的,所以访问appb,输出的是”1 1”,因为两个类是通过不同的类加载器加载的(就是说不一样的类),肯定生成的servlet实例是不一样的。

第二个问题,这种是共享库的做法,所以实际上他们使用的是同一个类,但是对于不同的app,用的是不同的servlet实例。 所以会出现静态变量有影响,当实例变量是独立的。所以最后会输出”3 1”。所以有维护静态变量的话,使用共享库是有不一样的。

第三个问题,这种其实在生产中也很常见,上新程序的时候就可能变成这样了。其实这个跟第一种情况是一样的。 不过,在was上加载顺序是可选的,所以情况也可能变成第二种情况。

后话

这里讲解的只是皮毛,有兴趣的童鞋,可以google更多资料和书籍,加以研究。

Comments