类加载器

1、类加载是什么?

把描述类的数据加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机使用的Java类型。

2、类的生命周期

3、类加载器

1:bootstrap ClassLoader

根类加载器,由JVM自身实现,不是java.lang.ClassLoader的子类,负责加载jre/lib下面的jar包。

2:Extendsion Classloader

扩展类加载器,负责加载jre/lib/ext下面的jar

3:system Classloader

系统类加载器,负责在JVM启动时加载来自Java命令的-classpath、java.class.path系统属性,或者CLASSPATH环境变量指定的jar包或类路径。默认情况下,用户自定义的类加载的父类都是它。

4、类加载器的机制

1:全盘负责,当类加载器负责加载一个类时,该类所依赖和引入的其他类也由该加载器负责加载。

2:父类委托,层层委托。也就是说,最先调用loadClass的ClassLoader是最后一个去加载类的。

3:缓存机制,首先去找自己是否加载过,如果没有就去父类加载器找是否加载过。所以类似Object的类都是已经加载过的,并且只会加载一份。

(这里还有一个问题,一个class是否被一个classLoader加载并不是根据loadClass方法来判断的,而是那个读取到了class字节码并在调用defineClass的时候将这个字节码流作为参数传递的ClassLoader加载了类,类也将保存在那个ClassLoader的已加载类列表中。)

5、Tomcat的类加载原理

1、可以首先了解下JNDI的类加载原理

JNDI接口是Java本地实现的,并且放在了rt.jar里面,当程序启动之后肯定会被启动类加载器加载,而JNDI的接口实现都是各个厂商实现的,放在了classpath下面,所以,当启动类加载了接口并且准备调用接口实现的时候,会首先尝试加载接口实现,可是接口实现并不在启动类加载器加载路径中,所以肯定无法找到接口实现,这时候实际上JNDI服务是使用现场上下文类加载器去加载SPI所需的代码的,这个时候就是使用的子类的加载器去加载所需代码的。JDBC类加载原理类似。

2、Tomcat的类加载

6、spark的类加载器的实现

private[spark] class ChildFirstURLClassLoader(urls: Array[URL], parent: ClassLoader)
  extends MutableURLClassLoader(urls, null) {

  private val parentClassLoader = new ParentClassLoader(parent)

  override def loadClass(name: String, resolve: Boolean): Class[_] = {
    try {
      super.loadClass(name, resolve)
    } catch {
      case e: ClassNotFoundException =>
        parentClassLoader.loadClass(name, resolve)
    }
  }

  override def getResource(name: String): URL = {
    val url = super.findResource(name)
    val res = if (url != null) url else parentClassLoader.getResource(name)
    res
  }

  override def getResources(name: String): Enumeration[URL] = {
    val childUrls = super.findResources(name).asScala
    val parentUrls = parentClassLoader.getResources(name).asScala
    (childUrls ++ parentUrls).asJavaEnumeration
  }

  override def addURL(url: URL) {
    super.addURL(url)
  }

}

7、常见的错误

1、错误发生在<clinit> 和 <init>

clinit方法是类变量和static静态初始化块合并而成的。

init是实例构造器。

2、不同的类加载器加载同一个类。

17/07/07 13:36:44 ERROR yarn.ApplicationMaster: User class threw exception: java.lang.LinkageError: loader constraint violation: when resolving method "org.slf4j.impl.StaticLoggerBinder.getLoggerFactory()Lorg/slf4j/ILoggerFactory;" 
the class loader (instance of org/apache/spark/util/ChildFirstURLClassLoader) of the current class, org/slf4j/LoggerFactory, and the class loader (instance of sun/misc/Launcher$AppClassLoader) for the method's defining class, org/slf4j/impl/StaticLoggerBinder, have different Class objects for the type org/slf4j/ILoggerFactory used in the signaturejava.lang.LinkageError: loader constraint violation: when resolving method "org.slf4j.impl.StaticLoggerBinder.getLoggerFactory()Lorg/slf4j/ILoggerFactory;" the class loader (instance of org/apache/spark/util/ChildFirstURLClassLoader) of the current class, org/slf4j/LoggerFactory, and the class loader (instance of sun/misc/Launcher$AppClassLoader) for the method's defining class, org/slf4j/impl/StaticLoggerBinder, have different Class objects for the type org/slf4j/ILoggerFactory used in the signature  
at org.slf4j.LoggerFactory.getILoggerFactory(LoggerFactory.java:306)    at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:276)    
at org.slf4j.LoggerFactory.getLogger(LoggerFactory.java:288)    
at com.streaming.saprk21kafka08.SparkAppRunner$.<init>(SparkAppRunner.scala:22) 
at com.streaming.saprk21kafka08.SparkAppRunner$.<clinit>(SparkAppRunner.scala)  
at com.streaming.saprk21kafka08.SparkAppRunner.main(SparkAppRunner.scala)   
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)    
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)    
at java.lang.reflect.Method.invoke(Method.java:498) 
at org.apache.spark.deploy.yarn.ApplicationMaster$$anon$2.run(ApplicationMaster.scala:637)

该错误是在使用spark过程中使用参数userClassPathFirstr后非常容易出现的错误,错误的原因是自己的程序包里含有LoggerFactory(在包sl4j-api中)类,被spark自定义的类加载器ChildFirstURLClassLoader加载,然后在加载类StaticLoggerBinder(在包sl4j-log4j中)的时候,发现程序包中没有,请求用父类加载器即加载spark框架代码的AppClassLoader来加载,而AppClassLoader只会加载spark classpath路径下的类,这个时候会把spark框架中的StaticLoggerBinder加载上来,这个时候实际上有两个ILoggerFactory类被不同的类加载器加载。所以出现以上错误。

解决办法:程序包中包含所有的sl4j组件或者不包含所有的sl4j组件,不能只包含一部分。

3、这里使用一个例子来详细说明下不同类加载器造成的问题:

首先自己实现了一个类加载器,可以看出来和spark定义的ChildFirstURLClassLoader 非常类似(这里名字也取成一样的了),就是破坏了双亲委派模型,先让当前的类加载器自己加载,自己加载不了的时候才会请求父类加载。

public class ChildFirstURLClassLoader extends URLClassLoader{
    public ChildFirstURLClassLoader(URL[] urls, ClassLoader parent) {
        super(urls, parent);
    }

    public ChildFirstURLClassLoader(URL[] urls) {
        super(urls);
    }

    public ChildFirstURLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
        super(urls, parent, factory);
    }

    @Override
    protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        try {
            return findClass(name);
        } catch (ClassNotFoundException e) {
            System.out.println(e);
            return  super.loadClass(name, resolve);
        }
    }
}

然后定义了一个接口和一个实现类以及一个用来造成类冲突的Age类。

public class Age {
}
public interface Animal {    
  public Age getAge();
}
public class Monkey implements Animal{   
 @Override    
  public Age getAge() {
        return new Age();    
  }
}

下面来看测试例子:

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

    {
        //第13行
        Age age1 = new Age();
        URL[] urls = {new URL("file:/Users/zhaoshijie/temp/")};
        ChildFirstURLClassLoader childFirstURLClassLoader = new ChildFirstURLClassLoader(urls);
        System.out.println(childFirstURLClassLoader.getParent());
        //18行
        Animal animal = (Animal) childFirstURLClassLoader.loadClass("com.streaming.java.basic.classLoader.Monkey", false).newInstance();
        System.out.println(animal.getClass().getClassLoader());
        Age age = animal.getAge();
        System.out.println(age.getClass().getClassLoader());
    }
}

这里我们按照不同情况来分析,首先:
(1):目录/Users/zhaoshijie/temp/ 下为:Age.class和Monkey.class、Animal.class
运行之后的错误为:

sun.misc.Launcher$AppClassLoader@4617c264java.lang.ClassNotFoundException: java.lang.ObjectException 
in thread "main" java.lang.ClassCastException: com.streaming.java.basic.classLoader.Monkey cannot be cast to com.streaming.java.basic.classLoader.Animal    
at com.streaming.java.basic.classLoader.LinkedErrorTest.main(LinkedErrorTest.java:18)   
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)    
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)    
at java.lang.reflect.Method.invoke(Method.java:498) 
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

首先为什么会报java.lang.ClassNotFoundException: java.lang.Object 呢?这是因为我们用自己重新的类加载器加载Monkey类,而加载Monkey类的时候会加载Monkey类用到的所有类,任何用户自定义的类肯定是继承自Object,所以会首先加载Object,而我们自定义的类加载器加载路径下面是没有Object类的,所以首先会报这个错误。

接着报了ClassCastException 类型转换错误,这个就比较明显了,代码第18行等号后面的Monkey类是由我们自定义的类加载器加载的,而强制类型转换(Animla)是由当前程序的类加载器AppClassLoader加载的,这两个类没有继承或者相等关系,强制转换就会报错。但是奇怪的是,如果目录/Users/zhaoshijie/temp/ 下没有Animal.class,这一步是不会报错的。我们来看。

(2)目录/Users/zhaoshijie/temp/ 下为:Age.class和Monkey.class
报错为:

sun.misc.Launcher$AppClassLoader@4617c264
java.lang.ClassNotFoundException: com.streaming.java.basic.classLoader.Animal
com.streaming.java.basic.classLoader.ChildFirstURLClassLoader@2f92e0f4
java.lang.ClassNotFoundException: java.lang.Object
Exception in thread "main" java.lang.LinkageError: loader constraint violation: loader (instance of com/streaming/java/basic/classLoader/ChildFirstURLClassLoader) previously initiated loading for a different type with name "com/streaming/java/basic/classLoader/Age"
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
    at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
    at com.streaming.java.basic.classLoader.ChildFirstURLClassLoader.loadClass(ChildFirstURLClassLoader.java:27)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at com.streaming.java.basic.classLoader.Monkey.getAge(Monkey.java:9)
    at com.streaming.java.basic.classLoader.LinkedErrorTest.main(LinkedErrorTest.java:20)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

首先java.lang.ClassNotFoundException: com.streaming.java.basic.classLoader.Animal和java.lang.ClassNotFoundException: java.lang.Object报错相信大家都能理解,是因为我们自定义的类加载器加载路径下没有这两个类。代码18行为什么没有报错呢?因为childFirstURLClassLoader加载器没有加载到Animal类,它根本就不”认识“Animal类,所以强转成一个它暂时还不认识的类的时候不会报错。

接着在代码第20行报LinkageError,这个错误一般也是同一个类被不同的类加载器加载造成冲突而形成的。这个原因就是不是因为Animal类了,而是由于Age类,childFirstURLClassLoader在加载Monkey类的时候也会加载Age类,而由于代码13行,Age类已经被AppClassLoader加载过一次了。而代码20行的右边得到的是childFirstURLClassLoader加载的Age类,左边是AppClassLoader加载的Age类,这个时候自然会发生错误。我们自然会猜想,如果把代码13行去掉,就不会报这个错误了。我们来试一下。

去掉之后的测试代码为:

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

    {
        URL[] urls = {new URL("file:/Users/zhaoshijie/temp/")};
        ChildFirstURLClassLoader childFirstURLClassLoader = new ChildFirstURLClassLoader(urls);
        System.out.println(childFirstURLClassLoader.getParent());
        Animal animal = (Animal) childFirstURLClassLoader.loadClass("com.streaming.java.basic.classLoader.Monkey", false).newInstance();
        System.out.println(animal.getClass().getClassLoader());
        Age age = animal.getAge();
        System.out.println(age.getClass().getClassLoader());
    }
}

测试结果为:

sun.misc.Launcher$AppClassLoader@4617c264
java.lang.ClassNotFoundException: com.streaming.java.basic.classLoader.Animal
com.streaming.java.basic.classLoader.ChildFirstURLClassLoader@2f92e0f4
java.lang.ClassNotFoundException: java.lang.Object
com.streaming.java.basic.classLoader.ChildFirstURLClassLoader@2f92e0f4

和我们测猜测基本一致。

8、其他一些总结

  • Class.forName 是使用加载调用该方法的类的classloader来加载对应name的类。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,686评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,668评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,160评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,736评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,847评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,043评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,129评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,872评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,318评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,645评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,777评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,861评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,589评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,687评论 2 351

推荐阅读更多精彩内容