声明
我是个菜鸟,不会写整个加载机制的介绍,其实大部分相关的中文博客都是互相转载,我这里只是说一下自己的学习过程中的疑惑,和如何解决的方法,是为了有相同疑惑的人可以少走点路.
疑惑
- 从类加载器开始说起,双亲委派机制大家都知道,但我一般很想知道为什么,为什么使用这个机制,各种博客大同小异,都是说为了避免重复加载,很多说到这里就没了,这完全是误人子弟,怎么避免重复加载了,这明显就像小学生写作文,光说这个东西怎么怎么好,但就是不说好在哪里.
- 继续向下,看到了介绍加载器内部的代码,类加载器先从缓存中寻找,找不到,再将加载任务递交给父加载器等等.这里就有一个问题,这个缓存是整个JVM共享的吗,如果是的话,那如何实现的避免重复加载,既然整个缓存是共享的,那我用哪一个加载器不是都一样吗?
解惑
- 首先,要确定两个类(Class对象)相同的的一个必要条件,就是这两个类必须都是由同一个类加载器加载进虚拟机的,之所以如此规定,是为来保证类本身在虚拟机中的唯一性;否则即使两个类来自同一个字节码文件,也不算一个类
以下引自<深入理解Java虚拟机>
-
类加载器
- 启动类加载器(Bootstrap ClassLoader),C++实现,是虚拟机的一部分,这个类负责将存放在<JAVA_HOME>\lib目录中的,或者被参数
-Xbootclasspath
指定的路径的,并且被虚拟机识别的(按名字识别,名字不符合,放在lib下也没用),Java程序无法调用这个启动类加载器 - 扩展类加载器 : Java语言实现,继承
java.lang.ClassLoader
,负责<JAVA_HOME>\lib\ext目录下的,或者被java.ext.dirs变量所指定的路径下的所有类库,程序可以直接调用这个 - 应用程序类加载器,使用
ClassLoader.getSystemClassLoader()
方法获得,一般称为系统类加载器,负责加载用户类路径上的指定的类库,如果没有自定义的类加载器,默认是这个;
- 启动类加载器(Bootstrap ClassLoader),C++实现,是虚拟机的一部分,这个类负责将存放在<JAVA_HOME>\lib目录中的,或者被参数
-
双亲委派机制模型要求除了启动类加载器以外,所有类加载器都应当有自己的父类加载器(组合实现,不是继承),工作过程是这样的,如果一个类加载器收到类加载请求,自己不会去加载这个类,而是交给父类加载器加载,直到启动类加载器,如果父类加载器在自己的搜索范围没有找到所需要的类,子加载器自己尝试加载;
-
这样的机制也就避免了重复加载
-
一些实验
我先是自己定义了一个Integer,当我在同包或放在同一类文件下使用时,发现可以加载,是通过系统类加载器加载的,而java的java.lang.Integer没有被启动类加载器加载,这虽然困惑了我一会,但是很快就明白了,判定类加载是类的权限定名,不是单个类的名字.
那么问题来了,如果我创建一个权限定名和java.lang.Integer一样的类,会怎样呢?此时,我创建java.lang这个包在我的classpath下,然后调用,此时你会发现调用的并不是你的类,而是java的类.
这才是双亲委派的意义所在,并不是简单避免重复加载,更重要的是防止更改系统本身的类,防止像骇客一般的行为,而我们正常在自己的包中创建一个相同类名的类,是可以调用的.
还有一个问题,就是网上的一道类加载的面试题,能不能自己创建一个java.lang.xx的类,按照我们上面的说法,这是不行的,但有很多答案说,虽然明面上不行,但是可以通过实现一个自定义类加载器来创建这个类.这就是我说的错误的根源.
从代码上将,看一下ClassLoad中定义类的命名检测方法:源代码
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
看这一行
if ((name != null) && name.startsWith("java.")) {
你能加载以java开头的方法吗?
可能有人会说,我可以重写这个方法,但是对不起,这是私有方法,其次调用这个方法的方法defineClass
是一个final方法,是不允许重写的.
也就是说,我们是不能写自己的java.lang.xx方法的