前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解JVM虚拟机---类的双亲委托模型

深入理解JVM虚拟机---类的双亲委托模型

作者头像
俺也想起舞
发布2020-04-08 15:44:33
7150
发布2020-04-08 15:44:33
举报

类的双亲委托模型&相关动作&命名空间&上下文类加载器

类的双亲委托机制

有一个类(A.class)需要类加载器去加载,如果有父类,先让父类去加载,如此向上追溯,知道根 类加载器,然后根类加载器尝试去加载,加载成功则结束,加载失败,又往下,一层层的尝试去加载,最终如果都没有加载成功,则报错 classnotfound; 但是并不是所有的jvm都是这样,hotspot遵循这样规则。

类加载时的动作

  • 隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中。
  • 显式装载, 通过class.forname()等方法,显式加载需要的类

一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是一种可以理解的机制,而用到时再加载这也是java动态性的一种体现

数组class对象是jvm虚拟机在运行时动态创建的

类的卸载

  • 当MySample类被加载、连接和初始化后,它的生命周期就开始了。当代表MySample类的Class对象不再被引用,即不可触及是,Class对象就会结束生命周期,MySample类在方法去的数据就会被卸载,从而结束MySample类的生命周期
  • 一个类何时结束生命周期,取决于Class对象合适结束生命周期
  • Java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载。Java虚拟机自带的类加载器包括根类加载器、拓展类加载器和系统类加载器。Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用他们所加载的Class对象,因此这些Class对象是始终可以触及的,只有自己自定义的类加载所加载的Class对象才存在被卸载的可能

命名空间

  • 每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器的类组成
  • 在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类
  • 在不同的命名空间中,可能出现类的完整名(包括类的包名)相同的两个类

不同类加载器的命名空间关系

  • 同一命名空间的类是相互可见的
  • 子类的命名空间包含所有父类加载器的命名空间。因此由子加载器加载的类能看见父类加载器加载的类,例如系统加载器加载的类能看见根类加载器加载的类
  • 由父加载器加载的类不能看见子加载器加载的类
  • 如果两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类相互不可见

例子:

代码语言:javascript
复制
// 初始化两个自定义类加载器
ClassLoader cl1 = new MyClassLoader();
ClassLoader cl2 = new MyClassLoader();

Class<?> clazz1 = cl1.loadClass("com.example.MyClass1")
Class<?> clazz2 = cl2.loadClass("com.example.MyClass1")

System.out.println(clazz1==clazz2) //false

在上述例子中clazz1和clazz2就属于两个不同命名空间中相同的类,虽然他们是加载的相同的类,但是在JVM中他们并不相同。

线程上下文类加载器

概念

线程上下文类加载器是从JDK1.2开始引入的,类Thread中的getContextClassLoader()setContextClassLoader(Classloader c)分别用来获取和设置上下文类加载器。 如果没有通过setContextClassLoader(Classloader c)来进行设置的话,线程将继承其弗雷德上下文类加载器。Java应用运行时的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过该类加载器来类与资源。

作用

父ClassLoader可以使用当前线程的Thread.currentThread().getContextClassLoader()所指定的ClassLoader加载类。这就改变了父ClassLoader不能使用子ClassLoader或其他没有直接父子关系的ClassLoader加载的类的情况,即改变了双亲委托模型。 在双亲委托模型下,类的加载是自下而上的,即下层的类加载器会委托上层进行加载。但是对于SPI(Service Provider Interface)来说,有些接口是Java核心库所提供的,而Java核心库是有类加载来进行加载的,而这些接口的实现确是来自不同厂商提供的Jar包,Java的启动类加载器默认是不同加载其他来源的Jar包,这样传统的双亲委托模型就无法满足SPI的要求,而通过给当前线程设置上线文类加载器,就可以由设置的上下文类加载器来实现对于接口类的加载。

线程上下文的一般使用模式

获取 - 使用 - 还原

代码语言:javascript
复制
ClassLoader loader = Thread.currentThread().getContextClassLoader(); //获取
try {
    //使用
    Thread.currentThread().setContextClassLoader(targetTccl);
    myMethod();
}finally {
    Thread.currentThread().setContextClassLoader(loader); //还原
}

总结

类加载器双亲委托模型的好处:

  • 可以确保Java和核心库的类型安全:所有Java应用都至少会引用java.lang.Object类,也就是说在运行期间,java.lang.Object这个类会被加载到Java虚拟机中;如果这个加载过程是由Java应用自己的类加载器完成,那么很可能就会在JVM中存在不同版本的java.lang.Object类,而且这些类相互不兼容,相互不可见。借助双亲委托机制,Java核心类库的加载工作都是由启动类加载器统一完成,从而确保Java所有应用使用的都是同一版本的Java核心类库,他们之间是相互兼容的
  • 可以确保Java核心类库所提供的类不会被自定义的类所打扰。例如,我们自定义一个java.lang.Object类,他是无论如果不会被加载到JVM中的,而是由根类加载器去核心库中寻找
  • 不同的类加载器可以为相同的名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在Java虚拟机中,只需要用不同的类加载器来加载他们即可。不同类加载器所加载的类之间是不兼容的,这就相当于在Java虚拟机内部创建了一个又一个相互隔离的Java类空间,这类技术在很多框架中得到实际应用
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 类的双亲委托机制
  • 类加载时的动作
  • 类的卸载
  • 命名空间
  • 线程上下文类加载器
    • 概念
      • 作用
        • 线程上下文的一般使用模式
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档