classloader机制研究(3) --- 类型安全
2012-05-25 00:21
218 查看
转载,请注明出处!
本文实际是对《Dynamic class loading in the Java Virtual machine》第四部分的翻译。第四部分的题目是"Maintaining Type-safe Linkage",翻译成中文就是“链接时保证类型安全”。按照我的理解,用户自定义class loader以及class loader委托机制是造成类型不安全的原因。这部分就是在解释:用户自定义class loader和委托机制如何造成了类型不安全,以及使用何种方法才能保证类型安全。
为了让大家理解这句话,举一个例子。如下的代码。
如果同一个class loader在不同时刻装载的同名类得到的是不同的class实例,那么类C中出现两次的类X就是两个不同类型的class实例。即,方法g中f(new X())与方法f的形参f(X x)是两个不同的类型。上面的代码相当于
如果允许方法g正常运行,那么java就不是类型安全的。
好了问题来了,条件1是恒成立的吗?答案是否定的。因为Java平台允许用户自定义ClassLoader对象,所以用户完全可以破坏条件1。如下代码,
这个用户自定义ClassLoader在不同时刻,就从不同的目录下装载类。因此,在不同时刻使用同一个classloader对象装载同名类得到的肯定是不同的class实例,违背了条件1。为了确保用户自定义ClassLoader也满足条件1,Java虚拟机使用了两个方法:
将class文件中的类名与loadClass的实参进行比较。如果两者相同,则正常结束loadClass。如果两者不同,那么抛出异常。
将loadClass返回的class实例,缓存到java虚拟机中。下次装载同名的类时,先在内部缓存池中查找。如果找到,则不进行实际的装载动作,直接将缓存池中的class实例返回给loadClass的调用者。如果未找到,再进行实际的装载动作。这一点需要额外做些解释。这里所说的查找、装载全部都是在defineClass中进行的。
如果classloader L1的方法L1.loadClass将类cl装载进虚拟机,那么L1就是cl的initiating classloader。
如果classloader L2的方法L2.defineClass将类cl装载进虚拟机,那么L2就是cl的defining classloader。
在编译时刻类型只取决于类名,在运行时刻类型取决于classloader实例和类名。使用符号<C, Ld>Li表示类型。其中C是类名,Li是类C的initiating classloader, Ld是类C的defining classloader。如果不关心defining classloader,那么使用符号<C>Li来表示类型。如果不关心initiating classloader,那么使用符号<C, Ld>来表示类型。
如果classloader L1委托classloader L2装载类C,那么<C>L1 == <C>L2等式成立。
先看如下的代码,
因为classloader L1将类C装载进虚拟机,所以L1是类C的defining classloader。<C, L1>引用的其余类型都由类C的defining classloader负责装载。因此,Spoofed以及Delegated类型的装载是由L1启动的。由代码可以看出,Spoofed是由L1完成装载,Delegated的装载却被委托给L2了。因为Delegated的defining classloader是L2,所以Delgated引用的类型由L2负责装载。即Delegated.g返回的Spoofed类是由L2装载的。问题出现了,Delegated.g返回的是<Spoofed,
L2>,x的类型却是<Spoofed, L1>。这是两个不同的类型。如果允许方法f正常运行,那么Java就不是类型安全的。
这个问题实际就是由于L1与L2分别创建了一个命名空间,这两个命名空间是不同的。这种行为是具有隐患的。如果允许这种行为,会造成什么结果呢?接着上面的例子,假如<Spoofed L1>与<Spoofed L2>的定义如下,
正常情况下,用户应该使用Delegated.g实际返回的类型,即使用<Spoofed, L2>类型。因此,期望Delegated.g的调用者不可以直接访问私有成员变量secret_value,forged_pointer。但实际上Delegated.g的返回值被转换为了<Spoofed, L1>。也就可以直接访问secret_value了,甚至可以私自更改forged_pointer的类型。
在将class实例存入java虚拟机内部缓存池之前,判断class实例是否满足约束集合中的所有约束条件。如果满足,那么将class实例存入java虚拟机的内部缓存池。如果不满足,那么类装载失败。
在将新约束条件加入约束集合之前,判断缓存池中的class实例是否都满足这个约束条件。如果都满足,那么可以将约束条件加入约束集合。如果不是都满足,那么类装载失败。
现在看一看如何创建约束条件。有3种情况,
如果<C, L1>引用了另外一个类<D, L2>的成员变量T fieldName,那么将约束条件<T>L1 == <T>L2加入约束集合。
如果<C, L1>引用了另外一个类<D, L2>的方法T0 methodName(T1, ..., Tn),那么将约束条件<T0>L1 == <T0>L2, <T1>L1 == <T1>L2, ..., <Tn>L1 == <Tn>L2加入约束集合。
如果子类<sub, L2>改写了父类<super, L1>的方法T0 methodName(T1, ..., Tn),那么将约束条件<T0>L1 == <T0>L2, <T1>L1 == <T1>L2, ..., <Tn>L1 == <Tn>L2加入约束集合。
现在来看看使用这种方法如何解决上面遇到的问题。
因为方法f引用了Deleaged.g,所以在装载<C, L1>时向约束集合中添加了约束条件<Spoofed>L1 == <Spoofed>L2。在执行方法g时,虚拟机动态装载<Spoofed, L2>。在将<Spoofed, L2>L2存入内部缓存池之前,判断<Spoofed>L2与<Spoofed>L1不同。因此,<Spoofed, L2>L2装载失败,抛出异常。
本文实际是对《Dynamic class loading in the Java Virtual machine》第四部分的翻译。第四部分的题目是"Maintaining Type-safe Linkage",翻译成中文就是“链接时保证类型安全”。按照我的理解,用户自定义class loader以及class loader委托机制是造成类型不安全的原因。这部分就是在解释:用户自定义class loader和委托机制如何造成了类型不安全,以及使用何种方法才能保证类型安全。
第一、临时命名空间必须具有一致性。
为了保证类型安全,必须保证在任何时刻,只要使用相同class loader对象装载同名的类,那么得到的class实例都是相同的。---------------- (1)为了让大家理解这句话,举一个例子。如下的代码。
class C{ void f(X x){...} ... void g(){f(new X());} }
如果同一个class loader在不同时刻装载的同名类得到的是不同的class实例,那么类C中出现两次的类X就是两个不同类型的class实例。即,方法g中f(new X())与方法f的形参f(X x)是两个不同的类型。上面的代码相当于
class C{ void f(type1_X x){...} ... void g() { f(new type2_X()); } }
如果允许方法g正常运行,那么java就不是类型安全的。
好了问题来了,条件1是恒成立的吗?答案是否定的。因为Java平台允许用户自定义ClassLoader对象,所以用户完全可以破坏条件1。如下代码,
class MyClassLoader extends ClassLoader{ private static int serialNum = 0; private String directory; ... public synchronized Class loadClass(String name){ .... seriaNum++; String location = directory+"/"+seriaNum.toString+"/"; byte[] data = getClassData(location, name); return defineClass(name, data, 0, data.length()); .... } }
这个用户自定义ClassLoader在不同时刻,就从不同的目录下装载类。因此,在不同时刻使用同一个classloader对象装载同名类得到的肯定是不同的class实例,违背了条件1。为了确保用户自定义ClassLoader也满足条件1,Java虚拟机使用了两个方法:
将class文件中的类名与loadClass的实参进行比较。如果两者相同,则正常结束loadClass。如果两者不同,那么抛出异常。
将loadClass返回的class实例,缓存到java虚拟机中。下次装载同名的类时,先在内部缓存池中查找。如果找到,则不进行实际的装载动作,直接将缓存池中的class实例返回给loadClass的调用者。如果未找到,再进行实际的装载动作。这一点需要额外做些解释。这里所说的查找、装载全部都是在defineClass中进行的。
第二、被委托class loader与委托class loader分别创建了一个命名空间,这两个命名空间必须具有一致性。
在阐述正文之前,先给出如下的定义。如果classloader L1的方法L1.loadClass将类cl装载进虚拟机,那么L1就是cl的initiating classloader。
如果classloader L2的方法L2.defineClass将类cl装载进虚拟机,那么L2就是cl的defining classloader。
在编译时刻类型只取决于类名,在运行时刻类型取决于classloader实例和类名。使用符号<C, Ld>Li表示类型。其中C是类名,Li是类C的initiating classloader, Ld是类C的defining classloader。如果不关心defining classloader,那么使用符号<C>Li来表示类型。如果不关心initiating classloader,那么使用符号<C, Ld>来表示类型。
如果classloader L1委托classloader L2装载类C,那么<C>L1 == <C>L2等式成立。
先看如下的代码,
class <C, L1>{ void f(){ <Spoofed, L1>L1 x = <Delegated, L2>L1.g(); } } class <Delegated, L2>{ <Spoofed, L2>L2 g(){...} }
因为classloader L1将类C装载进虚拟机,所以L1是类C的defining classloader。<C, L1>引用的其余类型都由类C的defining classloader负责装载。因此,Spoofed以及Delegated类型的装载是由L1启动的。由代码可以看出,Spoofed是由L1完成装载,Delegated的装载却被委托给L2了。因为Delegated的defining classloader是L2,所以Delgated引用的类型由L2负责装载。即Delegated.g返回的Spoofed类是由L2装载的。问题出现了,Delegated.g返回的是<Spoofed,
L2>,x的类型却是<Spoofed, L1>。这是两个不同的类型。如果允许方法f正常运行,那么Java就不是类型安全的。
这个问题实际就是由于L1与L2分别创建了一个命名空间,这两个命名空间是不同的。这种行为是具有隐患的。如果允许这种行为,会造成什么结果呢?接着上面的例子,假如<Spoofed L1>与<Spoofed L2>的定义如下,
class <Spoofed, L1>{ public int secret_value; public int[] forged_pointer; } class <Spoofed, L2>{ private int secret_value; private int forged_pointer; }
正常情况下,用户应该使用Delegated.g实际返回的类型,即使用<Spoofed, L2>类型。因此,期望Delegated.g的调用者不可以直接访问私有成员变量secret_value,forged_pointer。但实际上Delegated.g的返回值被转换为了<Spoofed, L1>。也就可以直接访问secret_value了,甚至可以私自更改forged_pointer的类型。
1. 第一种解决方法
避免上述问题的方法其实比较简单。只需在执行方法Delegated.g之前,将<Spoofed, L1>和<Spoofed, L2>都装载进虚拟机。然后判断<Spoofed, L1>与<Spoofed, L2>是否相同。如果相同则允许执行Delegated.g,如果不同则不允许执行Delegated.g方法。但这种方法有一个缺点:类不再是延迟装载、按需装载的了。延迟、按需装载是在真正需要创建类的实例是才装载类。在本例中,是在运行方法Delegated.g时,才装载<Spoofed, L2>。如果采用本小节的解决方法,那么必须在方法执行之前就将这个方法中引用的类型装载进虚拟机。2. 第二种解决方法
为每一个类型<Class, defining cl>创建一个约束集合,每装载一个类型都要判断是否满足约束集合,都要更新这个约束集合。具体步骤如下,在将class实例存入java虚拟机内部缓存池之前,判断class实例是否满足约束集合中的所有约束条件。如果满足,那么将class实例存入java虚拟机的内部缓存池。如果不满足,那么类装载失败。
在将新约束条件加入约束集合之前,判断缓存池中的class实例是否都满足这个约束条件。如果都满足,那么可以将约束条件加入约束集合。如果不是都满足,那么类装载失败。
现在看一看如何创建约束条件。有3种情况,
如果<C, L1>引用了另外一个类<D, L2>的成员变量T fieldName,那么将约束条件<T>L1 == <T>L2加入约束集合。
如果<C, L1>引用了另外一个类<D, L2>的方法T0 methodName(T1, ..., Tn),那么将约束条件<T0>L1 == <T0>L2, <T1>L1 == <T1>L2, ..., <Tn>L1 == <Tn>L2加入约束集合。
如果子类<sub, L2>改写了父类<super, L1>的方法T0 methodName(T1, ..., Tn),那么将约束条件<T0>L1 == <T0>L2, <T1>L1 == <T1>L2, ..., <Tn>L1 == <Tn>L2加入约束集合。
现在来看看使用这种方法如何解决上面遇到的问题。
因为方法f引用了Deleaged.g,所以在装载<C, L1>时向约束集合中添加了约束条件<Spoofed>L1 == <Spoofed>L2。在执行方法g时,虚拟机动态装载<Spoofed, L2>。在将<Spoofed, L2>L2存入内部缓存池之前,判断<Spoofed>L2与<Spoofed>L1不同。因此,<Spoofed, L2>L2装载失败,抛出异常。
相关文章推荐
- 面向智能机器人的通讯安全机制研究与改进
- 【Android安全研究笔记】-Android 应用程序框架安全机制研究及改进
- (转)Hadoop的安全机制研究
- classloader机制研究(2) -- 应用场景
- Silverlight项目笔记7:xml/json数据解析、TreeView、引用类型与数据绑定错误、图片加载、虚拟目录设置、silverlight安全机制引发的问题、WebClient缓存问题
- java类型安全枚举与反射机制相结合的应用 作者:封宇
- Qt信号槽机制的实现(面试的感悟,猜测每一个类保存的一个信号和槽的二维表,实际使用函数指针 元对象 还有类型安全的检查设定等等)
- 关于蓝牙安全机制的研究介绍
- 关于蓝牙安全机制的研究介绍
- Perl数据类型安全研究
- classLoader机制研究(1) -- 概念用法
- 基于HTTPS的安全机制的研究(一)
- SeAndroid 安全机制研究学习心得
- 基于HTTPS的安全机制的研究(二)
- Ajax请求接口加密研究(针对网页前端的接口安全加密机制研究)
- IEEE 802.16安全机制的研究与实现
- Activemq 安全机制以及稳定性研究
- STM32时钟安全机制(CSS)研究及实现
- ClassLoader和双亲委派机制