类加载器使用
文章目录
类加载器
引入
一般开发都不会接触classloader的概念,但程序的运行却是建立在classloader之上的
为什么要有类加载器: java程序的启动首先就需要jvm去加载class
而代码中的对象创建类调用【new Person(); Autil.currentTime();】 都需要class被jvm加载
使用类的静态方法,或者创建对象前,需要先加载class
而每个class必然由一个classloader加载
方向
貌似类加载的事情不需要我们操心,虚拟机已经帮我们解决了
那学这个能干嘛:
* 更了解虚拟机;
* 在复杂的生产环境可能遇到class找不到异常,了解这个有助于排查错误;
* 自研服务/插件,动态加载代码;
* tomcat的项目代码热部署,jsp热部署;
* 各种热部署插件;
* 软件破解;
划重点
一个class在内存中只存一份,以及限定条件 defineClass决定class的类加载器是谁 线程上下文类加载器用于隐式传参 一个对象的类由哪个类加载器去加载,取决于显示使用类或显示使用类加载器
类的加载方式有哪些
也可以说类在哪些情况下会被加载
-
以类名方式直接使用时必然会被加载
new A() B.hello()
-
Class.forName
Class.forName("com.mysql.jdbc.Driver") Class.forName("com.mysql.jdbc.Driver", false, myclassloader)
-
直接调用myclassloader.loadClass(“xx.yy.Abc”)
有些人的类加载器例子会这么写myclassloader.findClass(“xx.yy.Abc”),有时会出问题。
|
|
class在JVM中只能加载一份, 但此处cp1与cp2都会被加载,且二者内存地址不同。 Person的class在JVM中分为 姓(某Classloader对象)和名(类全名 fluffy.mo.Person) 此处二者加载的都是fluffy.mo.Person, 但二者姓不同,也就是c1和c2不同,所以冲突。
注意:
类的加载,与类的加载器可能会有所误解。 当一个Person类由classloaderA去加载时,classloaderA可能会通过父类委托给classloaderB。 如果classloaderB成功的加载了Person类,则Person的类加载器其实是classloaderB,而不是classloaderA。判定Person类的类加载器是谁, 并不是依据classloaderA或classloaderB 调用了findClass方法。 而是看谁最终调用defineClass方法, 即调用defineClass方法的classloader才是Person类的类加载器。
关于类加载器的详细介绍 https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html
自定义类加载器-实例
主要步骤
- 继承ClassLoader
- 重写findClass()方法
- 调用defineClass()方法
|
|
自带类加载器与双亲委托
委托机制可实现类共享,保证内存中只出现一次class字节码。
sun.misc.Launcher.ExtClassLoader
sun.misc.Launcher.AppClassLoader
|
|
某个类加载器在收到加载类的请求时,则先将加载类任务委托给父类加载器,层层向上请求。
如果父类加载器可以完成类加载任务,就成功返回;在父类加载器加载失败时,才自己去加载。
AppClassloader的父加载器为ExtClassloader
ExtClassloader的父类加载器为BootstrapClassloader 显示为null (因为BootstrapClassLoader是使用C++写的,没有java类)
委托机制是java程序默认的运行方式,但某些情况下我们的程序会不遵守该规则,比如tomcat加载的web应用。
BootstrapClassloader
该类c语言实现 主要用来加载java的核心jar包
如rt.jar、resources.jar、jce.jar等
ExtClassloader
扩展类加载器 默认加载JAVA_HOME/jre/lib/ext/目下的所有jar包
AppClassloader
为ClassLoader.getSystemClassLoader() 用来加载classpath下的class和jar包
如何自定义类加载器
通常来说是继承ClassLoader,重写findClass
如果不想破坏双亲委派机制,则只需要重写findClass方法
如果想打破双亲委派机制,那么就重写整个loadClass方法
ContextClassLoader
线程的上下文类加载器: 每个线程都会有一个classloader的引用保存在context中
java.lang.Thread有两个方法 getContextClassLoader() 和 setContextClassLoader(ClassLoader cl)
java程序启动时默认的ContextClassLoader为AppClassloader。
当创建新线程时,新线程的ContextClassLoader会取当前线程的ContextClassLoader。
用法为在某一个时刻为当前线程设置 contextClassLoader , 作为一个全局变量方便使用。
然后在必要的时候取出该类加载器使用。
思考
能不能自己写个类叫java.lang.System
写自然可以写,但不会被JVM加载。
按照双亲委托机制,JVM在加载该自定义System类前,会先找父加载器,
最后找到`BootstrapClassloader`, `BootstrapClassloader`由c++编写,用于加载系统类,只会去特定位置加载Java核心库。
这样JVM最终加载的是自带的`java.lang.System`, 而不是自己写的`java.lang.System`。
那么可以自己写一个类加载器,并且绕开背双亲委托机制,直接加载自己的写的类呢。
还是不行,因为自定义类加载器在加载class二进制文件后,还是要调用java.lang.ClassLoader#defineClass方法。
深入源码发现其会调用preDefineClass方法,而java.lang.ClassLoader#preDefineClass方法拒绝加载以`java.`开头的类。
类名.class和getClass()的区别
|
|
以代码举例说明
类实例b1是直接以类名获取,其类加载器和代码所处位置有关, 也就是加载B的class时,会让A类加载器去加载。
类实例b2是通过obj_x. getClass()使用方法是间接获取,obj_x对象的类加载器,其父类加载器可以动态化。
b1是怎么加载的,直接看A的类加载器就行。
b2是怎么加载的,要从对象obj_x向上找。
obj_x如果是new出来的,那也是看A的类加载器。
obj_x如果是cc.newInstance()出来的,那b2和cc相同,找b2就是找cc。
而cc从哪加载的,就要看使用的什么类加载器了。
ContextClassLoader, new, Class.forName, 自定义ClassLoader 之间的关系
|
|
以【Person的对象p1 与 p1的class对象】为例说明 在创建p1对象前,JVM会先加载p1的class,即cp。 而cp一定由某个类加载器加载,此处cdr为cp的类加载器。
p1可以由两种方式被创建 new 和 .newInstance() 。 当采用 new 创建的对象p1,p1的class对象cp的类的加载,会由 而 cp.newInstance() 其实是两步 class对象cp被加载 与使用class对象cp创建对象p1 。 这里只需要关心第一步,class对象cp是如何被加载,就足够了。
cp可以由多种渠道获取。
cp最初一定是从类加载器获取的,但从出厂,到消费者手中,中间的过程可以很曲折。
|
|
在v1版本下,没有显示指定用某个类加载器。
代码写在Test中,则会找Test的类加载器去加载Person的class。
在v2版本下,显示指定用某个ClassLoader,则自然是用我们指定的去加载Person的class。
在v3版本下,对v2版本升级,代码分成两部分。
在某一时刻,将一个myClassloader放在了当前线程的context中。
在必要的时候,从当前线程的context中取出该 classLoader为我们所用
文章作者 duansheli
上次更新 2019-12-25 (325c7b3)