为什么JVM需要多种类加载器
JVM的类加载器
刚刚学习JVM的类加载机制的时候,会被教育说JVM的类加载机制需要不同的类加载器。过了很久之后忘记了。现在再复习一下。
为什么需要多个类加载器?
加载器作用是通过类名来获取二进制字节流。
我们先抛开所有问题,从写程序的角度来讲一个程序应该具有什么?
1、健壮性
2、功能性
3、鲁棒性
4、效率性
5、维护性
6、可靠性(安全性)
由此,我们对比JVM。JVM也是一个软件,也应该基本符合上述的几个特性。
健壮性和功能性:JVM可以从不同的地方去加载class,比如文件系统,web,FTP等,这就要求JVM屏蔽底层的加载逻辑,只需要提供一个classloard()的接口就行了,客户端就可以加载类但是却不用管类加载器到底是怎么实现的。
再说安全性:JVM得保证自有类不遭到破坏(比如java.lang包下的类,这个破坏的原理就是类对同包级别及其子包下的public类具有操作权限)。为了解决这个问题,使用双亲委派机制刚好可以解决。
此外,如果我想在一个JVM中启动两个相同的应用的不同版本(不同版本值得是同样的全限定名的类,但是功能不一样),怎么办?假设只有一个类加载器,那么第二次加载该类会失败,第二个版本的应用还是使用了第一个版本的class,那么第二个应用就无法提供第二个版本类的功能了。所以得需要每个应用都有相互隔离的类加载器,否则第二个应用的类可能会覆盖第一个应用之前加载的类,从而造成一些意想不到的后果。
每种加载器都有对应的层级来加载某些特定的类,来保证他们之间的安全性。
自定义的类加载器。
package com.stat;
import java.util.Date;
public class MyDate extends Date {
@Override
public String toString() {
return "MyDate : 我的时间。";
}
}
package com.stat;
import java.io.*;
/**
* 自定义类加载器
*/
public class MyClassLoader extends ClassLoader {
String classDir;
public MyClassLoader() {
}
public MyClassLoader(String classDir) {
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String classFile = classDir + "/" + name + ".class";
System.out.println("classFile path==" + classFile);
try {
//这个地方我们只是简单的读取文件流的方式来获取byte数组
//其实可以尝试将class文件加密以后 这里解密 这样就可以保证
//这种class文件 只有你写的classloader才能读取的了。
//其他任何classloader都读取不了 包括系统的。
byte[] classByte = toByteArray(classFile);
return defineClass(classByte, 0, classByte.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
public static byte[] toByteArray(String filename) throws IOException, FileNotFoundException {
File f = new File(filename);
if (!f.exists()) {
throw new FileNotFoundException(filename);
}
try (ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length())) {
BufferedInputStream in = null;
in = new BufferedInputStream(new FileInputStream(f));
int bufSize = 1024;
byte[] buffer = new byte[bufSize];
int len = 0;
while (-1 != (len = in.read(buffer, 0, bufSize))) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
}
package com.stat;
import java.util.Date;
public class MyClassTest {
public static void main(String[] args)
{
try {
//注意这个路径:是在target目录下的,是编译后的路径
Class classDate = new MyClassLoader("D:\\javaworkspace\\test02\\test02_module01\\target\\classes\\com\\stat").loadClass("com.stat.MyDate");
Class classDate2 = new MyClassLoader("D:\\javaworkspace\\test02\\test02_module01\\target\\classes\\com\\stat").loadClass("MyDate");
Date date = (Date) classDate.newInstance();
System.out.println("date ClassLoader:"+date.getClass().getClassLoader().getClass().getName());
System.out.println(date);
Date date2 = (Date) classDate2.newInstance();
System.out.println("date2 ClassLoader:"+date2.getClass().getClassLoader().getClass().getName());
System.out.println(date2);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
输出结果如下
classFile path==D:\javaworkspace\test02\test02_module01\target\classes\com\stat/MyDate.class
date ClassLoader:sun.misc.Launcher$AppClassLoader
MyDate : 我的时间。
date2 ClassLoader:com.stat.MyClassLoader
MyDate : 我的时间。
可以看到date 的类加载器是AppClassLoader,而date2的类加载器是自定义的
大家可以看到classdate和classDate2 这2个类,我们在用classLoader去加载的时候传的参数唯一的不同就是前者传入了完整的包名,而后者没有。这就导致了前者的classLoader依旧是系统自带的appclassloader 而后者才是我们自定义的classloader。 原因:
虽然对于classDate和classDate2来说,我们手动指定了她的类加载是我们自定义的myclassloader,但是根据类加载器的规则,我们能用父亲的loadclass就肯定不会用自己的,而我们系统类加载器,AppClassLoader要想loadclass成功是需要传入完整的包名的。所以classDate的构造还是传入了完整的包名,这就是为啥classDate的加载器还是AppClassLoader,但是classDate2并没有传入完整的包名,所以AppClassLoader也是找不到这个CustomDate类的,最后只能交给MyClassLoader这个最底层的,我们自定义的classloader来load