暂时看不懂这两张图没关系,跟着老哥
往下看
类的生命周期包括:加载、链接、初始化、使用和卸载,其中加载
、链接
、初始化
,属于类加载的过程
,我们下面仔细讲解。使用是指我们new对象进行使用,卸载指对象被垃圾回收掉了。
这个类加载器使用C/C++语言实现的,嵌套在JVM内部,java程序无法直接操作这个类。 它用来加载Java核心类库,如:
JAVA_HOME/jre/lib/rt.jar
、resources.jar
、sun.boot.class.path
路径下的包,用于提供jvm运行所需的包。 并不是继承自java.lang.ClassLoader,它没有父类加载器 它加载扩展类加载器
和应用程序类加载器
,并成为他们的父类加载器 出于安全考虑,启动类只加载包名为:java、javax、sun开头的类
Java语言编写,由
sun.misc.Launcher$ExtClassLoader
实现,我们可以用Java程序操作这个加载器 派生继承自java.lang.ClassLoader,父类加载器为启动类加载器
从系统属性:java.ext.dirs
目录中加载类库,或者从JDK安装目录:jre/lib/ext
目录下加载类库。我们就可以将我们自己的包放在以上目录下,就会自动加载进来了。
Java语言编写,由
sun.misc.Launcher$AppClassLoader
实现。 派生继承自java.lang.ClassLoader,父类加载器为启动类加载器
它负责加载环境变量classpath
或者系统属性java.class.path
指定路径下的类库 它是程序中默认的类加载器,我们Java程序中的类,都是由它加载完成的。 我们可以通过ClassLoader#getSystemClassLoader()
获取并操作这个加载器
一般情况下,以上3种加载器能满足我们日常的开发工作,不满足时,我们还可以
自定义加载器
比如用网络加载Java类,为了保证传输中的安全性,采用了加密操作,那么以上3种加载器就无法加载这个类,这时候就需要自定义加载器
自定义加载器实现步骤
继承
java.lang.ClassLoader
类,重写findClass()方法 如果没有太复杂的需求,可以直接继承URLClassLoader
类,重写loadClass
方法,具体可参考AppClassLoader
和ExtClassLoader
。
获取ClassLoader几种方式
它是一个抽象类,其后所有的类加载器继承自 ClassLoader(不包括启动类加载器)
// 方式一:获取当前类的 ClassLoader
clazz.getClassLoader()
// 方式二:获取当前线程上下文的 ClassLoader
Thread.currentThread().getContextClassLoader()
// 方式三:获取系统的 ClassLoader
ClassLoader.getSystemClassLoader()
// 方式四:获取调用者的 ClassLoader
DriverManager.getCallerClassLoader()
jvm对class文件采用的是按需加载的方式,当需要使用该类时,jvm才会将它的class文件加载到内存中产生class对象。
在加载类的时候,是采用的双亲委派机制
,即把请求交给父类处理的一种任务委派模式。
(1)如果一个类加载器
接收到了类加载
的请求,它自己不会先去加载,会把这个请求委托给父类加载器
去执行。
(2)如果父类还存在父类加载器,则继续向上委托,一直委托到启动类加载器:Bootstrap ClassLoader
(3)如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载失败,就由子类自己去尝试加载,如果子类加载失败就会抛出ClassNotFoundException
异常,这就是双亲委派模式
在Java应用中存在着很多服务提供者接口(Service Provider Interface,SPI),这些接口允许第三方为它们提供实现,如常见的 SPI 有 JDBC、JNDI等,这些 SPI 的接口属于 Java 核心库,一般存在rt.jar包中,由Bootstrap类加载器加载。
而Bootstrap类加载器无法直接加载SPI的实现类,同时由于双亲委派模式的存在,Bootstrap类加载器也无法反向委托AppClassLoader加载器SPI的实现类。
在这种情况下,我们就需要一种特殊的类加载器来加载第三方的类库,而线程上下文类加载器(双亲委派模型的破坏者)就是很好的选择。
从图可知rt.jar核心包是有Bootstrap类加载器加载的,其内包含SPI核心接口类,由于SPI中的类经常需要调用外部实现类的方法,而jdbc.jar包含外部实现类(jdbc.jar存在于classpath路径)无法通过Bootstrap类加载器加载,因此只能委派线程上下文类加载器把jdbc.jar中的实现类加载到内存以便SPI相关类使用。
显然这种线程上下文类加载器的加载方式破坏了“双亲委派模型”,它在执行过程中抛弃双亲委派加载链模式,使程序可以逆向使用类加载器,当然这也使得Java类加载器变得更加灵活。
自定义 String 类,但是在加载自定义 String 类的时候会率先使用引导类加载器加载,而引导类加载器在加载的过程中会先加载 JDK 自带的文件(rt.jar 包中的 javalangString.class),报错信息说没有 main 方法就是因为加载的 rt.jar 包中的 String 类。这样可以保证对 Java 核心源代码的保护,这就是沙箱安全机制。
3 破坏双亲委派机制的场景
既然Java中引入了双亲委派机制,为什么要破坏它呢?
答:因为它有一些缺点。
下面给大家列举一下,破坏双亲委派机制最常见的场景。
JNDI是Java中的标准服务,它的代码由启动类加载器去加载。
但JNDI要对资源进行集中管理和查找,它需要调用由独立厂商在应用程序的ClassPath下的实现了JNDI接口的代码,但启动类加载器不可能“认识”这些外部代码。
为了解决这个问题,Java后来引入了线程上下文类加载器
(Thread Context ClassLoader)。
这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置。
如果创建线程时没有设置,他将会从父线程中继承一个,如果在应用程序的全局范围内都没有设置过的话,那这个类加载器默认就是应用程序类加载器。
有了线程上下文加载器,JNDI服务就可以使用它去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载的动作,这样就打破了双亲委派机制。
原生的JDBC
中Driver驱动本身只是一个接口,并没有具体的实现,具体的实现是由不同数据库类型去实现的。
例如,MySQL的mysql-connector.jar中的Driver类具体实现的。
原生的JDBC中的类是放在rt.jar包,是由启动类加载器进行类加载的。
在JDBC中需要动态去加载不同数据库类型的Driver实现类,而mysql-connector.jar中的Driver实现类是用户自己写的代码,启动类加载器肯定是不能加载的,那就需要由应用程序启动类去进行类加载。
为了解决这个问题,也可以使用线程上下文类加载器
(Thread Context ClassLoader)。
Tomcat是Servlet容器,它负责加载Servlet相关的jar包。
此外,Tomcat本身也是Java程序,也需要加载自身的类和一些依赖jar包。
这样就会带来下面的问题:
这些原因导致,Tomcat没有办法使用传统的双亲委派机制加载类了。
那么,Tomcat加载类的机制是怎么样的?
由于用户对程序动态性的追求,比如:代码热部署、代码热替换等功能,引入了OSGi(Open Service Gateway Initiative)。
OSGi中的每一个模块(称为Bundle)。
当程序升级或者更新时,可以只停用、重新安装然后启动程序的其中一部分,对企业来说这是一个非常诱人的功能。
OSGi的Bundle类加载器之间只有规则,没有固定的委派关系。
各个Bundle加载器是平级关系。
不是双亲委派关系。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。