当前位置: 移动技术网 > IT编程>开发语言>Java > java ClassLoader机制详细讲解

java ClassLoader机制详细讲解

2019年07月22日  | 移动技术网IT编程  | 我要评论
要深入了解classloader,首先就要知道classloader是用来干什么的,顾名思义,它就是用来加载class文件到jvm,以供程序使用的。我们知道,java程序可

要深入了解classloader,首先就要知道classloader是用来干什么的,顾名思义,它就是用来加载class文件到jvm,以供程序使用的。我们知道,java程序可以动态加载类定义,而这个动态加载的机制就是通过classloader来实现的,所以可想而知classloader的重要性如何。

看到这里,可能有的朋友会想到一个问题,那就是既然classloader是用来加载类到jvm中的,那么classloader又是如何被加载呢?难道它不是java的类?

没有错,在这里确实有一个classloader不是用java语言所编写的,而是jvm实现的一部分,这个classloader就是bootstrap classloader(启动类加载器),这个classloader在jvm运行的时候加载java核心的api以满足java程序最基本的需求,其中就包括用户定义的classloader,这里所谓的用户定义是指通过java程序实现的classloader,一个是extclassloader,这个classloader是用来加载java的扩展api的,也就是/lib/ext中的类,一个是appclassloader,这个classloader是用来加载用户机器上classpath设置目录中的class的,通常在没有指定classloader的情况下,程序员自定义的类就由该classloader进行加载。

当运行一个程序的时候,jvm启动,运行bootstrap classloader,该classloader加载java核心api(extclassloader和appclassloader也在此时被加载),然后调用extclassloader加载扩展api,最后appclassloader加载classpath目录下定义的class,这就是一个程序最基本的加载流程。

上面大概讲解了一下classloader的作用以及一个最基本的加载流程,接下来将讲解一下classloader加载的方式,这里就不得不讲一下classloader在这里使用了双亲委托模式进行类加载。

每一个自定义classloader都必须继承classloader这个抽象类,而每个classloader都会有一个parent classloader,我们可以看一下classloader这个抽象类中有一个getparent()方法,这个方法用来返回当前classloader的parent,注意,这个parent不是指的被继承的类,而是在实例化该classloader时指定的一个classloader,如果这个parent为null,那么就默认该classloader的parent是bootstrap classloader,这个parent有什么用呢?

我们可以考虑这样一种情况,假设我们自定义了一个clientdefclassloader,我们使用这个自定义的classloader加载java.lang.string,那么这里string是否会被这个classloader加载呢?事实上java.lang.string这个类并不是被这个clientdefclassloader加载,而是由bootstrap classloader进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为在任何一个自定义classloader加载一个类之前,它都会先委托它的父亲classloader进行加载,只有当父亲classloader无法加载成功后,才会由自己加载,在上面这个例子里,因为java.lang.string是属于java核心api的一个类,所以当使用clientdefclassloader加载它的时候,该classloader会先委托它的父亲classloader进行加载,上面讲过,当classloader的parent为null时,classloader的parent就是bootstrap classloader,所以在classloader的最顶层就是bootstrap classloader,因此最终委托到bootstrap classloader的时候,bootstrap classloader就会返回string的class。

我们来看一下classloader中的一段源代码:

 protected synchronized class loadclass(string name, boolean resolve) 
  throws classnotfoundexception 
    { 
  // 首先检查该name指定的class是否有被加载 
  class c = findloadedclass(name); 
  if (c == null) { 
    try { 
    if (parent != null) { 
      //如果parent不为null,则调用parent的loadclass进行加载 
   = parent.loadclass(name, false); 
    } else { 
      //parent为null,则调用bootstrapclassloader进行加载 
      c = findbootstrapclass0(name); 
    } 
    } catch (classnotfoundexception e) { 
      //如果仍然无法加载成功,则调用自身的findclass进行加载       
      c = findclass(name); 
    } 
  } 
  if (resolve) { 
    resolveclass(c); 
  } 
  return c; 
    } 

从上面一段代码中,我们可以看出一个类加载的大概过程与之前我所举的例子是一样的,而我们要实现一个自定义类的时候,只需要实现findclass方法即可。

为什么要使用这种双亲委托模式呢?

第一个原因就是因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子classloader再加载一次。

第二个原因就是考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的string来动态替代java核心api中定义类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为string已经在启动时被加载,所以用户自定义类是无法加载一个自定义的classloader。

上面对classloader的加载机制进行了大概的介绍,接下来不得不在此讲解一下另外一个和classloader相关的类,那就是class类,每个被classloader加载的class文件,最终都会以class类的实例被程序员引用,我们可以把class类当作是普通类的一个模板,jvm根据这个模板生成对应的实例,最终被程序员所使用。

我们看到在class类中有个静态方法forname,这个方法和classloader中的loadclass方法的目的一样,都是用来加载class的,但是两者在作用上却有所区别。
class<?> loadclass(string name)
class<?> loadclass(string name, boolean resolve)
我们看到上面两个方法声明,第二个方法的第二个参数是用于设置加载类的时候是否连接该类,true就连接,否则就不连接。

说到连接,不得不在此做一下解释,在jvm加载类的时候,需要经过三个步骤,装载、连接、初始化。装载就是找到相应的class文件,读入jvm,初始化就不用说了,最主要就说说连接。

连接分三步,第一步是验证class是否符合规格,第二步是准备,就是为类变量分配内存同时设置默认初始值,第三步就是解释,而这步就是可选的,根据上面loadclass方法的第二个参数来判定是否需要解释,所谓的解释根据《深入jvm》这本书的定义就是根据类中的符号引用查找相应的实体,再把符号引用替换成一个直接引用的过程。有点深奥吧,呵呵,在此就不多做解释了,想具体了解就翻翻《深入jvm吧》,呵呵,再这样一步步解释下去,那就不知道什么时候才能解释得完了。

我们再来看看那个两个参数的loadclass方法,在java api 文档中,该方法的定义是protected,那也就是说该方法是被保护的,而用户真正应该使用的方法是一个参数的那个,一个参数的loadclass方法实际上就是调用了两个参数的方法,而第二个参数默认为false,因此在这里可以看出通过loadclass加载类实际上就是加载的时候并不对该类进行解释,因此也不会初始化该类。而class类的forname方法则是相反,使用forname加载的时候就会将class进行解释和初始化,forname也有另外一个版本的方法,可以设置是否初始化以及设置classloader,在此就不多讲了。


不知道上面对这两种加载方式的解释是否足够清楚,就在此举个例子吧,例如jdbc driver的加载,我们在加载jdbc驱动的时候都是使用的forname而非是classloader的loadclass方法呢?我们知道,jdbc驱动是通过drivermanager,必须在drivermanager中注册,如果驱动类没有被初始化,则不能注册到drivermanager中,因此必须使用forname而不能用loadclass。

通过classloader我们可以自定义类加载器,定制自己所需要的加载方式,例如从网络加载,从其他格式的文件加载等等都可以,其实classloader还有很多地方没有讲到,例如classloader内部的一些实现等等,

  通过此文,小编希望大家对classloader 机制有所了解,谢谢支持! 

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网