您的位置:首页 > 其它

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实例都是相同的。---------------- (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装载失败,抛出异常。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: