8.1.19示例:卸载无法触及的greeter类
动态装载的类型变为无法触及而要被虚拟机卸载时的例子如下面的程序:
因为引用的类(这里是HowDoYouDo )是被GreeterClassLoader对象装载的。这两个类都是Java API的一部分,不管怎样都会最终被启动类装载器装载,因为loadClass ( )首先会把请求委派给 它的双亲。
记住在GreeterClassLoader的loadClass ()方法试图自行在基础目录(这里是greeters目录) 中搜索所请求的类型之前,它会调用它的双亲——系统类装载器。系统类装载器也会先委派给 它的双亲,它的双亲也会再委派给它自己的双亲,这样不断重复。最终findSystemClass ()被 调用,委派给了启动类装载器,委派链到了终点。因为启动类装载器(通过findSystemClass ()) 能够装载 java.lang.System 和 java.io.PrinlStream , loadClass ()方法简单地返回从 findSystemClass ()返回的Class实例。这些类不会被标记为已由GreeterClassLoader对象定义, 而是被标记为已由启动类装载器定义。如果要解析任何从java.lang.System或者java.io.PrintStream发起的引用,虚拟机不会调用GreeterClassLoader对象的loadClass ()方法,也不是 系统类装载器,它会直接使用启动类装载器。
在Surprise的greet ()方法返回后,有两个类型被标记为已由GreeterClassLoader对象定义: 类Surprise和类HowDoYouDo。这两个类型在虚拟机中位于由GreeterClassLoader对象定义的类型列表中。
Surprise的greet ()方法一返回,Surprise和HowDoYouDo的Class实例就可以被程序所触及 了。垃圾收集器不会回收这些Class实例占据的空间,因为程序代码能够访问并且使用它们。图 8-11是关于这两个Class实例的可触及性的图形化描述。
Surprise的Class实例可以通过两条途径触及。首先,GreetAndForget的main ()方法中的局 部变量c可以直接触及它。其次,它可以通过局部变量o和greeter触及,它们指向的是同一个 Surprise对象。通过Surprise对象,虚拟机可以访问Surprise对象的类型数据,类型数据中包含指向Surprise类对象的引用。第三种方法是通过GreetAndForget的main ()方法的gel局部变量i访问c 这个局部变量指向GreelerClassLoader对象,而后者包含了一个HashTable对象,其中保存了一个 指向Surprise的类实例的引用。(自我感悟:即class和该class的定义类装载器是互相关联的)
HowDoYouDo的类实例可以通过两条途径触及。它的第一条途径也是触及Surprise的途径中 的一条:GreetAndForget的main ()方法中的gel局部变童指向GreeterClassLoader对象,后者包 含了一个HashTable对象的引用。而HashTable对象包含了一个指向HowDoYouDo的Class实例的 引用。触及HowDoYouDo的类实例的第二条途径是通过Surprise的常量池。当虚拟机解析完从 Surprise的常量池指向HowDoYouDo的符号引用后,它用一个直接引用替换符号引用。这个直接 引用指向HowDoYouDo的类型数据,类型数据中包含指向HowDoYouDo的Class实例的引用: 因此,从Surprise的常量池开始,HowDoYouDo的Class实例是可以触及的。但是为什么垃圾 收集器要首先注意从Surprise的常量池发出的直接引用呢?因为Surprise的Class实例是可以引用 的。当垃圾收集器发现它可以触及Surprise的Class实例时,它必须确信从Surprise的常量池中发 出的任何直接引用指向的类型都是可触及的。只要Surprise仍然是活动的,虚拟机就不能卸载任 何Surprise可能使用的类型。
请注意,在上面指出的可以触及Surprise的途径中,没有任何一个涉及到其他类型的常量池。 Surpris在GreetAndForget的常量池中没有作为符号引用出现。GreetAndForget在编译的时候不 知道有Surprise的存在。GreetAndForget程序是在运行时决定装载并连接类Surprise的。因此,类Surprise的Class实例只能够从GreetAndForget的main ()方法的局部变量触及。对Surprise来说 不好的是(最终对HowDoYouDo也是一样),这根维系生命的“稻草”并不是很牢固。 GreetAndForget的main ()方法中的以下4条语句,将会完全改变可触及状况:
这四条语句把可以触及Surprise的Class实例的四个起点全都变成了null。结果就是,这四条 语句执行后,Surprise的Class实例再也不能够被触及了。这些语句也使得HowDoYouDo的Class 实例无法触及。原来使o和grceter变量指向Surprise实例,gel变量指向GreeterClassLoader实例, GreeterClassLoader对象的类变量指向HashTable实例。现在这4个对象都可以被当作垃圾收集了。
当垃圾收集器处理并且释放不再被引用的Surprise和HowDoYouDo的Class实例的时候,它可 以同时释放Surprise和HowDoYouDo在方法区中相关联的类型数据。因为这些类的Class实例是不 可触及的,这些类型自己也是不可触及的了,可以被虚拟机卸载。
请注意,for循环语句再经过两次循环(假设使用上面的命令行),GreetAndForget程序会再 —次装载类Surprise。请记住虚拟机并不会重用第一次循环中装载的Surprise的类型数据。在第 一遍循环的终点,那个类型数据已经被授权卸载了。但是就算Surprise的Class实例在第一遍循环 的终点还没有变成不再被引用,第三遍循环也并不会重用第一遍循环使用的类型数据。
每一次循环,GreetAndForget的main ()方法都创建一个新的GreeterCIassLoader对象。因 此,每-个被GreetAndForget装载的greeter都是被一个不同的用户自定义类装载器装载的。比如, 如果使用那个列举了5次Hello欢迎者的命令行来调用GreetAndForget程序,程序会创建类 GreeterClassLoader的5个实例。Hello欢迎者会被5个不同的用户自定义类装载器装载5次。方法区会保存Hello的类型数据的5份拷贝。栈中会保存代表Hello类的5个Class实例--
毎一个装载了Hello的命名空间都有一个。当其中的一个Hello的Class实例变成不再被引用的时候,只有与这个特定的Class实例相关联的Hello类型数据会被准许卸载。