(三)类加载器

类加载器的作用是完成类加载过程中的装载步骤,即将.class文件加载到JVM

 

有两种类型的类加载器

1、JVM自带的类加载器

根类加载器(Bootstrap)

扩展类加载器(Extension)

系统类加载器(System)

2、用户自定义类加载器

扩展自java.lang.ClassLoader

 

这些类加载器以父子关系的形式存在



 

最顶层的类加载器,根类加载器,负责加载java的核心类,它加载的路径是jre/lib/rt.jar,我们用到的java核心类如集合框架类ArrayList以及String类等,都是在rt.jar中,即这些类是被BootStrap ClassLoader加载的;BootStrap ClassLoader 是用C++实现的;

Extension ClasssLoader负责加载jre的扩展类,它加载的路径是jre/lib/ext,它的父类加载器是BootStrap ClassLoader;

System ClassLoader从CLASSPATH环境变量指定的路径加载类,一般我们自己编写的类都是由System ClassLoader加载,System ClassLoader的父类加载是Extension ClasssLoader。

除了BootStrap ClassLoader外,其他的ClassLoader(包括用户自定义的)都需要继承java.lang.ClassLoader

 
类加载器在加载类时,采取父委托机制,即加载类时,先请求父类加载器去加载,如果能加载,则在父类加载器加载完后直接返回,如果父类加载器无法加载,则自身尝试去加载,比如,加载我们自己编写的 com . jiangnan . classloader . load .User这个类,首先由 System ClassLoader加载, System ClassLoader委托父类加载器 Extension ClasssLoader加载, Extension ClasssLoader委托 BootStrap ClassLoader加载, BootStrap ClassLoader加载不到, Extension ClasssLoader也加载不到, System ClassLoader尝试自己加载,从CLASSPATH环境变量指定的路径加载,如果该路径下有这个类,则加载进来,如果找不到,抛出 java . lang .ClassNotFoundException
 

JVM规范允许类加载器在预料到某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了class文件缺失或者存在错误,类加载器必须在程序首次主动使用该类时才报告错误,如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。

 

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类

 

由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看它们的包名是否相同,还要看定义类的加载器是否相同。只有属于同一运行时包的类才能互相访问包可见(默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。

 

同一个命名空间内的类是互相可见的

子加载器的命名空间包含所有父加载器的命名空间,因此,由子加载器加载的类能看见父加载器加载的类,例如,系统类加载器加载的类能看到根类加载器加载的类

由父加载器加载的类不能看见子加载器加载的类

如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见

 

接下来看几个测试

创建两个类User、Person,代码如下:

public class User {
    public User() {
        System.out.println("User loaded by " + this.getClass().getClassLoader());
        new Person();
    }
}  
public class Person {
    public Person() {
         System. out .println( "Person loaded by "  +  this .getClass().getClassLoader());
    }

}

分别在构造器里输出加载当前类的ClassLoader对象,在User的构造器中创建了Person对象

 

测试1:

     @Test
    public void testClassLoader1() {
        System.out.println(this.getClass().getClassLoader());
        new User();
        System.out.println("String loaded by " + String.class.getClassLoader());

    } 

控制台输出如下:

sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$AppClassLoader@addbf1

String loaded by null 

可以看出,当前测试类以及User、Person类都是由系统类加载器加载的,而String类是由根类加载器加载的,由于根类加载器是由C++实现的,所以在代码中获取会返回null

 

测试2:

     @Test
    public void testClassLoader2() {
        ClassLoader classLoader = this.getClass().getClassLoader();
        while(true) {
            System.out.println(classLoader);
            if (null == classLoader) {
                break;
            }
            classLoader = classLoader.getParent();
        }

    }  

从加载当前测试类的类加载器开始,输出父类加载器,控制台输出如下:

sun.misc.Launcher$AppClassLoader@addbf1
sun.misc.Launcher$ExtClassLoader@42e816

null 

可以看出,类加载器采用父子关系

 

接下来自定义一个类加载器,从指定的loadPath路径加载class,代码如下:

 public class MyClassLoader extends ClassLoader {

    private String name;
    /**加载路径*/
    private String loadPath;
    private static final String EXTENSION = ".class";
    
    public MyClassLoader(String name, String loadPath) {
        super();
        this.name = name;
        this.loadPath = loadPath;
    }
    
    public MyClassLoader(String name, String loadPath, ClassLoader parent) {
        super(parent);
        this.name = name;
        this.loadPath = loadPath;
    }
    
    @Override
    protected Class<?> findClass(String namethrows ClassNotFoundException {
        byte[] b = loadClassData(name);
        if (null == b || b.length == 0) {
            throw new ClassNotFoundException();
        }
        return defineClass(nameb, 0, b.length);
    }
    
    /**
     * 加载读取class文件
     * @param name
     * @return
     */
    private byte[] loadClassData(String name) {
        String dir = name.replace(".", File.separator);
        String realPath = this.loadPath + File.separator + dir + EXTENSION;
        ByteArrayOutputStream baos = null;
        DataInputStream dis = null;
        int end = 0;
        try {
            baos = new ByteArrayOutputStream();
            dis = new DataInputStream(new FileInputStream(realPath));
            while ((end = dis.read()) != -1) {
                baos.write(end);
            }
        } catch (IOException e) {
            //e.printStackTrace();
        } finally {
            if (null != baos) {
                try {
                    baos.close();
                } catch (IOException e) {
                }
            }
            if (null != dis) {
                try {
                    dis.close();
                } catch (IOException e) {
                }
            }
        }
        return baos.toByteArray();
    }
    @Override
    public String toString() {
        return this.name;
    }

}

类加载器在加载类时,会调用ClassLoader的loadClass(String name),查看jdk源码,loadClass方法中会调用父类加载器去加载,如果加载不到,会调用findClass(String name)自行加载,因此自定义的类加载器都需要覆盖findClass(String name)方法,以下为jdk中ClassLoader的loadClass方法的部分源码:
if (c == null) {
        try {
        if (parent != null) {
            c = parent.loadClass(namefalse);
        } else {
            c = findBootstrapClassOrNull(name);
        }
        } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
            // If still not found, then invoke findClass in order
            // to find the class.
            c = findClass(name);
        }
    }  
创建测试类,如下:
public class ClassLoaderTest1 {
    
    public static void main(String[] args) {
        MyClassLoader classLoader1 = new MyClassLoader("loader1""E:\\classloader\\path1");
        Class<?> clazz1;
        try {
            System.out.println("---------1----------");
            clazz1 = classLoader1.loadClass("com.jiangnan.classloader.classloader.User");
            clazz1.newInstance();
        } catch (Exception e) {
            System.out.println(e.getClass().getName());
        }
        System.out.println("---------1----------");
        MyClassLoader classLoader2 = new MyClassLoader("loader2""E:\\classloader\\path2"classLoader1);
        Class<?> clazz2;
        try {
            System.out.println("---------2----------");
            clazz2 = classLoader2.loadClass("com.jiangnan.classloader.classloader.User");
            clazz2.newInstance();
        } catch (Exception e) {
            System.out.println(e.getClass().getName());
        }
        System.out.println("---------2----------");
    }
}  
创建了两个MyClassLoader的实例,其中classLoader1是classLoader2的父类加载器,分别从指定的路径加载User类,运行此main方法,控制台输出如下:
---------1----------
User loaded by sun.misc.Launcher$AppClassLoader@addbf1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------1----------
---------2----------
User loaded by sun.misc.Launcher$AppClassLoader@addbf1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------2---------- 
两次都是由应用类加载器加载的,因为classLoader2的父加载器为应用类加载器,根据父委托机制,应用类加载器在CLASSPATH路径下能加载到User类
 
将工程bin目录下的User.class文件删除,放到  E:\\classloader\\path1, 注意,需要按照指定的包目录存放,例如,在我的环境中,包名是 com.jiangnan.classloader.classloader, 因此,User.class被放在 E:\classloader\path1\com\jiangnan\classloader\classloader目录下
现在的情况是,测试类 ClassLoaderTest1、自定义类加载器类 MyClassLoader以及Person类位于工程bin目录下,二User类位于path1路径下,运行main方法,控制台输出如下:
---------1----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------1----------
---------2----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------2----------  
User类是由classLoader1实例加载的,因为此时系统类加载器在bin目录下加载不到User类
 
在main方法中增加classLoader3实例,如下:
MyClassLoader classLoader3 = new MyClassLoader("loader3""E:\\classloader\\path3"null);
        Class<?> clazz3 = null;
        try {
            System.out.println("---------3----------");
            clazz3 = classLoader3.loadClass("com.jiangnan.classloader.classloader.User");
            clazz3.newInstance();
        } catch (Exception e) {
            System.out.println(e.getClass().getName());
        }
        System.out.println("---------3----------");
        
        System.out.println("clazz1 == clazz2 is " + (clazz1 == clazz2));
        System.out.println("clazz1 == clazz3 is " + (clazz1 == clazz3)); 
指定classLoader3的父类加载器为null,即classLoader3的类加载器为BootStrap根类加载器,同时判断clazz1和clazz2、clazz3是否相等,控制台输出如下:
---------1----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------1----------
---------2----------
User loaded by loader1
Person loaded by sun.misc.Launcher$AppClassLoader@addbf1
---------2----------
---------3----------
User loaded by loader3
Person loaded by loader3
---------3----------
clazz1 == clazz2 is true
clazz1 == clazz3 is false
clazz1与clazz3不相等,因为它们是被不同的类加载器加载,它们在内存中的存在形式如下:


 
 
测试代码位于 https://github.com/ywu2014/ClassLoader

猜你喜欢

转载自ywu.iteye.com/blog/2280262