JVM的艺术之什么是类加载器

发布时间:2021-10-25 17:35:00 作者:iii
来源:亿速云 阅读:174

这篇文章主要介绍“JVM的艺术之什么是类加载器”,在日常操作中,相信很多人在JVM的艺术之什么是类加载器问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”JVM的艺术之什么是类加载器”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

什么是定义类加载器和初始化类加载器?

什么是类加载器的双亲委派模型?

上篇文章我们提到了类加载器的双亲委派模型,也可以称为双亲委托模型。今天这篇文章我们就来把这个概念给讲明白。

概念:用一种简单的方式去描述双亲委托的概念。可以分为两个部分去理解

1委托:

jvm加载类的时候是通过双亲委派的方式去加载,自下而上的去委托。

自定义类加载器需要加载类时,先委托应用类加载器去加载,然后应用类加载器又向扩展类加载器去委托,扩展类加载器在向启动类加载器去委托。

如果启动类加载器不能加载该类。那么就向下加载

2加载:

jvm加载类的时候是通过双亲委派的方式去加载委托,但是加载的时候是由上向下去加载的,当委托到最顶层启动类加载器的时候,无法在向上委托,那么

启动类加载器就开始尝试去加载这个类,启动类加载器加载不了就向下交给扩展类加载器去加载,扩展类加载器加载不了就继续向下委托交给应用类加载器

去加载,以此类推。

如果文字描述你还不清楚什么是双亲委托机制,那么我画了一幅图可以更清楚类加载的过程。如下:

JVM的艺术之什么是类加载器

通过上图,我们知道更能清楚的知道,双亲委托模型的工作机制,用一句简单的话说,就是需要加载一个类的时候,向上委托,向下加载。

注意:在双亲委派机制中,各个加载器按照父子关系形成树型结构,除了根加载器以外,每一个加载器有且只有一个父加载器。

接下来,我也从jdk底层源码的角度给大家画了一张类加载的主要过程,图如下:

JVM的艺术之什么是类加载器

以上就是类加载器加载一个类的重要过程步骤。希望各位小伙儿可以结合源码的方式,仔细再研究一下。其实还挺好理解的。

下面咱们再说说,java采用双亲委托的方式去加载类,这样做的好处是什么呢?

什么是全盘委托加载?

解释:假如我们的Person类是由我们的系统类APP类加载器加载的,而person类所依赖的Dog类也会委托给App系统类进 行加载,这个委托过程也遵循双亲委派模型。代码如下

person类代码中创建Dog实例

public class Person {

  public Person(){
  
      new Dog();
  }

}

public class Dog {

    public Dog(){
        System.out.println("Dog 的构造函数");
    }
}

下面我们再看一个例子。我们把类路径下的Person.class文件删除掉,然后再运行一下上面的main函数,看看结果。代码如下:

JVM的艺术之什么是类加载器

通过那行结果我们看出,Person类是由我们的自定义类加载器加载的。那为什么Dog类没有进行全盘委托的,这是因为双亲委托模型的缘故,我们的类路径下并没有Person类,故此AppClassLoader是无法加载我们的路径I:\\test\\下的com.test.Person.class文件的。所以Person类是由我们自定的类加载器加载的。再看Dog类,由于它的加载要遵循双亲委托模型,因为类路径下有Dog.class文件,所以AppClassLoader就可以加载Dog类。故此加载Dog类的ClassLoader是AppClassLoader。写到这里,大家对类加载已经有了一个非常深刻的理解。那么java为什么使用双亲委托模型的好处我相信已经不言而喻了。那么下面来说说双亲委托模型,有没有他的弊端呢,或者说有什么不好的地方嘛?我们可以打破这种双亲委托的方式去加载类嘛?下面我们来看一个例子。

类加载器的命名空间

说到双亲委托模型的弊端,那我就离不开命名空间的概念。

类加载器的命名空间 是由类加载器本身以及所有父加载器所加载出来的binary name(full class name)组成.

①:在同一个命名空间里,不允许出现二个完全一样的binary name。

②:在不同的命名空间中,可以出现二个相同的binary name。当然二者对应的Class对象是相互不能感知到的,也就是说Class对象的类型是不一样的。

解释:同一个Person.class文件 被我们的不同的类加载器去加载,那么我们的jvm内存中会生成二个对应的Person的Class对象,而且这二个对应的Class对象是相互不可见的(通过Class对象反射创建的实例对象相互是不能够兼容的不能相互转型**

③:子加载器的命名空间中的binary name对应的类中可以访问 父加载器命名空间中binary name对应的类,反之不行

下面准备了一张图,以便于大家的理解。

JVM的艺术之什么是类加载器

上面这张图就很好的解释了命名空间的概念。大家可以再好好的体会一下。

我们光画图,光用嘴说并不是一种很有力的证据,就如同我写在这篇博文的时候所提,我们在学习和掌握某个概念的时候,就必须要拿出有力的证据,来证明自己的猜想或者是观点,那我们就举一个例子。来验证一下我们上面的理论是否正确。代码如下:

这是Person类的代码。

package com.test;public class Person {

    public Person() {
        new Dog();
        System.out.println("Dog的classLoader:-->"+ Dog.class.getClassLoader());
    }

    static{
        System.out.println("person类被初始化了");
    }
}

这是Dog类的代码。

package com.test;public class Dog {

    public Dog(){
        System.out.println("Dog 的构造函数");
    }
}

具体的验证思路是这样的,首先我们把Person类的Class文件放到启动类加载器的加载目录下(C:\Program Files\Java\jdk1.8.0_144\jre\classes 这是启动类加载器的加载目录)来达到Person类交给启动类加载器加载的目的。

然后呢,我们让Dog类去被AppClassLoader(系统类加载器去加载)。然后我们在Person类中去访问Dog类。看看能否访问成功。

测试环境:把我们的Person.class放置在C:\Program Files\Java\jdk1.8.0_131\jre\classes这个目录下,那么我们的Person.class就会被我们的启动类加载器加载,而我们的Dog类是被AppClassLoader进行加载,我们的Person类 中引用我们的Dog类会抛出异常.

创建main方法进行测试:

package com.test;import java.lang.reflect.Method;/**
 * jvm 类加载器 第一章
 * @author 奇客时间-时光
 * 自定义类加载器——命名空间
 * 测试父加载所加载的类,不能访问子加载器所加载的类。
 */public class MainClass02 {

    public static void main(String[] args) throws Exception {

        System.out.println("Person的类加载器:"+Person.class.getClassLoader());

        System.out.println("Dog的类加载器:"+Dog.class.getClassLoader());

        Class<?> clazz = Person.class;
        clazz.newInstance();


    }
}

运行结果:
    "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=59226:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass02
Person的类加载器:nullDog的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
person类被初始化了
Exception in thread "main" java.lang.NoClassDefFoundError: com/test/Dog
 at com.test.Person.<init>(Person.java:7)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
 at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
 at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
 at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
 at java.lang.Class.newInstance(Class.java:442)
 at com.test.MainClass02.main(MainClass02.java:20)

Process finished with exit code 1

JVM的艺术之什么是类加载器

总结:通过上面的代码我们就可以看出来,我们在Person中去new一个Dog的实例的时候,并没有创建成功,而是抛出了Exception in thread "main" java.lang.NoClassDefFoundError: com/test/Dog这样的异常,这也就证明了,我们上面所说的结论(父加载器所加载的类,不能访问子加载所加载的类。)

即启动类加载器所加载的类,不能访问系统类加载器所加载的类(AppClassLoader)。

那么肯定会有人问,我们的子加载器所加载的类,可以访问父加载器所加载的类嘛?我们不妨来证实一下,我们只需要改动一下MainClass02这个类的代码即可,让AppClassLoader去加载Dog类,让我们的自定义类加载器去加载我们的Person类。并在Person类中去访问Dog类。然后将之前C:\Program Files\Java\jdk1.8.0_131\jre\classes目录下的Person中的Class文件删除掉,另外还有把我们类路径下的Person文件删除掉,并且在I:\test\目录下添加com.test.Person.class文件。代码如下:

package com.test;import java.lang.reflect.Method;/**
 * jvm 类加载器 第一章
 * @author 奇客时间-时光
 * 自定义类加载器
 * 测试子类加载器所加载的类,能否访问父加载器所加载的类。
 */public class MainClass02 {

    public static void main(String[] args) throws Exception {
        //创建自定义类加载器的一个实例,并且通过构造器指定名称        Test01ClassLoader myClassLoader = new Test01ClassLoader("loader1");
        myClassLoader.setPath("I:\\test\\");
        Class<?> classz = myClassLoader.loadClass("com.test.Person");
        System.out.println(classz.getClassLoader());

        System.out.println("Dog的类加载器:"+Dog.class.getClassLoader());

        classz.newInstance();


    }
}

运行结果:"C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=60588:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass02
自己的类加载器被加载了
com.test.Test01ClassLoader@677327b6
Dog的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2
Dog 的构造函数

Process finished with exit code 0

从上面的结果可以看出,Person是由我们的Test01ClassLoader自定义类加载器所加载的,那么它的父亲加载器是AppClassLoader,显然Dog类是由我们的AppClassLoader所加载的。故此代码正常运行,没有抛出异常,从而得出结论:

1:父加载器所加载的类,不能访问子加载器所加载的类。
2:子加载器所加载的类,可以访问父加载器所加载的类。

双亲委托模型的弊端

JVM的艺术之什么是类加载器

案例分析

下面我提供了一张SPI的流程图。不知道什么是SPI的小伙伴儿,可以看一下这张图:

JVM的艺术之什么是类加载器

从上面的例子,我们可以看出,双亲委托模型的弊端。然后我们的jdk给我们提供了一种通过修改线程上下文类加载的方式来打破这种双亲委托的规则。关于修改线程上下文类加载的话题,我们下个章节再具体的讲解。接下来呢,我们再看看,获取类加载器的几个方法。并且奉上翻译好的java doc文档。方便我们后续学习线程类加载器。

获取类加载器的几个方法
/**
* Returns the class loader for the class(返回加载该类的类加载器). Some implementations may use
* null to represent the bootstrap class loader(有一些jvm的实现可能用null来表示我们的启动类加载器比如 hotspot).
* This method will return null in such implementations if this class was loaded by the bootstrap class loader.
* 若这个方法返回null的话,那么这个类是由我们的启动类加载器加载
*
* If this object represents a primitive type or void, null is returned.
(原始类型 比如int,long等等的类或者 void类型 那么他们的类加载器是null)
*
*
*/public ClassLoader getClassLoader() {
ClassLoader cl = getClassLoader0();if (cl == null)return null;
SecurityManager sm = System.getSecurityManager();if (sm != null) {
ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
}return cl;
}
/**
* Returns the system class loader for delegation(该方法返回系统类加载器). This is the default
* delegation parent for new ClassLoader instances(也是我们自己定义的类加载器的委托父类), and is
* typically the class loader used to start the application(通常系统类加载器是用来启动我们的应用的)
*
* This method is first invoked early in the runtime's startup
* sequence(程序在运行早起就会调用该方法), at which point it creates the system class loader and sets it
* as the context class loader of the invoking <tt>Thread</tt>.(在那个时间,调用线程创建我们的系统类加载器同时把系统类加载器设置到我们线程上下文中)
*
* <p> The default system class loader is an implementation-dependent
* instance of this class.(这句话没有很好的理解)
*
* <p> If the system property "<tt>java.system.class.loader</tt>" is defined
* when this method is first invoked then the value of that property is
* taken to be the name of a class that will be returned as the system
* class loader. The class is loaded using the default system class loader
* and must define a public constructor that takes a single parameter of
* type <tt>ClassLoader</tt> which is used as the delegation parent. An
* instance is then created using this constructor with the default system
* class loader as the parameter. The resulting class loader is defined
* to be the system class loader.
我们可以通过java.system.class.loader 系统属性来指定一个自定义的类加载的二进制名称作为新的系统类加载器,
在我们自定的加载中我们需要定义个带参数的构造函数,参数为classLoader,那么我们这个自定义的类加载器就会看做系统类加载器

*
* @return The system <tt>ClassLoader</tt> for delegation, or
* <tt>null</tt> if none
*
* @throws SecurityException
* If a security manager exists and its <tt>checkPermission</tt>
* method doesn't allow access to the system class loader.
*
* @throws IllegalStateException
* If invoked recursively during the construction of the class
* loader specified by the "<tt>java.system.class.loader</tt>"
* property.
*
* @throws Error
* If the system property "<tt>java.system.class.loader</tt>"
* is defined but the named class could not be loaded, the
* provider class does not define the required constructor, or an
* exception is thrown by that constructor when it is invoked. The
* underlying cause of the error can be retrieved via the
* {@link Throwable#getCause()} method.
*
* @revised 1.4
*/@CallerSensitivepublic static ClassLoader getSystemClassLoader() {//初始化系统类加载器initSystemClassLoader();if (scl == null) {return null;
}
SecurityManager sm = System.getSecurityManager();if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}return scl;
}

到此,关于“JVM的艺术之什么是类加载器”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. java JVM-类加载器
  2. 深入理解JVM,类加载器

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

jvm

上一篇:线程调度的随机性有哪些

下一篇:进阶必看的RocketMQ知识点总结

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》