前言
java类加载将. class文件加载到内存中,将类的数据结构放在方法区域中,并在堆区域中创建class类的对象(垃圾回收)。 堆栈空间用于存储局部变量和基本数据。 方法结束后清空。
加载类使用类加载器。 加载程序可以是java虚拟机附带的,也可以是用户自定义的。
java附带了虚拟机
根类加载器
由于没有继承ClassLoader,因此调用ClassLoader.getParent ()为空。 扩展类加载器的父类。 加载虚拟机专用的类。
扩展类加载器
加载由java.ext.dirs指定的类,或加载jrelibext目录中的类。 例如Object类。 系统类加载器的父类。
系统类加载器(APP应用程序类加载器)
从classpath或java.class.path中指定的目录加载类。 这是用户定义的类加载器的默认父类加载器。 用户定义的加载器指定父类加载器的方法newclassloader(parent,name )。
父类委托加载
子类加载器在加载过程中,首先尝试加载到父类加载器中。 如果父类加载器失败,则子类加载器将尝试下一次加载。 如果加载失败,将出现ClassNotFound错误。
一个子类加载器只有一个父类加载器。
进入正题
为什么这样的父类会使用委托加载的形式呢?
由于更安全,子类加载器无法加载父类加载器加载的类。 父类加载的类不需要重复加载。 这样可以防止恶意代码冒充java核心库引起风波。
父类加载器与子类加载器之间的关系? (以下简称父类、子类)
父类类似于子类和包装关系,子类命名空间中的所有类都可以加载到父类中,但父类中的类不能加载到子类中。
的加载器如何加载类?
想法:继承ClassLoader,覆盖核心方法findClass,定义专用方法loadClass,将其转换为二进制数据流,然后加载到Class类中。
代码部分
MyClassLoader.java
publicclassmyclassloaderextendsclassloader {
私有字符串路径=' d :\ ";
privatefinalstringfiletype='.class ';
//类加载器名称
私有字符串名称=null;
publicmyclassloader (字符串名称) {
super (;
this.name=name;
}
publicmyclassloader (类加载器parent,字符串名称)。
super(parent );
this.name=name;
}
getClassLoader ) )时返回此方法,如果未重载,则显示MyClassLoader的引用地址
公共字符串字符串
return this.name;
}
//设置文件加载路径
公共语音路径(字符串路径) {
this.path=path;
}
protectedclassfindclass (字符串名称) throws ClassNotFoundException{
byte[]data=loadclassdata(name;
//参数off表示什么?
returndefineclass(name,data,0,data.length );
}
//.class文件读取到内存中,并以字节数返回
private byte [ ] loadclassdata (字符串名称) throws ClassNotFoundException{
文件输入流fis=null;
字节输出流baos=null;
byte [ ]数据=null;
try{
//读取文件内容
name=name.replaceall(((. )、(() );
System.out.println;
//将文件读入数据流
fis=new文件输入(pathname filetype;
baos=new ByteArrayOutputStream (;
int ch=0;
wile((ch=fis.read ) )!=-1 ()
>baos.write(ch);}
data = baos.toByteArray();
}catch (Exception e){
throw new ClassNotFoundException("Class is not found:"+name,e);
}finally {
// 关闭数据流
try {
fis.close();
baos.close();
}catch (Exception e){
e.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception {
MyClassLoader loader1 = new MyClassLoader("loader1");
// 获取MyClassLoader加载器
System.out.println("MyClassLoader 加载器:" + MyClassLoader.class.getClassLoader());
// 设置加载类查找文件路径
loader1.setPath("D:\workspace\bac5\java\");
loader(loader1);
}
private static void loader(MyClassLoader loader) throws Exception {
// MyClassLoader 由系统加载器加载,跟test是不同的加载器,会出现NOClassDefFoundError
// 如果类中有package,则加载类名时,需要写全,不然找不到该类,会出现NOClassDefFoundError
Class test = loader.loadClass("test");
Object test1 = test.newInstance();
// test test2 = (test) test1;
// 如果MyClassLoader与test非同一个加载器,访问时,需要用到反射机制
Field v1 = test.getField("v1");// java反射机制,取test中的静态变量
System.out.println("被加载出来的类是:"+v1.getInt(test1));
// 卸载,将引用置空
test = null;
test1 = null;
// 重新加载
test = loader.loadClass("test");
test1 = test.newInstance();
System.out.println("test1 hashcode:"+test1.hashCode());
}
}
test.java
public class test {
public static int v1= 1;
public test(){
System.out.println("调用到了test");
System.out.println("test加载器为:"+this.getClass().getClassLoader());
}
}
注意:
1.package,如果加载的类有package文件,则查找时,class的名字应该为包名.类名,不然会报NOClassDefFoundError。
2.如果两类不是同一个加载器加载,强制转换,会报NOClassDefFoundError错误。
3.不是同一加载器加载的两类如果想访问对方,则需要使用反射机制。
心得
在编写代码的过程中,同一个包下的文件,总是会报NOClassDefFoundError。直至看到含包名文件的存储方式,才发现需要将loadClass的文件名称替换成包名.类名的形式,才能正确加载到类。