详细了解类加载器

定义

什么是类加载器:虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的字节流”这个动作放到java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为类加载器

类加载器带来的好处

对于自定义的一些类,考虑使用按需加载的原则,即在程序使用到时才加载类,节省内存消耗,这时即可通过类加载器来动态加载。

类加载器的种类

  • 启动类加载器(Bootstrap ClassLoader):
    加载对象是Java核心库,把一些核心的Java类加载进JVM中,这个加载器使用原生代码(C/C++)实现,并不是继承java.lang.ClassLoader,它是所有其他类加载器的最终父加载器,负责加载<JAVA_HOME>/jre/lib目录下JVM指定的类库,或者被-Xbootclasspath参数所指定的路径中的。其实它属于JVM整体的一部分,JVM一启动就将这些指定的类加载到内存中,避免以后过多的I/O操作,提高系统的运行效率。启动类加载器无法被Java程序直接使用。
  • 扩展类加载器(Extension ClassLoader):
    加载的对象为Java的扩展库,即加载
    <JAVA_HOME>/jre/lib/ext目录里面的类,或者被java.ext.dirs系统变量所指定的路径中的所有类库。这个类由启动类加载器加载,但因为启动类加载器并非用Java实现,已经脱离了Java体系,所以如果尝试调用扩展类加载器的getParent()方法获取父加载器会得到null。然而,它的父类加载器是启动类加载器。
  • 应用程序类加载器(Application ClassLoader):
    亦叫系统类加载器(System ClassLoader),它负责加载用户类路径(CLASSPATH)指定的类库,如果程序没有自己定义类加载器,就默认使用应用程序类加载器。它也由启动类加载器加载,但它的父加载类被设置成了扩展类加载器。如果要使用这个加载器,可通过ClassLoader.getSystem ClassLoader()获取。
    类加载器关系
    类加载器关系

自定义类加载器

打印类加载器的测试类
TestClassLoader

public class TestClassLoader {
    public TestClassLoader(){
    	//打印出加载此类的类加载器
        System.out.println(this.getClass().getClassLoader().toString());
    }
}

1、沿用双亲委派机制自定义类加载只需要继承java.lang.ClassLoader即可并重写findClass方法即可。
MyClassLoader

public class MyClassLoader extends ClassLoader {
	// 类加载器的名称
    private String name;

    public MyClassLoader(ClassLoader parent,String name){
        super(parent);
        this.name = name;
    }

    @Override
    public String toString() {
        return this.name;
    }

    @Override
    public Class<?> findClass(String classpath) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        try{
            baos = new ByteArrayOutputStream();
            // 如果想让被加载的类用自定义的类加载器,需要把这个class文件
            // 转移到不是classpath路径下的目录,并且把classpath路径下的class文件删除
            // 否则还会被父类加载器加载
            inputStream = new FileInputStream(new File("D://TestClassLoader.class"));
            int len = -1;
            while((len = inputStream.read()) != -1){
                baos.write(len);
            }
            data = baos.toByteArray();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return this.defineClass(classpath,data,0,data.length);
    }

    public static void main(String[] args) {
        MyClassLoader myClassLoader = new MyClassLoader(MyClassLoader.class.getClassLoader(),"MyClassLoader");
        Class clazz = null;
        try {
            // loadClass方法的参数为包名加类名,需要和编译的class中
            // package后面的包一直
            clazz = myClassLoader.loadClass("classloadertest.TestClassLoader");
            Object TestClassLoader = clazz.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

2、打破双亲委派模型的自定义类加载器
BreakParentClassLoader

public class BreakParentClassLoader extends ClassLoader {
    private String name;

    public BreakParentClassLoader(ClassLoader parent,String name){
        super(parent);
        this.name = name;
    }

    @Override
    public String toString() {
        return this.name;
    }

    @Override
    public Class<?> loadClass(String classpath) throws ClassNotFoundException {
        Class<?> clazz = null;
        ClassLoader systemClassLoader = getSystemClassLoader();
        try{
            /**
             * 打破双亲委派模型,需要判断是否是自己的类,因为rt.jar还是
             * 需要启动类加载器加载,还有就是类加载器还有全盘负责机制,即当一个类加载器加载一个类是,这个
             * 类所依赖的、引用的其他所有类都由这个类加载器加载,
             * 除非在程序中显示的指定另一个类加载器加载
              */
            if(!classpath.startsWith("classloadertest")){
                clazz = systemClassLoader.loadClass(classpath);
            }else {
                clazz = findClass(classpath);
            }

        }catch (Exception e){
            e.printStackTrace();
        }
        return clazz;
    }

    @Override
    protected Class<?> findClass(String classpath) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        // D:/idea/webservcie/target/classes/classloadertest/TestClassLoader.class
        try{
            baos = new ByteArrayOutputStream();
            inputStream = new FileInputStream(new File("D:/idea/webservcie/target/classes/classloadertest/TestClassLoader.class"));
            int len = -1;
            while((len = inputStream.read()) != -1){
                baos.write(len);
            }
            data = baos.toByteArray();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return this.defineClass(classpath,data,0,data.length);
    }

    public static void main(String[] args) {
        BreakParentClassLoader breakParentClassLoader = new BreakParentClassLoader(BreakParentClassLoader.class.getClassLoader(),"BreakParentClassLoader");
        Class clazz = null;
//        WebappClassLoader
        try {
            clazz = breakParentClassLoader.loadClass("classloadertest.TestClassLoader");
            Object testClassLoader = clazz.newInstance();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

类加载机制

双亲委派模型
双亲委培模型会在类加载器加载类是首先委托给父类加载器加载,除非父类加载器不能加载才自己加载。这种模型要求除了顶层的启动类加载器,其余的类加载器都应当有自己的父类加载器。这里类加载器之间父子关系一般不会以继承的关系来实现,而是使用组合的关系来复用父类加载器的代码。从上图中类加载器关系图中可以看出,越重要的类加载器就越被JVM载入,这是考虑到安全性,因为先加载的类加载器会充当下一个类加载器的父加载器,在双亲委派模型机制下就能确保安全性 。
双亲委派模型在JDK1.2之后才被引入。

使用这种模型的好处

  1. 保证了java应用所使用的都是同一个版本的java核心库。
  2. 也保证了安全性:设想如果应用程序类加载器想要加载一个有破坏性的外部的java.lang.System类,双亲委派模型会一层层向上委派,最终委派给启动类加载器,而启动类加载器检查到缓存中已经有了这个类,并不会再加载这个有破坏性的System类。包括自定义一个java.lang.Object类是无法使用的。
  3. 在Tomcat中根据实际情况利用类加载器可以提供类库的隔离及共享,保证软件不同级别的逻辑分隔程序不会互相影响,提供更好的安全性。

参考书籍:《Tomcat内核设计剖析》《深入理解java虚拟机》
还有其他大神的博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值