详细讲解Java中的类加载器

1 简介

与普通程序不同的是,Java程序(class文件)并不是本地的可执行程序。当运行Java程序时,首先运行JVMJava虚拟机),然后再把Java class文件加载到JVM里运行,负责加载Java class的这部分就叫做Class Loader

JVM 本身包含了一个ClassLoader称为Bootstrap ClassLoader,和JVM一样,Bootstrap ClassLoader是用本地代码实现的,它负责加载核心Java Class,即所有java.*开头的类,它搜索的范围为:jdk/jre/lib/*.jar

另外JVM还会提供两个ClassLoaderExtClassLoaderAppClassLoader。它们都是用 Java语言编写的,由Bootstrap ClassLoader加载,其中Extension ClassLoader负责加载扩展的Java Class类,包括所有javax.*开头的类和存放在jdk/jre/ext/*.jar目录下的类)。Application ClassLoader负责加载应用程序自身的类,它搜索的范围为:classPath指定的jar或者class类。如下图所示:

详细讲解Java中的类加载器 - 第1张  | IT江湖


2 类加载器的父子关系

I am loading

person类加载器sun.misc.Launcher$AppClassLoader@82ba41

person类加载器的父类加载器sun.misc.Launcher$ExtClassLoader@923e30

person类加载器的祖父类加载器null

注意:$说明是内部类。

3 委托加载机制

在Java的类加载器中,存在一种委托关系:当类加载器需要加载一个类的时候,会首先委托它的父类从其搜索路径中搜索相关类,如果找到,则加载父类加载器所找到的类,否则,才从自身的搜索路径中寻找相关的类,如果还是找不到,将会抛出一个ClassNotFoundException异常。注意:在这里的类加载器之间的委托是递归的,它将一层一层的往上委托,直到Bootstrap ClassLoader。具体实现过程见下面的例子:

上面类的关系为:

详细讲解Java中的类加载器 - 第2张  | IT江湖


将sample.class放到d:\loader1d:\loader3中,运行结果为:

Sample is load by :sun.misc.Launcher$AppClassLoader@3e25a5

Sample is load by :loader3

但是将classpath中的sample.class文件删除之后,运行结果为:

Sample is load by :loader1

Sample is load by :loader3


上面类的内部引用关系为:


详细讲解Java中的类加载器 - 第3张  | IT江湖

4 类的互见性质

命名空间

每个类加载器都有自己的命名空间,命名空间由该类加载器及所有父加载器所加载的类组成。在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类;在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。

运行时包

由同一类加载器加载的属于相同包的类组成了运行时包。决定两个类是不是属于同一个运行时包,不仅要看他们的包名是否相同,还要看定义类加载器是否相同。只有属于同一运行时包的类才能互相访问包可见(即默认访问级别)的类和类成员。这样的限制能避免用户自定义的类冒充核心类库的类,去访问核心类库的包可见成员。假设用户自己定义了一个类java.lang.Spy,并由用户自定义的类加载器加载,由于java.lang.Spy和核心类库 java.lang.*由不同的加载器加载,它们属于不同的运行时包,所以java.lang.Spy不能访问核心类库java.lang包中的包可见成员。

类的互见

同一个命名空间内的类是互相可见的。子加载器的命名空间包含所有父加载器的命名空间,因此由子加载器加载的类能看见父加载器加载的类,例如系统类加载器加载的类能看见根类加载器加载的类;但是由父加载器加载的类不能看见子加载器加载的类;如果两个加载器之间没有直接或间接的父子关系,那么他们各自加载的类相互不可见。如下所示:

运行结果为:

Sample is load by :loader1

Exception in thread "main" java.lang.NoClassDefFoundError: Sample

错误在于Sample sample = Sampleobject;这一行;

MyClassLoader类由系统类加载器加载,而Sample类由loader1类加载器加载,因此MyClassLoader类看不见Sample 类。在MyClassLoadermain方法中使用Sample类,会导致NoClassDefFoundError错误。当两个不同命名空间内的类互相不可见时,可采用java的反射机制来访问对方实例的属性和方法。如下:

Field field = clazz.getField("v1");

int v1 = field.getInt(object);

System.out.println(v1);

详细讲解Java中的类加载器 - 第4张  | IT江湖

5 资源的加载


每一个.class文件加载都要用到类加载器,加载器也可以加载其他文件。

6 类的卸载

当Sample类被加载、连接和初始化后,他的生命周期就开始了。当代表Sample类的Class对象不再被引用,即不可触及时,Class对象就会结束生命周期,Sample类在方法区内的数据也会被卸载,从而结束Sample类的生命周期。由此可见,一个类何时结束生命周期,取决于代表他的Class对象何时结束生命周期。

java虚拟机自带的类加载器所加载的类,在虚拟机的生命周期中,始终不会被卸载,java虚拟机自带的类加载器包括根类加载器、扩展类加载器和系统类加载器。java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象,因此这些Class对象始终是可触及的。

运行结果一为:

6413875

Sample is load by :sun.misc.Launcher$AppClassLoader@3e25a5

6413875

Sample is load by :sun.misc.Launcher$AppClassLoader@3e25a5

 

运行结果二为:

6413875

Sample is load by :loader1

17510567

Sample is load by :loader1

7线程上下文类加载器

线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader() setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值