Java类加载机制(二)

类加载器原理

将class文件字节码内容加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据访问的入口

类缓存 标准的JavaSE类记载器可以按照要求查找类,但一旦某个类被加载到类加载器中, 它将维持加载(缓存)一段时间。不过,JVM垃圾回收器可以回收这些Claas对象。

类加载器树状结构、双亲委托机制

类加载器树状结构

  • 引导类加载器    用来加载Java的核心库(JAVA_HOME/jre/lib/rt/jar,或sun.boot.class.path路径下的内容),是用原生的代码(c++)实现的,并不继承java.lang.ClassLoader。加载扩展类加载器和应用程序类加载器。并指定它们的父类加载器。

  • 扩展类记载器     用来加载Java的扩展库(JAVA_HOME/jre/ext/*.jar,或java.ext.dirs路径下的内容)。 Java虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载Java类。

  • 应用程序类加载器    它根据Java应用的类路径(classpath,java.class.path路类)    一般来说,Java应用的类都是由它来完成加载的。由sun.misc.Launcher$AppClassLoader实现。

  • 自定义类加载器    开发人员可以通过继承java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需要。

类加载器图示

public class Demo {
    public static void main(String[] args) {
        //获取应用程序类加载器
        System.out.println(ClassLoader.getSystemClassLoader());
        //获取扩展类加载器
        System.out.println(ClassLoader.getSystemClassLoader().getParent());
        //获取引导类加载器
        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
        //获取classpath()
        //System.out.println(System.getProperty("java.class.path"));
    }
}
//结果
sun.misc.Launcher$AppClassLoader@2503dbd3
sun.misc.Launcher$ExtClassLoader@511d50c0
null
public class FileSystemClassLoader extends ClassLoader {
    String rootDir;

    public FileSystemClassLoader(String rootDir) {
        this.rootDir = rootDir;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class c = findLoadedClass(name);
        if (c != null) {
            return c;
        } else {
            ClassLoader parent = this.getParent();
            try {
                c = parent.loadClass(name);
            }catch (Exception e){
                e.printStackTrace();
            }
            if (c != null) {
                return c;
            } else {
                byte[] classData = getClassData(name);
                if (classData == null) {
                    throw new ClassNotFoundException("自定义类加载器没有加载到");
                } else {
                    c = defineClass(name, classData, 0, classData.length);
                }
            }
        }
        return c;
    }

    private byte[] getClassData(String className) {

        String path = rootDir + "/" + className.replace(".", "/") + ".class";
        InputStream is = null;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try {
            is = new FileInputStream(path);

            byte[] buffer = new byte[1024];
            int temp = 0;
            while ((temp = is.read(buffer)) != -1) {
                baos.write(buffer,0,temp);
            }

            return baos.toByteArray();
        } catch (Exception e) {
            return null;
        } finally {
            if(is != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(baos != null){
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }

}


public class TestMyClassLoader {
    public static void main(String[] args) throws ClassNotFoundException {
        FileSystemClassLoader loader = new FileSystemClassLoader("/Users/wjk/Desktop");
        FileSystemClassLoader loader1 = new FileSystemClassLoader("/Users/wjk/Desktop");
        Class c = loader.loadClass("com.Hello");
        Class c1 = loader1.loadClass("com.Hello");
        Class c2 = loader.loadClass("com.Hello");
        Class c3 = loader.loadClass("java.lang.String");


        System.out.println(c.hashCode());//被两个类加载器加载的同一个类,JVM认为是不同的(c和c1的hashCode值不一样)
        System.out.println(c1.hashCode());
        System.out.println(c2.hashCode());


        System.out.println(c.getClassLoader());//使用的是自定义的类加载器
        System.out.println(c3.getClassLoader());//使用的是引导类加载器
    }
}

//结果
1725154839
1670675563
1725154839
classLoaderTest.FileSystemClassLoader@5e2de80c
null

双亲委托机制

  • 代理模式:交给其他类加载器加载指定的类

  • 双亲委托机制    

    (1)就是当某个特定的类加载器接到加载类的请求的时候,首先委托给其父类(父类如果有父类一直向上追溯),直到父类加载器无法加载时,该加载器进行加载。

    (2)双亲委托机制是为了保证Java核心库的类型安全。

            这种机制保证不会加载到用户自定义的java.lang.Class类的情况 (3)类加载器除了用于加载类,也是安全最基本的屏障。

双亲委托机制是代理模式的一种,但是并不是所有的类加载都是双亲委托机制,比如tomcat类加载器首先尝试特定的 类加载器,加载不到类时在尝试器父类加载器。

自定义类加载器

如何实现自定义类加载器:
(1)继承java.lang.ClassLoader
(2)检查所请求的类型是否已经被这个类加载器加载到命名空间,如果已经被加载直接返回。
(3)委派给父类加载(也可以不委派,这个程序控制)。
(4)调用自定义加载器findClass()方法获取字节码,然后调用defineClass()导入类型到方法区。

线程上下文类加载器

双亲委派机制以及类加载器的问题

(1)一般情况下,保证一个类中所关联的其他类都是由当前类加载器所加载的。
例如:ClassA本身在扩展类加载器下找到,那么它里面new出来的一些也就只能用扩展类加载器查找(
不会低一个级别),所以有的应用程序类加载器可以找到,却没有找到。
(2)JDBC API。它有实现driven的部分(mysql/sql server),我们的JDBC API都是有引导类加载器
或者扩展类加载器载入的,但是JDBC drive却是由扩展类记载器或者应用程序类加载器载入,那么就有可能找不到drive中,在Java领域,其实只要分成Api+SPI(service provice interface特定厂商提供)的,都会遇到这个问题。简而言之:接口定义在Java本身,实现却在第三方,Java本本身使用引导类加载器或者扩展类加载器载入,而第三方实现使用扩展类加载器或者应用程序类记载器加载。

SPI接口是Java核心库的一部分,是由引导类架子器加载的;SPI实现的Java类一般是由应用程序类加载器加载的。引导类加载器是无法找到SPI的实现类的,因为它只加载Java的核心库。

动态加载资源的时候必须需要的加载器

(1)系统类加载器
(2)当前类加载器
(3)当前线程类加载器

线程类加载器

线程类加载器是为了抛弃双亲委托加载链式模式。
每一个线程都有一个关联上下文类加载器,如果用new Thread()方式生成
新的线程,新线程将继续继承其父线程的上下文类加载器。如果程序对线程上下文
类加载器没有任何变动的话,程序中所有的线程将都使用系统类加载器(即:应用程序类
加载器)作为上下文类加载器。
public class Demo3 {
    public static void main(String[] args) {
        //获得Demo类的类加载器
        ClassLoader loader1 = Demo.class.getClassLoader();
        System.out.println(loader1);
        //获得当前线程类加载器
        ClassLoader loader2 = Thread.currentThread().getContextClassLoader();
        System.out.println(loader2);

        //使用自定义类加载器
        Thread.currentThread().setContextClassLoader(new FileSystemClassLoader("/Users/wjk/Desktop"));
        //获得当前线程类加载器
        System.out.println(Thread.currentThread().getContextClassLoader());
    }
}
//结果
sun.misc.Launcher$AppClassLoader@2503dbd3
sun.misc.Launcher$AppClassLoader@2503dbd3
classLoaderTest.FileSystemClassLoader@60e53b93

服务器类加载器原理和OSGI介绍

服务器类加载器

(1)一切为了安全,TOMCAT不能使用系统默认的类加载器。
原因:
    如果TOMCAT跑你的web项目的时候使用系统默认的类加载器,

    你可以直接肆无忌惮的操作系统的各个目录,这是相当危险的。对于Java EE容器中的Web应用来说,类加载器的实现
与一般Java应用有所不同。每一个Web应用都一个对应的类加载器,它先尝试加载某个类,加载不了再委托给父类加载器,

(2)为了安全TOMCAT需要实现自己的类加载器
    我可以限制你把类写在指定的位置,否则我不给你加载。

OSGI介绍

  • OSGI(Open Service Gateway Initative)是面向Java的动态模块系统。它为开发人员提供了 面向服务和基础组件的运行环境,并提供标准的方式用来管理软件的生命周期。

  • Eclipse就是基于OSGI技术构建的。

原理: OSGI中的每个模块都包含Java包和类。模块可以声明它所依赖的需要导入的其他模块和Java包和 类,也可以声明自己导出的包和类,供其他模块来使用。也就是说能够隐藏和共享某些Java包和类。 这个是通过OSGI特有的类加载器机制来实现的。OSIG中每个模块都有对应一个类加载器,它负责加载模块 自己包含的Java包和类。当它需要加载Java核心库(java开头的包和类)的类时,它要代理给父类加载器来完成。当它需要加载导入的Java类的时候,它会代理给导出此Java类的模块来完成加载,模块也可以显式声明某些类和包必须由父类加载器加载。


转载于:https://my.oschina.net/u/2361475/blog/603798

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值