Jvm提供了三大内置的类加载器,不同的类加载器负责将不同的类加载到内存之中
- 根加载器(Bootstrap ClassLoader)
是最顶层的加载器,是由C++编写的,主要负责虚拟机核心类库的加载,如整个java.lang包,根加载器是获取不到引用的,因此String.class.getClassLoader()返回为空 - 扩展类加载器(Ext ClassLoader)
他是根加载器的子类,由纯java实现 - 系统类加载器(Application ClassLoader)
负责加载classpath下的类库资源,或者我们项目开发中的第三方jar,他的父加载器是扩展类加载器,同时他也是自定义类加载器的默认父加载器
然后我们自定义个类加载器
@RequiresApi(api = Build.VERSION_CODES.O)
public class MyClassLoader extends ClassLoader {
//定义默认的class存放路径
private final static Path DEFAULT_CLASS_DIR= Paths.get("E:","classex");
private final Path classDir;
public MyClassLoader(){
super();
classDir = DEFAULT_CLASS_DIR;
}
//允许放入指定路径的class路径
public MyClassLoader(String classDir){
super();
this.classDir=Paths.get(classDir);
}
//指定class路径的同时,指定父加载器
public MyClassLoader(String classDir,ClassLoader parent){
super(parent);
this.classDir=Paths.get(classDir);
}
//重写父类的findClass方法
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//读取class的二进制数据
byte[] classBytes=this.readClassBytes(name);
if(null==classBytes||classBytes.length==0){
throw new ClassNotFoundException("Can not load the class "+name);
}
//调用defineClass方法定义class
return this.defineClass(name,classBytes,0,classBytes.length);
}
//将class文件读入内存
private byte[] readClassBytes(String name) throws ClassNotFoundException {
//将包名分隔符转换为文件路径分隔符
String classPath=name.replace(".","/");
Path classFullPath=classDir.resolve(Paths.get(classPath+".class"));
// Path classFullPath=Paths.get(classPath+".class");
if(!classFullPath.toFile().exists()){
throw new ClassNotFoundException("The class "+name+"not found");
}
try(ByteArrayOutputStream baos=new ByteArrayOutputStream()){
Files.copy(classFullPath,baos);
return baos.toByteArray();
} catch (IOException e) {
throw new ClassNotFoundException("load the class "+name + "occurerror.",e);
}
}
public String toString(){
return "My ClassLoader";
}
}
package classloader;
public class World {
public String welcome(){
return "你好";
}
}
public class TestClassLoader {
@RequiresApi(api = Build.VERSION_CODES.O)
public static void main(String [] args){
MyClassLoader myClassLoader=new MyClassLoader();
try {
Class<?> aclass=myClassLoader.loadClass("classloader.World");
System.out.println(aclass.getClassLoader());
Object helloWorld=aclass.newInstance();
System.out.println(helloWorld);
Method welcomeMethod=aclass.getMethod("welcome");
String result= (String) welcomeMethod.invoke(helloWorld);
System.out.print("Result:"+result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
sun.misc.Launcher$AppClassLoader@232204a1
classloader.World@1540e19d
Result:你好
双亲委托机制
当一个类加载器被调用了loadClass之后他并不会直接将其加载,而是先交给父加载器尝试加载直到最顶层的父加载器,然后再依次向下加载,直到加载成功
那么我们如何绕过双亲委托机制,直接使用我们自定义的类加载器呢
- 绕过系统加载器,直接将扩展类加载器作为MyClassLoader的父加载器
//获取系统类加载器
ClassLoader extClassLoader=TestClassLoader.class.getClassLoader().getParent();
MyClassLoader classLoader=new MyClassLoader("G:\\classloader",extClassLoader);
Class<?> mClass=classLoader.loadClass("com.classloader.World");
System.out.println(classLoader);
这样一来,根加载器和扩展类加载器都无法对该类加载,自然而然只能交给MyClassLoader进行加载
- 在构造MyClassLoader 的时候指定其父类加载器为null
//获取系统类加载器
MyClassLoader classLoader=new MyClassLoader("G:\\classloader",null);
Class<?> mClass=classLoader.loadClass("com.classloader.World");
System.out.println(classLoader);
使用不同的类加载器,或者同一个类加载器的不同实例,去加载同一个class,则会在堆内存和方法区产生多个class对象
在类加载器进行类加载的时候,首先会到加载记录表也就是缓存中,查看该类是否已经被加载过了,如果已经被加载过了,就不会重复加载,否则将会认为其是首次加载
JVM规定了一个Class只有在满足下面三个条件的时候才会被GC回收,也就是类被卸载:
- 该类的所有的实例都已经被GC,比如Simple.class的所有Simple实例都被回收掉
- 加载该类的ClassLoader实例被回收
- 该类的class实例没有在其他地方被引用
网友评论