Java语言是一种具有动态性的解释型语言,类只有被加载到JVM中才能运行。JVM会将编译生成的.class文件按需求和一定的规则加载到内存,并组织成一个完整的Java应用程序,这个过程由加载器来完成(ClassLoader及其子类),类加载器也是一个类,其实质是把类文件从硬盘读取到内存中。
类加载分为隐式装载(new的方式)和显式装载(class.forName);
程序启动时只把需要的类加载到JVM,其他的在被使用的时候才会被加载,采用这种方式一方面可以加快加载速度,另一方面可以节约程序运行过程中对内存的开销。Java中每个类和接口都对应一个.class文件,这些文件可被看作可以被动态加载的单元,因此当只有部分类被修改时,只需要重新编译变化的类即可,而不需要编译所有的文件;
Java类的加载是动态的,类可分为三类:系统类、扩展类和自定义类。Java针对这三种提供了三种类型的加载器:
用户还可以自定义类加载器,除了顶层的启动类加载器外,其他的类加载器由自己的父类加载,这种父子关系是通过组合关系来实现而非继承。
当有类需要被加载时,类加载器会请求父类来完成加载工作,父类会根据自己的搜索路径来搜索需要被载入的类,如果搜索不到那么子类就按照自己的搜索路径来搜索待加载的类。
类加载的主要3个步骤:
- 检查:检查加载的class文件的正确性;
- 准备:给类中的静态变量分配空间;
- 解析:将符合引用转换为直接引用(可选的)。
两个类相等必须满足的条件:
双亲委派模型的系统实现原理
Java默认使用的类加载模型就是双亲委派模型,但是对于Java提供的很多服务提供者接口(Service Provider Interface,SPI)在Java类库中只定义了接口,允许第三方来实现这些接口(JDBC,JCE,JNDI,JAXP,JBI等)。以JDBC为例,它是Java语言提供的一组来执行SQL语句的接口,由两部分组成:Java类库提供的接口和数据库厂商提供的具体实现类(通常称为driver)。接口和具体实现应该由同一个类加载器进行加载,否则就会造成接口找不到具体的实现。
这种情况如果使用双亲委派模式进行加载,那么SPI接口是Java核心库的一部分,是由启动类加载器Bootstrap Loader来加载,但SPI的具体实现类只能由应用程序加载器AppClassLoader来加载。显然启动类加载器无法找到SPI的实现类,因为它只负责加载核心库,同时也不能代理给应用程序类加载器,因为它是应用程序类加载器的父类,在这种情况下双亲委派模型不能很好的工作了。
在线程上下文类加载器中可以自定义类加载器,并且指定这种类加载器不使用双亲委托机制,从而可以实现接口和实现类使用相同的类加载器。
Thread类中提供了getContextClassLoader()和setContextClassLoader(ClassLoader cl)两个方法用来获取和设置上下文类加载器,如果没有set那么线程将继承父线程的上下文类加载器,如果整个环境都没有设置,就会使用默认的应用程序类加载器(Application ClassLoader),对于SPI接口和实现类可以通过这种方式而不使用双亲委派模式。
原文:https://www.cnblogs.com/z-dk/p/14729129.html