前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >类加载器详解

类加载器详解

作者头像
用户1257393
发布2018-01-30 12:17:02
7210
发布2018-01-30 12:17:02
举报
文章被收录于专栏:精讲JAVA

内容:转自 java知音

类加载器是负责将可能是网络上、也可能是磁盘上的class文件加载到内存中。并为其生成对应的java.lang.class对象。一旦一个类被载入JVM了,同一个类就不会被再次加载。

那么怎样才算是同一个类?在JAVA中一个类用其全限定类名(包名和类名)作为其唯一标识,但是在JVM中,一个类用其全限定类名和其类加载器作为其唯一标识。也就是说,在JAVA中的同一个类,如果用不同的类加载器加载,则生成的class对象认为是不同的。

当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构

1、启动类加载器BootstrapClassLoader:

是嵌在JVM内核中的加载器,该加载器是用C++语言写的,主要负载加载JAVA_HOME/lib下的类库,启动类加载器无法被应用程序直接使用。

2、扩展类加载器Extension ClassLoader:

该加载器器是用JAVA编写,且它的父类加载器是Bootstrap,是由sun.misc.Launcher$ExtClassLoader实现的,主要加载JAVA_HOME/lib/ext目录中的类库。开发者可以这几使用扩展类加载器。

我们知道java中系统属性java.ext.dirs指定的目录由ExtClassLoader加载器加载,如果程序中没有指定该系统属性(Djava.ext.dirs=sss/lib)那么该加载器默认加载$JAVA_HOME/lib/ext目录下的所有jar文件,通过程序来看下系统变量java.ext.dirs所指定的路径:

代码语言:javascript
复制
public class Test{    public static void main(String[] args)    {        System.out.println(System.getProperty("java.ext.dirs"));    }}

执行结果:

代码语言:javascript
复制
C:\Program Files (x86)\Java\jdk1.6.0_43\jre\lib\ext;C:\Windows\Sun\Java\lib\ext

3、系统类加载器App ClassLoader:

系统类加载器,也称为应用程序类加载器,负责加载应用程序classpath目录下的所有jar和class文件。它的父加载器为Ext ClassLoader。

代码语言:javascript
复制
public class Test{    public static void main(String[] args)    {        System.out.println(ClassLoader.getSystemClassLoader());    }}

执行结果:

代码语言:javascript
复制
sun.misc.Launcher$AppClassLoader@addbf1

程序中的方法是返回委托的系统类加载器,通过执行结果,可以知道,系统类加载器是通过sun.misc.Launcher$AppClassLoader实现的。

上述三种类加载器的层次关系如下:

注意:类加载器的体系并不是“继承”体系,而是委派体系,大多数类加载器首先会到自己的parent中查找类或者资源,如果找不到才会到自己本地查找。类加载器的委托行为动机是为了避免相同的类被加载多次。

我们可以通过程序来验证下:

代码语言:javascript
复制
public static void main(String[] args)    {        System.out.println(ClassLoader.getSystemClassLoader().getParent());    }

执行结果:

代码语言:javascript
复制
sun.misc.Launcher$ExtClassLoader@42e816

在这里可以看到,Application ClassLoader的父加载器确实是ExtClassLoader。

我们在往上走一层,如果猜想没错的话,ExtClassLoader的父加载器应该是BootStrap ClassLoader

代码语言:javascript
复制
public static void main(String[] args)    {        System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());    }

执行结果:

代码语言:javascript
复制
null

这里不是说ExtClassLoader没有父加载器,而是因为Bootstrap ClassLoader使用C++写的。

UML类图:

我们解读源码来看下这些类的继承关系

AppClassLoader与ExtClassLoader都是Lancher的内部类:

代码语言:javascript
复制
static class ExtClassLoader extends URLClassLoaderstatic class AppClassLoader extends URLClassLoaderpublic class URLClassLoader extends SecureClassLoaderpublic class SecureClassLoader extends ClassLoaderpublic abstract class ClassLoader

我们有这样一个结论,除了启动类加载器Bootstrap ClassLoader,其他的类加载器都是ClassLoader的子类。

我们来反编译看下rt.jar,在sun.misc.Launcher$AppClassLoader路径中,看下AppClassLoader的源码:

代码语言:javascript
复制
static class AppClassLoader extends URLClassLoader  {    public static ClassLoader getAppClassLoader(ClassLoader paramClassLoader)      throws IOException    {      String str = System.getProperty("java.class.path");      File[] arrayOfFile = (str == null) ? new File[0] : Launcher.access$200(str);      return ((AppClassLoader)AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader)      {        public Object run() {          URL[] arrayOfURL = (this.val$s == null) ? new URL[0] : Launcher.access$300(this.val$path);          return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);        }      }));    }

在第6行代码中可以看到,系统类加载器只能加载java.class.path路径下的class文件。我们通过程序看下java.class.path指定的路径

代码语言:javascript
复制
public class Test{    public static void main(String[] args)    {        System.out.println(System.getProperty("java.class.path"));    }}

执行结果:

如果是JAVA工程:

代码语言:javascript
复制
F:\workSpace\test\bin

如果是JAVAWEB工程:

代码语言:javascript
复制
F:\workSpace\study\WebRoot\WEB-INF\classes

双亲委派模型

如果一个类加载器收到了一个类加载请求,它不会自己去尝试加载这个类,而是把这个请求转交给父类加载器去完成。每一个层次的类加载器都是如此。

因此所有的类加载请求都应该传递到最顶层的启动类加载器中,只有到父类加载器反馈自己无法完成这个加载请求(在它的搜索范围没有找到这个类)时,子类加载器才会尝试自己去加载。委派的好处就是避免有些类被重复加载。

双亲委派的实现比较简单,我们来看下源码:

代码语言:javascript
复制
protected synchronized Class<?> loadClass(String paramString, boolean paramBoolean)    throws ClassNotFoundException  {       //检查是否被加载过    Class localClass = findLoadedClass(paramString);       //如果没有加载,则调用父类加载器    if (localClass == null) {      try {           //父类加载器不为空        if (this.parent != null)          localClass = this.parent.loadClass(paramString, false);        else {             //父类加载器为空,则使用启动类加载器          localClass = findBootstrapClass0(paramString);        }      }      catch (ClassNotFoundException localClassNotFoundException)      {           //如果父类加载失败,则使用自己的findClass方法进行加载        localClass = findClass(paramString);      }    }    if (paramBoolean) {      resolveClass(localClass);    }    return localClass;  }

先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass方法,若父类加载器不存在,则使用启动类加载器。如果父类加载器加载失败,则抛出异常之后看,再调用自己的findClass方法进行加载。

代码语言:javascript
复制
作者:冬瓜蔡原文:http://www.cnblogs.com/dongguacai/p/5879931.html
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 精讲JAVA 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档