类加载器子系统

来源:互联网 发布:淘宝店铺淘金币签到 编辑:程序博客网 时间:2024/05/29 06:42

类加载器子系统(Class Loader) 

类加载器子系统负责加载编译好的.class字节码文件,并装入内存,使JVM可以实例化或以其它方式使用加载后的类。JVM的类加载子系统支持在运行时的动态加载,动态加载的优点有很多,例如可以节省内存空间、灵活地从网络上加载类,动态加载的另一好处是可以通过命名空间的分隔来实现类的隔离,增强了整个系统的安全性。

1、ClassLoader的分类:
a.启动类加载器(BootStrap Class Loader):负责加载rt.jar文件中所有的Java类,即Java的核心类都是由该ClassLoader加载。在Sun JDK中,这个类加载器是由C++实现的,并且在Java语言中无法获得它的引用。
b.扩展类加载器(Extension Class Loader):负责加载一些扩展功能的jar包。
c.系统类加载器(System Class Loader):负责加载启动参数中指定的Classpath中的jar包及目录,通常我们自己写的Java类也是由该ClassLoader加载。在Sun JDK中,系统类加载器的名字叫AppClassLoader。
d.用户自定义类加载器(User Defined Class Loader):由用户自定义类的加载规则,可以手动控制加载过程中的步骤。

2、ClassLoader的工作原理
类加载分为装载、链接、初始化三步。
a.装载
通过类的全限定名和ClassLoader加载类,主要是将指定的.class文件加载至JVM。当类被加载以后,在JVM内部就以“类的全限定名+ClassLoader实例ID”来标明类。
在内存中,ClassLoader实例和类的实例都位于堆中,它们的类信息都位于方法区。
装载过程采用了一种被称为“双亲委派模型(Parent Delegation Model)”的方式,当一个ClassLoader要加载类时,它会先请求它的双亲ClassLoader(其实这里只有两个ClassLoader,所以称为父ClassLoader可能更容易理解)加载类,而它的双亲ClassLoader会继续把加载请求提交再上一级的ClassLoader,直到启动类加载器。只有其双亲ClassLoader无法加载指定的类时,它才会自己加载类。
双亲委派模型是JVM的第一道安全防线,它保证了类的安全加载,这里同时依赖了类加载器隔离的原理:不同类加载器加载的类之间是无法直接交互的,即使是同一个类,被不同的ClassLoader加载,它们也无法感知到彼此的存在。这样即使有恶意的类冒充自己在核心包(例如java.lang)下,由于它无法被启动类加载器加载,也造成不了危害。
由此也可见,如果用户自定义了类加载器,那就必须自己保障类加载过程中的安全。
b.链接
链接的任务是把二进制的类型信息合并到JVM运行时状态中去。
链接分为以下三步:
a.验证:校验.class文件的正确性,确保该文件是符合规范定义的,并且适合当前JVM使用。
b.准备:为类分配内存,同时初始化类中的静态变量赋值为默认值。
c.解析(可选):主要是把类的常量池中的符号引用解析为直接引用,这一步可以在用到相应的引用时再解析。
c.初始化
初始化类中的静态变量,并执行类中的static代码、构造函数。
JVM规范严格定义了何时需要对类进行初始化:
a、通过new关键字、反射、clone、反序列化机制实例化对象时。
b、调用类的静态方法时。
c、使用类的静态字段或对其赋值时。
d、通过反射调用类的方法时。
e、初始化该类的子类时(初始化子类前其父类必须已经被初始化)。
f、JVM启动时被标记为启动类的类(简单理解为具有main方法的类)。

3、自定义类加载器和打破双亲委派

一般的场景中使用Java默认的类加载器即可,但有时为了达到某种目的又不得不实现自己的类加载器,例如为了达到类库的互相隔离,例如为了达到热部署重加载功能

a、沿用双亲委派机制自定义类加载器很简单,只需继承ClassLoader类并重写findClass方法即可。如下例子:

①先定义一个待加载的类Test,它很简单,只是在构建函数中输出由哪个类加载器加载。

public class Test {    public Test(){        System.out.println(this.getClass().getClassLoader().toString());    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

②定义一个TestClassLoader类继承ClassLoader,重写findClass方法,此方法要做的事情是读取Test.class字节流并传入父类的defineClass方法即可。然后就可以通过自定义累加载器TestClassLoader对Test.class进行加载,完成加载后会输出“TestLoader”。

public class TestClassLoader extends ClassLoader {    private String name;    public TestClassLoader(ClassLoader parent, String name) {        super(parent);        this.name = name;    }    @Override    public String toString() {        return this.name;    }    @Override    public Class<?> findClass(String name) {        InputStream is = null;        byte[] data = null;        ByteArrayOutputStream baos = new ByteArrayOutputStream();        try {            is = new FileInputStream(new File("d:/Test.class"));            int c = 0;            while (-1 != (c = is.read())) {                baos.write(c);            }            data = baos.toByteArray();        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                is.close();                baos.close();            } catch (IOException e) {                e.printStackTrace();            }        }        return this.defineClass(name, data, 0, data.length);    }    public static void main(String[] args) {        TestClassLoader loader = new TestClassLoader(                TestClassLoader.class.getClassLoader(), "TestLoader");        Class clazz;        try {            clazz = loader.loadClass("test.classloader.Test");            Object object = clazz.newInstance();        } catch (Exception e) {            e.printStackTrace();        }     }}

b、打破双亲委派机制则不仅要继承ClassLoader类,还要重写loadClass和findClass方法,如下例子:

①定义Test类。

public class Test {    public Test(){        System.out.println(this.getClass().getClassLoader().toString());    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

②重新定义一个继承ClassLoader的TestClassLoaderN类,这个类与前面的TestClassLoader类很相似,但它除了重写findClass方法外还重写了loadClass方法,默认的loadClass方法是实现了双亲委派机制的逻辑,即会先让父类加载器加载,当无法加载时才由自己加载。这里为了破坏双亲委派机制必须重写loadClass方法,即这里先尝试交由System类加载器加载,加载失败才会由自己加载。它并没有优先交给父类加载器,这就打破了双亲委派机制。

public class TestClassLoaderN extends ClassLoader {    private String name;    public TestClassLoaderN(ClassLoader parent, String name) {        super(parent);        this.name = name;    }    @Override    public String toString() {        return this.name;    }    @Override    public Class<?> loadClass(String name) throws ClassNotFoundException {        Class<?> clazz = null;        ClassLoader system = getSystemClassLoader();        try {            clazz = system.loadClass(name);        } catch (Exception e) {            // ignore        }        if (clazz != null)            return clazz;        clazz = findClass(name);        return clazz;    }    @Override    public Class<?> findClass(String name) {        InputStream is = null;        byte[] data = null;        ByteArrayOutputStream baos = new ByteArrayOutputStream();        try {            is = new FileInputStream(new File("d:/Test.class"));            int c = 0;            while (-1 != (c = is.read())) {                baos.write(c);            }            data = baos.toByteArray();        } catch (Exception e) {            e.printStackTrace();        } finally {            try {                is.close();                baos.close();            } catch (IOException e) {                e.printStackTrace();            }        }        return this.defineClass(name, data, 0, data.length);    }    public static void main(String[] args) {        TestClassLoaderN loader = new TestClassLoaderN(                TestClassLoaderN.class.getClassLoader(), "TestLoaderN");        Class clazz;        try {            clazz = loader.loadClass("test.classloader.Test");            Object object = clazz.newInstance();        } catch (Exception e) {            e.printStackTrace();        }    }}

原创粉丝点击