您的位置:首页 > 其它

关于ClassLoader的原理

2017-12-12 11:21 211 查看

1. Class文件的加载过程

Java虚拟机类加载过程是把Class类文件加载到内存,并对Class文件中的数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的java类型的过程。

在加载阶段,java虚拟机需要完成以下3件事:
a.通过一个类的全限定名来获取定义此类的二进制字节流。

b.将定义类的二进制字节流所代表的静态存储结构转换为方法区的运行时数据结构。

c.在java堆中生成一个代表该类的java.lang.Class对象,作为方法区数据的访问入口。

而类的加载过程是通过类加载器完成的。

2. 加载器加载类的机制探索

在Java中,任意一个类都需要由加载它的类加载器和这个类本身一同确定其在java虚拟机中的唯一性,即比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提之下才有意义,否则,即使这两个类来源于同一个Class类文件,只要加载它的类加载器不相同,那么这两个类必定不相等(这里的相等包括代表类的Class对象的equals()方法、isAssignableFrom()方法、isInstance()方法和instanceof关键字的结果)。

看下面的例子:

要验证的类:

public class Friend {
public void say(){
System.out.println("nice to meet u");
}
}
自定义ClassLoader
事先把class拷贝到D:/java-web/WorkSpace/classes目录下面了

public class MyClassLoader extends ClassLoader{

private String pathString = "D:/java-web/WorkSpace/classes";

public MyClassLoader(String pathString) {
this.pathString = pathString;
}

public MyClassLoader(ClassLoader parent, String pathString) {
super(parent);
this.pathString = pathString;
}

@Override
public Class<?> findClass(String name){
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);

}

private byte[] loadClassData(String name){
try {
name = name.replace(".", "//");
FileInputStream is = new FileInputStream
(new File(pathString + name + ".class"));
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b;
while((b = is.read()) != -1){
baos.write(b);
}
return baos.toByteArray();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}

return null;
}
}
验证程序

public class ClassLoaderTest {
public static void main(String[] args) throws Exception{
//情形1
MyClassLoader myClassLoader = new MyClassLoader("D:\\java-web\\WorkSpace\\classes\\");
//情形2
//MyClassLoader myClassLoader = new MyClassLoader(null,"D:\\java-web\\WorkSpace\\classes\\");

Object obj = myClassLoader.loadClass("com.lynash.test.Friend").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.lynash.test.Friend);

}
}

结论

情形1输出:
class com.lynash.test.Friend
true
情形2输出:
class com.lynash.test.Friend
false


3.ClassLoader的加载机制



(1).BootStrap ClassLoader:称为启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等,

负责加载存放在%JAVA_HOME%\lib目录中的,或者通被-Xbootclasspath参数所指定的路径中的,并且被java虚拟机识别的(仅按照文件名识别,如rt.jar,名字不符合的类库,即使放在指定路径中也不会被加载)类库到虚拟机的内存中,启动类加载器无法被java程序直接引用。

(2).Extension ClassLoader:扩展类加载器,由sun.misc.Launcher$ExtClassLoader实现,负责加载%JAVA_HOME%\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。

(3).Application ClassLoader:应用程序类加载器,由sun.misc.Launcher$AppClassLoader实现,负责加载用户类路径classpath上所指定的类库,是类加载器ClassLoader中的getSystemClassLoader()方法的返回值,开发者可以直接使用应用程序类加载器,如果程序中没有自定义过类加载器,该加载器就是程序中默认的类加载器。

注意: 除了Java默认提供的三个ClassLoader之外,用户还可以根据需要定义自已的ClassLoader,而这些自定义的ClassLoader都必须继承自java.lang.ClassLoader类,也包括Java提供的另外二个ClassLoader(Extension ClassLoader和App ClassLoader)在内,但是Bootstrap ClassLoader不继承自ClassLoader,因为它不是一个普通的Java类,底层由C++编写,已嵌入到了JVM内核当中,当JVM启动后,Bootstrap
ClassLoader也随着启动,负责加载完核心类库后,并构造Extension ClassLoader和App ClassLoader类加载器。

这里需要注意的是上述三个JDK提供的类加载器虽然是父子类加载器关系,但是没有使用继承,而是使用了组合关系。

从JDK1.2开始,java虚拟机规范推荐开发者使用双亲委派模式(ParentsDelegation Model)进行类加载,其加载过程如下:

(1).如果一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器去完成。

(2).每一层的类加载器都把类加载请求委派给父类加载器,直到所有的类加载请求都应该传递给顶层的启动类加载器。

(3).如果顶层的启动类加载器无法完成加载请求,子类加载器尝试去加载,如果连最初发起类加载请求的类加载器也无法完成加载请求时,将会抛出ClassNotFoundException,而不再调用其

子类加载器去进行类加载。

双亲委派 模式的类加载机制的优点是java类它的类加载器一起具备了一种带优先级的层次关系,越是基础的类,越是被上层的类加载器进行加载,保证了java程序的稳定运行。

所以呢, 这是java classloader的委派机制搞的鬼. 加载是有优先级的, 当应用path下面有这个class, 就已经被父ClassLoader给加载了.

上面通过参数null,实际默认的父类加载器变成 Bootstrap ClassLoader .但是它不会加载class path下的类. 所以可以实现切换到自己的classloader加载类.






                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: