- Date:2015-5-10
- Tag:java;类加载器;ClassLoader;ExtClassLoader;AppClassLoader
- Author:踏雪
- Email:[email protected]
1、 Java类加载器是Java运行时环境的一部分,负责动态加载Java类到JVM的内存空间中。每个Java类必须由某个类加载器装入到内存中。每一个类加载器都有一个父类加载器(BootStrap引导类加载器没有)。
2、 JVM中有3个默认的加载器:
(1) BootStrap:引导类加载器。这个加载器很特殊,它不是JAVA类,因此它不需要被别人加载,它嵌套在JVM内核里,也就是说JVM启动的时候BootStrap就启动了,它是C++写的二进制代码,可以加载别的类。这也是为什么System.class.getClassLoader()结果为null的原因,因为它不是JAVA类,所以它的引用返回null。负责加载核心Java库,存储在/jre/lib/rt.jar
(2) ExtClassLoader:扩展类加载器。
(3) AppClassLoader:根据类路径来加载java类。一般我们自定义的类都是通过这个AppClassLoader加载。
3、类加载器及其委托机制
(1) 当Java虚拟机加载一个类时,如何加载呢?
首先当前线程的类加载器去加载线程中的第一个类(如类A):
- 如果类A引用了类B,Java虚拟机将加载类A的加载器去加载类B
- 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类
(2) 委托机制的作用—防止内存中出现多分同样的字节码
例如类A和类B都要加载System类:
- 如果不用委托机制,都是自己加载,那么类A会加载一份Sysem字节码,同时类B也会加载一份字节码看,这样内存中就出现了两份System字节码。
- 如果使用委托机制,会递归地向父类查找,首选用BootStrap尝试加载,如果找不到就向下。这里System就能在BootStrap中找到然后加载。如果此时B也加载System,也从BootStrap,此时BootStrap发现已经加载过System字节码,则直接返回内存中的System字节码而不是重新加载,这样就保证了内存中只有一份字节码。
例如:用户使用一个自定义的类(没有使用自定义类加载器),那么系统就开始从AppClassLoader向父类加载器发送请求,一直到BootStrap,然后BootStrap类加载器没有父类,于是就开始查找对应路径下是否有符合要求的类。如果没有,则又向下查询,最终回到AppClassLoader(请求的发起者),如果有则返回,没有则会抛出ClassNotFoundException的异常。如果在AppClassLoader之前,其他类加载器已经找到,则由对应的类加载其返回。
代码实例1:
public class ClassLoaderTest {
public static void main(String[] args) {
System.out.println(ClassLoaderTest.class.getClassLoader().getClass().getName());
System.out.println("=========================================");
ClassLoader loader = ClassLoaderTest.class.getClassLoader();
while(loader != null) {
System.out.println(loader.getClass().getName());
loader = loader.getParent();
}
System.out.println(loader);
}
}
结果:
代码实例2:
首先我们自定义一个类:
public class Secret{
public String key(){
return "The key is 5132561";
}
}
然后打印加载这个类的类加载器:
System.out.println(new Secret().getClass().getClassLoader().getClass().getName());
输出结果如下:
因为从BootStrapExtClassLoaderAppClassLoader这个过程中,只有到AppClassLoader才找到对应的类,所以打印AppClassLoader。
代码实例3:
如果我们将Secret的字节码打成jar包并放到ExtClassLoader所指向的目录–/jre/lib/ext目录下,那么BootStrapExtClassLoader,到ExtClassLoader就找到了对应的类并返回,这时就打印ExtClassLoader。
打包成jar包并保存到/jre/lib/ext目录下。
再次输出:
System.out.println(new Secret().getClass().getClassLoader().getClass().getName());
自定义的类加载器必须继承ClassLoader,并实现重载findClass方法。
代码实例:
public class DecodeClassLoader extends ClassLoader{
private String classDir;
public DecodeClassLoader(){}
public DecodeClassLoader(String classDir){
this.classDir = classDir;
}
@Override
protected Class<?> findClass(String name) {
File f = new File(classDir,name.substring(name.lastIndexOf(".") + 1) + ".class");
try {
InputStream in = new FileInputStream(f);
ByteArrayOutputStream out = new ByteArrayOutputStream();
encode(in, out);
byte[] bytes = out.toByteArray();
in.close();
out.close();
return defineClass(bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
return null;
}
private static void encode(InputStream in, OutputStream out) throws IOException{
int b = 0;
while((b=in.read())!= -1){
out.write(b ^ 0xff);
}
}
}
通过一个加密类对一个重要的类字节码进行加密,使得只有使用解密类加载器才可以成功加载并使用。示意图如下:
代码实例:
(1)DecodeClassLoader.java见上面。
(2)加密类:对Secret字节码进行加密
public class EncodeUtil {
private static void encode(InputStream in, OutputStream out) throws IOException{
int b = 0;
while((b=in.read())!= -1){
out.write(b ^ 0xff);
}
}
public static void main(String[] args) throws IOException {
String srcPath = "E:\\java_workspace\\004ClassLoaderDemo\\bin\\com\\shuwoom\\classloader\\Secret.class";//args[0];
String destDir = "shuwoomlib";
FileInputStream in = new FileInputStream(srcPath);
String destFileName = srcPath.substring(srcPath.lastIndexOf("\\") + 1);
String destPath = destDir + "\\" + destFileName;
System.out.println(destPath);
FileOutputStream out = new FileOutputStream(destPath);
encode(in,out);
in.close();
out.close();
}
}
(3)被加载的类:
public class Secret{
public String key(){
return "The key is 5132561";
}
}
首先运行EncodeUtil加密工具类,将Secret.class文件加密并保存到指定的shuwoomlib目录下,此时,在bin/bom/shuwoom/classloader目录下的Secret是未经加密的字节码。现在我们将shuwoomlib目录下加密的Secret.class替换掉bin/bom/shuwoom/classloader目录下的Secret.class。那么AppClassLoader找到的就是经过加密的字节码。
//如果直接使用AppClassLoader加载,会报错。
System.out.println(new Secret().key());
//通过DecodeClassLoader类加载器获得Secret原字节码
ClassLoader classLoader = new DecodeClassLoader("shuwoomlib");
Class clazz = classLoader.loadClass("Secret");
Method getKeyMethod = clazz.getMethod("key");
System.out.println(getKeyMethod.invoke(clazz.newInstance(), null));
此时才能正常使用。