• 首先看对jdk中对java.lang.LinkageError的定义 Subclasses of {@code LinkageError} indicate that a class has some dependency on another class; however, the latter class has incompatibly changed after the compilation of the former class.
  • 就是说当从一个类中去试图加载另一个类时,发现在编译之后,发生了不兼容的变化。常见的比如NoClassDefFoundError,NoSuchMethodError等,也就是说,在编译时,所有的类都可以正常找到,正常编译,在运行时,却发现某些类改变了。一般来说,我们是不会在编译之后,运行时之前去改变类的.class文件的,所以为什么会出现不一致呢,NoClassDefFoundError,NoSuchMethodError这类错误大多数情况下都是,在类加载的时候和类编译的时候使用的不是一个类。
  • LinkeageError 顾名思义,是链接时期错误,jvm的类加载过程如下图,
  • 其中加载是指将.class文件加载到方法区中,并在堆内存中生成一个对应的Class对象。
  • 在加载过程中主要做了三件事:读取class文件,转换成二进制流,从二进制流中构造出在方法区存储的数据结构,在堆中生成一个class对象作为方法区数据结构访问的入口。
  • 验证. 对文件格式合法性和元数据合法性等做验证。
  • 准备。 在方法区中为类变量分配内存,实例变量不会分配,实例变量分配在堆中。
  • 解析 把符号引用转换为直接引用,把方法变量等转换为地址。
  • 初始化。 对类变量赋初值。

什么时候进行类加载

  • JVM对类加载没有明确的规范,类并不是在第一次被使用时才开始加载,虚拟机可以自己决定加载的时机,如果预测到这个类在未来会被加载,可以先加载。
  • 什么时候进行初始化呢?
  • 虚拟机启动时,定义了main()方法的那个类先初始化,这是程序的入口,从这个入口开始,所有这个类中涉及到的其他类根据以下其他规则进行初始化。
  • 创建类的实例
  • 访问类的静态变量
  • 访问类的静态方法
  • 反射 class.forName();
  • 当初始化一个类时,发现其父类还未初始化,则先出发父类的初始化

类加载器和双亲委派模型

  • 存在三种类加载器,bootstrap classLoader,extension classload,application classloader,其中bootstrap classloader是使用c实现的,是位于JVM体系内的,其他两个classloader均是继承自cloassloader这个抽象类。
  • 这样的实现避免类chicken-egg-problem,机extension和application两个classloader本质上也是一个class,由哪个classloader来加载呢,答案是在JVM启动时,由bootStrap classloader来加载。
  • classloader在加载类时,遵循双亲委派模型,即在拿到一个类时,首先交由父classloader进行加载,父classloader加载不了时,再自己加载,这样做到jdk定义的一些核心类如string object等永远有bootstrap classloader加载,不会被自定义的篡改。
  • 在jvm中,一个类由这个类的全限定名称和加载这个类的classloader两个唯一确定。

最开始的问题

  • java.lang.LinkageError: loader constraint violation: loader previously initiated loading for a different type with name X
  • 违反类加载约束,X这个类已经被别的类加载器初始化过了。
  • 这种问题一般出现在多个classloader的场景下,classloader的实现没有完全遵循双亲委派模型。
  • 最常见的tomcat在做多个webApp之间的类隔离时,违反类双亲委派模型,每个webApp自己的classloader加载自己类,而不会交给父classloader去加载。

参考