1.简介:
类加载器负责将.class文件(可能在磁盘上,也可能在网络上)加载到内存中并为之生成对应的Java.lang.Class对象。尽管在Java开发中无须过分关心类的加载机制,但是所有的编程人员都应该了解其工作机制,明白如何才能让其更好的满足我们的需要。类加载器负责加载所有的类,一旦一个类被载入JVM中,同一个类就不会被再次载入了。
JVM中,有一个类加载子系统,它包括了四种类加载器:
Bootstrap ClassLoader:根类加载器,
Extension ClassLoader:扩展类加载器,
System ClassLoader:系统类加载器和类加载器。
根类加载器比较特殊,它并不是Java.lang.ClassLoader的子类,而是由JVM自身类实现的,
扩展类加载器负责加载JRE的扩展目录(%JAVA_HOME%/jre/lib/ext或者由java.ext.dirs系统属性指定的目录)中JAR包的类,通过这种方式,就可以为java扩展核心类以外的新功能,只要把自己开发的类打包成jar文件,放入JAVA_HOME/jre/lib/ext路径即可。
系统类加载器,负载在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器都以类加载器作为父加载器。
自定义类加载器通过继承java.lang.ClassLoader类的方式实现自己的类加载器。
2.类加载机制
JVM的类加载机制主要有以下三种:
(1)全盘负责,就是当一个类加载器负责加载某个class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显式使用另外一个类加载器来载入。
(2)父类委托,先让父类加载器加载该class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
(3)缓存机制,它会保证所有加载过的Class都会缓存,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区中。这就是为什么修改了Class后需要重新启动JVM,程序所做的修改才会生效的原因。
注:类加载器之间的父子关系并不是类继承上的父子关系,这里的父子关系是类加载器实例之间的关系。
创建自己的类加载器,只需要扩展Java.lang.ClassLoader类,然后覆盖它的findClass(String name)方法即可,该方法根据参数指定的类的名字,返回对应的Class对象的引用。
3.自定义类加载器
FileSystemClassLoader.java
package com.www.test;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
/**
* 自定义文件系统类加载
* @author weiwenwen
*/
public class FileSystemClassLoader extends ClassLoader{
private String rootDir;
public FileSystemClassLoader(String rootDir){
this.rootDir=rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException{
Class<?> c=findLoadedClass(name);
//如果已经加载这个类,直接返回加载好的类。如果没有,加载新的类
if(c!=null){
return c;
}else{
ClassLoader parent=this.getParent();
try {
c=parent.loadClass(name);//委派给父类加载
} catch (Exception e) {
}
if(c!=null){
return c;
}else{
byte[] classData=getClassData(name);//返回字节数组
if(classData==null){
throw new ClassNotFoundException();
}else{
c=defineClass(name, classData, 0, classData.length);
}
}
}
return c;
}
private byte[] getClassData(String classname){
String path=rootDir + "/" + classname.replace('.', '/')+".class";
//将流中的数据转换为字节数组
InputStream is=null;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
is = new FileInputStream(path);
byte[] buffer =new byte[1024];
int temp=0;
while((temp=is.read(buffer)) != -1){
baos.write(buffer,0,temp);
}
return baos.toByteArray();
} catch (Exception e) {
return null;
} finally{
try{
if(is !=null){
is.close();
}
} catch (IOException e){
e.printStackTrace();
}
try{
if(baos !=null){
baos.close();
}
} catch (IOException e){
e.printStackTrace();
}
}
}
}
Demo.java
package com.www.test;
public class Demo {
public static void main(String[] args) throws Exception{
FileSystemClassLoader loader=new FileSystemClassLoader("D:/weiwenwen/testClassWholeProcess/src/com/www/test");
FileSystemClassLoader loader2=new FileSystemClassLoader("D:/weiwenwen/testClassWholeProcess/src/com/www/test");
Class<?> c = loader.loadClass("com.www.test.HelloWorld");
Class<?> c2 = loader.loadClass("com.www.test.HelloWorld");
Class<?> c3 = loader2.loadClass("com.www.test.HelloWorld");
Class<?> c4 = loader2.loadClass("java.lang.String");
System.out.println(c.hashCode());
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());//同一个类被不同的加载器加载,JVM认为也是不相同的类
System.out.println(c4.hashCode());
System.out.println(c4.getClassLoader()); //引导类加载器
System.out.println(c3.getClassLoader()); //自定义的类加载器
}
}
运行结果:
1829164700
1829164700
1930164689
2018699554
null
com.www.test.FileSystemClassLoader@631816e9
sun.misc.Launcher$AppClassLoader@73d16e93
4.从上面的程序中可以看到
(1)同一个类被相同的加载器加载被认为是相同的类,被不同的加载器加载,认为是不同的类。就像一个项目中有这个类,另一个项目中还有这个类,会被不同的加载器加载。
(2)System,String这些核心类库属于根类加载器,根类加载器并没有继承ClassLoader抽象类,返回null