类加载

虚拟机之类加载机制

当我们编写完程序,生成了.class文件,再编译成.java文件,接着我们便要开始执行这个程序,那么虚拟机把文件加载进入并生成类结构数据放置再内存中这个过程是什么样的呢?

1、加载的时机

除了主动引用类会触发类加载外,引用子类继承自父类的静态字段会触发父类加载。

  • I.主动引用

    • ①当使用显式代码并生成了new,getstatic,putstatic,invokestatic字节码指令的情况属于主动引用,常见的场景是:实例化对象、读取或设置非常量静态字段,调用静态方法时。

    • ②反射调用类的场景

    • ③初始化子类时会一并初始化父类,因此父类也会被加载进来

    • ④执行主类时

  • II.被动引用

    • ①引用子类继承自父类的静态字段会触发父类加载,而子类不会

    • ②数组定义引用类

    • ③某个类初始化时引用另外一个类的常量不会导致另一个类加载,原因在于被引用的常量会直接存入调用类中

2、类加载过程

类加载

值得注意的是:

  • I.类加载的只是类的静态成员

  • II.准备阶段为类的变量分配内存并初始化时并不是直接将源码中指定的值赋给变量,而只是给这些变量赋“零值”,如给int a这个变量赋‘0’值,给String s这个变量赋Null值。

3、类加载机制

  • I.每一个类都须有加载它的类加载器和类本身确定其再虚拟机内存中的唯一性,即是如果类相等如使用equals(),isAssignalbleFrom(),isInstance()和instanceOf结果为‘真’时,则该类文件和加载这个类文件的加载器是同一个。

  • II.双亲委派机制:当某个类加载器收到类加载请求时,它会沿着继承关系将请求委派最顶层父类加载器,如果顶层父类加载器无法加载该类(该类不在这个加载器的加载范围),子类才开始尝试加载。这个机制可保证被加载类在全局中的一致性。

  • III.三个破坏双亲委派机制的情况

    • ①在双亲委派机制出现之前,用户继承java.lang.ClassLoader类只是为了重写ClassLoader类下的loadClass()用来让虚拟机在类加载时也能使用用户自定义的类加载器,而为了向前兼容,在双亲委派机制出现之后,在java.lang.ClassLoader类添加了一个findClass()方法,用来在父类加载器没法加载类时使用这个子类加载器加载

    • ②第二个情况由机制本身产生的,当由父类加载器加载的基础类需要反过来调用用户的代码时,这个机制就无能为力了,因此出现了了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader),这个加载器可由java.lang.Thread类的setContextClassLoader()方法设置。

    • ③为了实现程序动态性的特性以拥有“热部署”或“代码热替换”功能,让虚拟机中的类能向计算机的外设一样能随意拔插,出现了OSGi规范,在OSGi中,允许类加载器将请求委派给平级的类加载器,也就是将树状的请求结构变成网状结构。

      OSGi的机制为:每个模块(称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时会连同类加载器一起更换,而类搜索顺序如下:

      将以java.lang.*的类委派给父类加载器加载,
      否则将委派列表名单中的类委派给父类加载器加载,
      否则将Import列表中的类委派给Export这个类的Bundle的类加载器加载,
      否则查找当前Bundle的ClassPath以使用自己的类加载器加载,
      否则查找是否在自己的Fragment Bundle即平级Bundle中以委派给平级Bundle的类加载器加载,
      否则查找Dynamic Import列表中的Bundle以委派给对应的Bundle的类加载器加载,
      否则搜索失败。