Java 类加载器(二)

下面比较深入的介绍java 类加载原理和过程,虽然很多情况你都不需要了解类加载的过程,但是如果你希望了解Java 是如何工作的,或者希望定义自己的类加载器,就一定要了解类加载的过程,当然,无论你是否要参与类加载过程,了解这个过程对你都会有很大的帮助。

 

本文使用的JDKsunjdk_1.6 版本,当然这里重在阐述原理,和JDK 本身没有多大的关系。

 

这里试图解决下面一些问题:

1,类加载原理

2,引导类加载器,扩展类加载器和系统类加载器

3,如何知道某个类是哪个类加载器加载的

4,如何得到系统类加载器加载了那些类

 

一 类加载原理:

首先我们要分析类加载原理,java 中默认有三种类加载器:引导类加载器,扩展类加载器,系统类加载器( 也叫应用类加载器)

引导类加载器 负责加载jdk 中的系统类,这种类加载器都是用c 语言 实现的,在java 程序中没有办法获得这个类加载器,对于java 程序是一个概念而已,基本上不用考虑它的存在,像String,Integer 这样的类都是由引导类加载器加载器的.

扩展类加载器 负责加载标准扩展类,一般使用java 实现 ,这是一个真正的java 类加载器,负责加载jre/lib/ext 中的类,和普通的类加载器一样,其实这个类加载器对我们来说也不是很重要,我们可以通过java 程序获得这个类加载器。

系统类加载器 ,加载第一个应用类的加载器( 其实这个定义并不准确, 下面你将会看到) ,也就是执行java MainClass 时加载MainClass 的加载器,这个加载器使用java 实现 ,使用的很广泛,负责加载classpath 中指定的类。

 

类加载器之间有一定的关系( 父子关系) ,我们可以认为扩展类加载器的父加载器是引导类加载器( 当然不这样认为也是可以的,因为引导类加载器表现在java 中就是一个null), 不过系统类加载器的父加载器一定是扩展类加载器,类加载器在加载类的时候会先给父加载器一个机会,只有父加载器无法加载时才会自己去加载。

 

 

二,引导类加载器,扩展类加载器和系统类加载器以及 如何知道某个类是哪个类加载器加载的

引导类加载器:

我们无法获得引导类加载器,因为它是使用c 实现的,而且使用引导类加载器加载的类通过getClassLoader 方法返回的是null. 所以无法直接操作引导类加载器,但是我们可以根据Class.getClassLoader 方法是否为null 判断这个类是不是引导类加载器加载的,可以通过下面的方法获得引导类加载器加载的类路径( 每个jar 包或者文件夹对应了一个URL);

sun.misc.Launcher.getBootstrapClassPath().getURLs()

你可以直接在你的main 函数中输出就可以了

System.out.println(java.util.Arrays.asList(sun.misc.Launcher.getBootstrapClassPath().getURLs()).toString());

得到的结果是:

[file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/rt.jar,

 file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/i18n.jar,

 file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/sunrsasign.jar,

 file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jsse.jar,

 file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/jce.jar,

 file:/C:/Program%20Files/Java/j2re1.4.2_10/lib/charsets.jar,

 file:/C:/Program%20Files/Java/j2re1.4.2_10/classes]

 

其实我们是可以指定引导类加载器的类路径的,java 提供了一个-Xbootclasspath 参数,不过这个参数不是标准参数。

java -Xbootclasspath: 运行时指定引导类加载器的加载路径(jar 文件或者目录)

java -Xbootclasspath/p: 和上面的相同,不过把这个路径放到原来的路径前面

java -Xbootclasspath/a: 这个就是在原引导类路径后面添加类路径。

上面我们有提过加载第一个应用类未必就是系统加载器。

如果我把这个应用类的路径放到引导类路径中,它将会被引导类加载器加载,大致这样

java -Xbootclasspath/a:myjar.jar MainClass

如果MainClassmyjar.jar, 那么这个类将会被引导类加载器加载。

如果希望看详情,使用-verbose 参数,为了看的更清楚,使用重定向,大致为(windows)

java -verbose -Xbootclasspath/a:myjar.jar MainClass -> C:/out.txt

通过这个参数我们可以实现自己的系统类,比如替换掉java.lang.Object 的实现,自己可以扩展一些方法,不过这样做似乎没有好处,因为那就不是标准了。

 

系统类加载器(App加载器):

我们最关心的还是系统类加载器,一般都认为系统类加载器是加载应用程序第一个类的加载器,也就是java MainClass 命令中加载MainClass 的类加载器,这种说法虽然不是很严谨,但基本上还是可以这样认为的,因为我们很少会改变引导类加载器和扩展类加载器的默认行为。应该说系统类加载器负责加载classpath 路径中的而且没有被扩展类加载器加载的类 ( 当然也包括引导类加载器加载的) 。如果classpath 中有这个类,但是这个类也在扩展类加载器的类路径,那么系统类加载器将没有机会加载它。

我们很少改变扩展类加载器的行为,所以一般你自己定义的类都是系统类加载器加载器的。

 

如何获得系统类加载器?

获得系统类加载器非常简单,假设MyClass 是你定义的一个类

MyClass.class.getClassLoader() 返回的就是系统类加载器 ,当然这种方法无法保证绝对正确。

 

我们可以使用更简单而且一定正确的方式:

ClassLoader.getSystemClassLoader() 获得系统类加载器。我们知道ClassLoader 是一个抽象类,所以系统类加载器肯定是ClassLoader 的一个子类实现。我们来看看它是什么

ClassLoader.getSystemClassLoader().getClass() ;

结果是class sun.misc.Lancher$AppClassLoader  

可以看出这是sun 的一个实现,从名字可以看出是一个内部类,目前我也没有看到这个源代码,似乎还不是很清晰:

我们在看看它的父类是什么:

ClassLoader.getSystemClassLoader().getClass().getSuperclass();

结果是:class java.net.URLClassLoader

这个是j2se 的标准类,它的父类是SecureClassLoader,SecureClassLoader 是继承ClassLoader 的。

现在整个关系应该很清楚,我们会看到几乎所有的ClassLoader 实现都是继承URLClassLoader 的。

因为系统类加载器是非常重要的,而且是我们可以直接控制的,所以我们后面还会介绍,不过先来看一下扩展类加载器以及它们之间的关系。

 

 

扩展类加载器:

扩展类加载器似乎是一个不起眼的角色,它负责加载java 的标准扩展(jre/lib/ext 目录下的所有jar) ,它其实就是一个普通的加载器,看得见摸得着的。

首先的问题是怎么知道扩展类加载器在哪里?

的确没有直接途径获得扩展类加载器,但是我们知道它是系统类加载器的父加载器,我们已经很容易的获得系统类加载器了,所以我们可以间接的获得扩展类加载器:

ClassLoader.getSystemClassLoader().getParent().getClass();

其实是通过系统类加载器间接的获得了扩展类加载器 ,看看是什么东西:

结果是:class sun.misc.Launcher$ExtClassLoader

这个类和系统类加载器一样是一个内部类,而且定义在同一个类中。

同样看看它的父类是什么:

ClassLoader.getSystemClassLoader().getParent().getClass().getSuperclass();

可以看出结果也是class java.net.URLClassLoader

扩展类加载jre/lib/ext 目录下的所有类,包括jar, 目录下的所有类( 目录名不一定要classes).

现在可以回答上面的问题了,你写一个HelloWorld ,放到jre/lib/ext/ 下的某个目录。比如 jre/lib/ext/myclass/HelloWorld.class

然后在你classpath 也设置一份到这个类的路径,结果执行java HelloWorld 时,这个类是被扩展类加载器加载器的,可以这样证明

public static void main(String[] args){

        System.out.println("loaded by"+HelloWorld.class.getClassLoader().getClass());

        System.out.println("Hello World");

}

结果可以得到class sun.misc.Launcher$ExtClassLoader

当然如果你把jre/lib/extmyclass 这个目录删除,仍然可以运行,但是这样结果是

class sun.misc.Lancher$AppClassLoader

如果你不知道这个过程的话,假设在你扩展类路径下有一份classpath 中的拷贝,或者是比较低的版本,当你使用新的版本时会发现没有起作用,知道这个过程你就不会觉得奇怪了。另外就是两个不同的类加载器是可以加载一个同名的类的,也就是说虽然扩展类加载器加载了某个类,系统类加载器是可以加载自己的版本的, 但是现有的实现都没有这样做,ClassLoader 中的方法是会请求父类加载器先加载的,如果你自己定义类加载器完全可以修改这种默认行为,甚至可以让他没有父加载器。

 

这里给出一个方法如何获得扩展类加载器加载的路径:

    String path=System.getProperty("java.ext.dirs");

        File dir=new File(path);

        if(!dir.exists()||!dir.isDirectory()){

            return Collections.EMPTY_LIST;

        }

        File[] jars=dir.listFiles();

        URL[] urls=new URL[jars.length];

        for(int i=0;i<jars.length;i++){

            urls[i]=sun.misc.URLClassPath.pathToURLs(jars[i].getAbsolutePath())[0];

        }

        return Arrays.asList(urls);

 

对于扩展类加载器我们基本上不会去关心,也很少把你自己的jar 放到扩展路径,大部分情况下我们都感觉不到它的存在,当然如果你一定要放到这个目录下,一定要知道这个过程,它会优先于classpath 中的类

 

现在我们应该很清楚知道某个类是哪个加载器加载的,并且知道为什么是它加载的,如果要在运行时获得某个类的类加载器,直接使用ClassgetClassLoader() 方法就可以了。

 

用户定义的类一般都是系统类加载器加载的, 我们很少直接使用类加载器加载类, 我们甚至很少自己加载类。因为类在使用时会被自动加载,我们用到某个类时该类会被自动加载, 比如new A() 会导致类A 自动被加载,不过这种加载只发生一次

我们也可以使用系统类加载器手动加载类,ClassLoader 提供了这个接口

ClassLoader.getSystemClassLoader().loadClass("classFullName ");

这就很明确的指定了使用系统类加载器加载指定的类, 但是如果该类能够被扩展类加载器加载, 系统类加载器还是不会有机会的。

我们最常用的还是使用Class.forName 加载使用的类,这种方式没有指定某个特定的ClassLoader, 会使用调用类的ClassLoader 。【前面一篇已经说过了这个问题,forName将会调用c语言实现的函数forName0来实现这个过程】

也就是说调用这个方法的类的类加载器将会用于加载这个类。比如在类A 中使用Class.forName 加载类B, 那么加载类A 的类加载器将会用于加载类B, 这样两个类的类加载器是同一个 【系统加载器】。

 

 

四, 如何得到系统类加载器加载了那些类

最后讨论一下如何获得某个类加载器加载了哪些类,这个似乎有一定的使用价值,可以看出哪些类被加载了。其实这个也不是很难,因为ClassLoader 中有一个 classes 成员变量就是用来保存类加载器加载的类列表,而且有一个方法

void addClass(Class c) { classes.addElement(c);}

这个方法被JVM 调用

我们只要利用反射获得classes 这个值就可以了, 不过classes 声明为private, 我们需要修改它的访问权限( 没有安全管理器时很容易做到)

classes = ClassLoader.class.getDeclaredField("classes");

classes.setAccessible(true);

List ret=(List) classes.get(cl); //classes 是一个Vector

可惜的是对于引导类加载器没有办法获得加载的类,因为它是c 实现的,java 中很难控制了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值