Java类加载器总结整理

  • Date:2015-5-10
  • Tag:java;类加载器;ClassLoader;ExtClassLoader;AppClassLoader
  • Author:踏雪
  • Email:[email protected]

一、 What(是什么?)

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());
输出结果如下:

因为从BootStrapExtClassLoaderAppClassLoader这个过程中,只有到AppClassLoader才找到对应的类,所以打印AppClassLoader。

代码实例3:
如果我们将Secret的字节码打成jar包并放到ExtClassLoader所指向的目录–/jre/lib/ext目录下,那么BootStrapExtClassLoader,到ExtClassLoader就找到了对应的类并返回,这时就打印ExtClassLoader。
打包成jar包并保存到/jre/lib/ext目录下。

再次输出:

System.out.println(new Secret().getClass().getClassLoader().getClass().getName());

二、 How(如何自定义类加载器?)

自定义的类加载器必须继承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);
        }
    }   
}

三、Where(在什么地方使用?)

  • 运行时装载或卸载类。常用于:
     实现脚本语言
     用于bean生成器
     允许用户定义的扩展性
     允许命名控件之间的通信。
  • 改变Java字节码的装入,例如Java类字节码的加密
  • 修改以装入的字节码

四、类加载器的综合应用实例

通过一个加密类对一个重要的类字节码进行加密,使得只有使用解密类加载器才可以成功加载并使用。示意图如下:

代码实例:

(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));

此时才能正常使用。

猜你喜欢

转载自blog.csdn.net/wen294299195/article/details/50084771