一。
JVM设计者把类加载阶段中的“通过'类全名'来获取定义此类的
二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
1.类与类加载器
对于任何一个类,都需要由加载它的类加载器和这个类来确立其在JVM中的唯一性。也就是说,两个类来源于同一个Class文件,并且被同一个类加载器加载,这两个类才相等。
2.双亲委派模型
从虚拟机的角度来说,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),该类加载器使用
C++语言实现,属于虚拟机自身的一部分。另外一种就是所有其它的类加载器,这些类加载器是由Java语言实现,独立于JVM外部,并且全部继承自抽象类java.lang.ClassLoader。
“双亲委派”机制只是Java推荐的机制,并不是强制的机制。
我们可以继承java.lang.ClassLoader类,实现自己的类加载器。如果想保持双亲委派模型,就应该重写findClass(name)方法;如果想破坏双亲委派模型,可以重写loadClass(name)方法。具体细节建议看下源码
————————————————
版权声明:本文为
CSDN博主「大魔王King」的
原创文章,遵循CC 4.0 BY-SA版权
协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_39222112/article/details/81316511
二。破坏双亲委派模型
假如我们自己写了一个java.lang.String的类,我们是否可以替换调JDK本身的类?
答案是否定的。我们不能实现。为什么呢?我看很多网上解释是说双亲委托机制解决这个问题,其实不是非常的准确。因为双亲委托机制是可以打破的,你完全可以自己写一个
classLoader来加载自己写的java.lang.String类,但是你会
发现也不会加载成功,具体就是因为针对java.*开头的类,jvm的实现中已经保证了必须由bootstrp来加载。
不遵循“双亲委托机制”的场景
上面说了双亲委托机制主要是为了实现不同的ClassLoader之间加载的类的交互问题,被大家公用的类就交由父加载器去加载,但是Java中确实也存在父类加载器加载的类需要用到子加载器加载的类的情况。下面我们就来说说这种情况的发生。
Java中有一个SPI(
Service Provider Interface)标准,使用了SPI的库,比如JDBC,JNDI等,我们都知道JDBC需要第三方提供的驱动才可以,而驱动的jar包是放在我们应 用程序本身的classpath的,而jdbc 本身的api是jdk提供的一部分,它已经被bootstrp加载了,那第三方厂商提供的实现类怎么加载呢?这里面JAVA引入了
线程上下文类加载的概念,线程类加载器默认会从父线程继承,如果没有指定的话,默认就是系统类加载器(AppClassLoader),这样的话当加载第三方驱动的时候,就可 以通过线程的上下文类加载器来加载。
另外为了实现更灵活的类加载器OSGI以及一些Java appserver也打破了双亲委托机制。
JVM自带的ClassLoader类
JDK中提供了三个ClassLoader,根据层级从高到低为:
Bootstrap ClassLoader,主要加载JVM自身工作需要的类。
Extension ClassLoader,主要加载%JAVA_HOME%\lib\ext目录下的库类。
Application ClassLoader,主要加载Classpath指定的库类,一般情况下这是程序中的默认类加载器,也是ClassLoader.getSystemClassLoader() 的返回值。(这里的Classpath默认指的是环境变量中配置的Classpath,但是可以在执行Java命令的时候使用-cp 参数来修改当前程序使用的Classpath)
什么是双亲委托模型
JVM加载类的实现方式,我们称为 双亲委托模型:
如果一个类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委托给自己的父加载器,每一层的类加载器都是如此,因此所有的类加载请求最终都应该传送到顶层的Bootstrap ClassLoader中,只有当父加载器反馈自己无法完成加载请求时,子加载器才会尝试自己加载。
双亲委托模型加载类的过程
我们用一张简单的图片来描述 “使用
自定义ClassLoader加载一个类的过程”:
自定义ClassLoader向自己的上层(Application ClassLoader)请求
Application ClassLoader继续向上层(Extension ClassLoader)请求
Extension ClassLoader继续向上层(Bootstrap ClassLoader)请求
Bootstrap ClassLoader是最上层类加载器,所以它尝试在自己的路径中查找要加载类,如果查找到了就加载类,否则向下层(Extension ClassLoader)反馈自己无法加载类。
Extension ClassLoader从自己的路径中寻找要加载类,找到则加载,找不到则继续向下返回。
Application ClassLoader从自己的路径中寻找要加载类,找到则加载,找不到则继续向下返回。
自定义ClassLoader从自己的路径中寻找要加载类,找到则加载。由于类加载请求是自定义ClassLoader发起的,所以当它自己也找不到要加载的类时会终止加载并抛出
ClassNotFoundException。
为什么要用双亲委托模型
双亲委托模型的重要用途是为了解决类载入过程中的
安全性问题。
假设有一个开发者自己编写了一个名为java.lang.Object的类,想借此欺骗JVM。现在他要使用自定义ClassLoader来加载自己编写的java.lang.Object类。然而幸运的是,双亲委托模型不会让他成功。因为JVM会优先在Bootstrap ClassLoader的路径下找到java.lang.Object类,并载入它。
Java的类加载是否一定遵循双亲委托模型
这个答案是否定的。
双亲委托模型只是JDK提供的ClassLoader类的实现方式。
在实际开发中,我们可以通过自定义ClassLoader,并重写父类的loadClass方法,来打破这一机制。
---------------------
作者:markzy
来源:CSDN
原文:https://blog.csdn.net/markzy/article/details/53192993
版权声明:本文为博主原创文章,转载请附上博文链接!
总结:
1. $java_home/lib 目录下的java核心api
2. $java_home/lib/ext 目录下的java扩展jar包
3. java -classpath/-Djava.class.path所指的目录下的类与jar包
4. $CATALINA_HOME/common目录下按照文件夹的顺序从上往下依次加载
5. $CATALINA_HOME/server目录下按照文件夹的顺序从上往下依次加载
6. $CATALINA_BASE/shared目录下按照文件夹的顺序从上往下依次加载
7. 我们的项目路径/WEB-INF/classes下的class文件
8. 我们的项目路径/WEB-INF/lib下的jar文件
在同一个文件夹下,jar包是按顺序从上到下依次加载
附:
JDK默认ClassLoader
JDK 默认提供了如下几种ClassLoader
1. Bootstrp loader
Bootstrp加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib,-Xbootclasspath参数指定的路径以及%JAVA_HOME%/jre/classes中的类。
1. ExtClassLoader
Bootstrp loader加载ExtClassLoader,并且将ExtClassLoader的父加载器设置为Bootstrploader.ExtClassLoader是用Java写的,具体来说就是 sun.misc.Launcher$ExtClassLoader,ExtClassLoader主要加载%JAVA_HOME%/jre/lib/ext,此路径下的所有classes目录以及java.ext.dirs系统变量指定的路径中类库。
2. AppClassLoader
Bootstrp loader加载完ExtClassLoader后,就会加载AppClassLoader,并且将AppClassLoader的父加载器指定为 ExtClassLoader。AppClassLoader也是用Java写成的,它的实现类是sun.misc.Launcher$AppClassLoader,另外我们知道ClassLoader中有个getSystemClassLoader方法,此方法返回的正是AppclassLoader.AppClassLoader主要负责加载classpath所指定的位置的类或者是jar文档,它也是Java程序默认的类加载器。
双亲委托模型
Java中ClassLoader的加载采用了双亲委托机制,采用双亲委托机制加载类的时候采用如下的几个步骤:
1. 当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。
每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
2. 当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader.
3. 当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。
说到这里大家可能会想,Java为什么要采用这样的委托机制?
理解这个问题,我们引入另外一个关于Classloader的概念“命名空间”, 它是指要确定某一个类,需要类的全限定名以及加载此类的ClassLoader来共同确定。也就是说即使两个类的全限定名是相同的,但是因为不同的ClassLoader加载了此类,那么在JVM中它是不同的类。明白了命名空间以后,我们再来看看委托模型。采用了委托模型以后加大了不同的 ClassLoader的交互能力,比如上面说的,我们JDK本生提供的类库,比如
hashmap,linkedlist等等,这些类由bootstrp 类加载器加载了以后,无论你程序中有多少个类加载器,那么这些类其实都是可以共享的,这样就避免了不同的类加载器加载了同样名字的不同类以后造成混乱。
如何自定义ClassLoader
Java除了上面所说的默认提供的classloader以外,它还容许应用程序可以自定义classloader,那么要想自定义classloader我们需要通过继承java.lang.ClassLoader来实现,接下来我们就来看看再自定义Classloader的时候,我们需要注意的几个重要的方法:
1.loadClass 方法
loadClass method declare
public Class<?> loadClass(String name) throws ClassNotFoundException
上面是loadClass方法的原型声明,上面所说的双亲委托机制的实现其实就实在此方法中实现的。下面我们就来看看此方法的代码来看看它到底如何实现双亲委托的。
loadClass method implement
public Class<?> loadClass(String name) throws ClassNotFoundException
{
return loadClass(name, false);
}
从上面可以看出loadClass方法调用了loadcClass(name,false)方法,那么接下来我们再来看看另外一个loadClass方法的实现。
Class loadClass(String name, boolean resolve)
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{ // First, check if the class has already been loaded Class c = findLoadedClass(name);
//检查class是否已经被加载过了 if (c == null)
{
try {
if (parent != null) {
c = parent.loadClass(name, false); //如果没有被加载,且指定了父类加载器,则委托父加载器加载。
} else {
c = findBootstrapClass0(name);//如果没有父类加载器,则委托bootstrap加载器加载 }
} catch (ClassNotFoundException e) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);//如果父类加载没有加载到,则通过自己的findClass来加载。 }
}
if (resolve)
{
resolveClass(c);
}
return c;
}
上面的代码,我加了
注释通过注释可以清晰看出loadClass的双亲委托机制是如何工作的。 这里我们需要注意一点就是public Class<?>loadClass(String name) throws ClassNotFoundException没有被标记为final,也就意味着我们是可以override这个方法的,也就是说双亲委托机制是可以打破的。另外上面注意到有个findClass方法,接下来我们就来说说这个方法到底是搞末子的。
2.findClass
我们查看java.lang.ClassLoader的源代码,我们发现findClass的实现如下:
protected Class<?> findClass(String name) throws ClassNotFoundException
{
throw new ClassNotFoundException(name);
}
我们可以看出此方法默认的实现是直接抛出
异常,其实这个方法就是留给我们应用程序来override的。那么具体的实现就看你的实现逻辑了,你可以从磁盘读取,也可以从网络上获取class文件的字节流,获取class二进制了以后就可以交给defineClass来实现进一步的加载。defineClass我们再下面再来描述。ok,通过上面的分析,我们可以得出如下结论:
我们在写自己的ClassLoader的时候,如果想遵循双亲委托机制,则只需要overridefindClass.
3.defineClass
我们首先还是来看看defineClass的源码:
defineClass
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(name, b, off, len, null);
}
从上面的代码我们看出此方法被定义为了final,这也就意味着此方法不能被Override,其实这也是jvm留给我们的唯一的入口,通过这个唯 一的入口,jvm保证了类文件必须符合Java虚拟机规范规定的类的定义。此方法最后会调用native的方法来实现真正的类的加载工作。
Ok,通过上面的描述,我们来思考下面一个问题:
假如我们自己写了一个java.lang.String的类,我们是否可以替换调JDK本身的类?
答案是否定的。我们不能实现。为什么呢?我看很多网上解释是说双亲委托机制解决这个问题,其实不是非常的准确。因为双亲委托机制是可以打破的,你完全可以自己写一个classLoader来加载自己写的java.lang.String类,但是你会发现也不会加载成功,具体就是因为针对java.*开头的类,jvm的实现中已经保证了必须由bootstrp来加载。
不遵循“双亲委托机制”的场景
上面说了双亲委托机制主要是为了实现不同的ClassLoader之间加载的类的交互问题,被大家公用的类就交由父加载器去加载,但是Java中确实也存在父类加载器加载的类需要用到子加载器加载的类的情况。下面我们就来说说这种情况的发生。
Java中有一个SPI(Service Provider Interface)标准,使用了SPI的库,比如JDBC,JNDI等,我们都知道JDBC需要第三方提供的驱动才可以,而驱动的jar包是放在我们应 用程序本身的classpath的,而jdbc 本身的api是jdk提供的一部分,它已经被bootstrp加载了,那第三方厂商提供的实现类怎么加载呢?这里面JAVA引入了线程上下文类加载的概念,线程类加载器默认会从父线程继承,如果没有指定的话,默认就是系统类加载器(AppClassLoader),这样的话当加载第三方驱动的时候,就可 以通过线程的上下文类加载器来加载。
另外为了实现更灵活的类加载器OSGI以及一些Java appserver也打破了双亲委托机制。
---------------------
作者:低调的洋仔
来源:CSDN
原文:https://blog.csdn.net/wangyang1354/article/details/49448007
版权声明:本文为博主原创文章,转载请附上博文链接!