同样,一个Class对象必须知道自己的超类、超级接口。因此,Class对象会引用自己的超类和超级接口的Class对象。这种引用一定是实例引用。(实际上,超类、超级接口的引用也存储在常量池中,但为了区分依赖类的引用,将它特殊表述一下。)
因此,我暂且得到两条结论。
结论一:持有一个Class对象的引用,则任何该Class对象直接或间接依赖的所有类(如果被加载了),都不可能被卸载。同样,只要有一个类直接或间接依赖某个类,则该类不可能被卸载。
结论二:持有一个Class对象的引用,则该类的所有超类、超接口都不能被卸载。同样,如果任何一个类(或接口)存在一个没有被卸载的子类(或实现类、子接口),则该类(接口)不可能被卸载。
由此可见,对于一个软件而言,其所有class文件可以看做一个从包含main方法的根节点出发,在一张依赖图中搜索所有可达节点,所有可达节点构成的集合。而这个集合中的绝大部分类,要么全部保留在内存中,要么全部卸载。
对于一个模块而言,如果该模块之间的类彼此都存在依赖,则该模块必须整个地被卸载。因此,如果一个应用程序想要利用一个模块卸载另外一个模块。必要条件就是,执行卸载操作的模块必须零依赖被卸载的模块。
零依赖的含义就是,被依赖的模块是完全透明的,是不可见的。
根据上面的结论,卸载必须是以模块为单位。因此,所谓卸载一个强耦合的类是完全不可能的。因此,我可以放弃说“卸载类”,而应该说“卸载模块”。
那么,卸载一个模块的充分条件是什么呢?
回到前面的那张引用关系图,让我们来看看那些其他要素可能引用Class对象。
对于任何一个Java对象而言,它都一定是某个类的实例,因此,它能直接或间接的引用到该类的Class对象。这一推论的理由有三,其一,JVM在将对象进行向下转型、instanceof判定的时候,一定要知道对象的类型,倘若对象无法引用到类的Class对象,这个过程何以进行呢?其二,子类可重写父类的方法,运行时的多态令虚拟机能正确调用到对象方法(防止子类调用到父类被覆盖的方法),如果不知对象类型,何以实现?其三,Object类有一个native方法getClass()可以直接获取对象的类Class对象。
类加载器在加载了一个类之后,当第二次接受到该类的类名之后,加载器会直接返回该类的对象,而不是重复加载该类。虚拟机绝不允许同一个命名空间中的一个类被加载了两次,因此类加载器必须缓存所有它加载的类的Class对象,以供返回。
此外,ClassLoader也好,Class对象也好,这些本身能被变量所引用。如果某些可达的变量(局部变量、成员变量、静态变量)引用了它们,自然也会让整个模块变得可达而无法被卸载。
此外,某些特殊的类加载器永远是可达的,它们是Bootstrap、Extendsion、System。因此,我可以得出一条结论。
结论三:Java的标准类库一旦加载,永远不能卸载。
结论四:CLASS_PATH中的类(系统加载器加载的类),一旦加载,永远不能被卸载。
因此,一个模块中的所有类必须使用同一个ClassLoader实例加载。而且这个ClassLoader实例不能是Extendsion和System。这个ClassLoader实例的控制权必须掌握在操作卸载的模块那里。随时释放该实例的引用,使ClassLoader变得不可达。
此外,该模块任意类的实例都必须是不可达的,存在任何一个可达的该模块类的实例,模块都无法释放。
至此,我得到了卸载模块的充分条件:模块的全部类由一个可控的ClassLoader加载;释放该模块的定义类加载器引用;释放该模块所有类的实例引用;释放该模块的Class对象引用。
还有,模块的定义类加载器、类实例、Class对象,这些东西,只要没有引用掌握在其他模块那里,也没有掌握在栈内存中,就一定可以卸载模块。特别的,模块自己引用了这些东西,尤其是模块的静态变量引用了这些东西,那是完全不影响卸载的。
有一种观点认为,垃圾回收判断的根节点包括所有类的静态变量,这是错误的。实际上如果一个模块变得不可达了,它的所有的类的静态变量也会变得不可达,并会被回收释放。
转自:
http://taozeyu.com/software/2014/03/15/jvm-class-loader-2.html