类加载器

1、类加载是什么?

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

2、类的生命周期

类加载器_第1张图片

3、类加载器

类加载器_第2张图片
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的类加载

类加载器_第3张图片

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方法是类变量和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$.(SparkAppRunner.scala:22) 
at com.streaming.saprk21kafka08.SparkAppRunner$.(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的类。

你可能感兴趣的:(类加载器)