《JAVA深度历险》札记(二)深入类加载器
预先载入与依照需求载入
我们自己所写的类,只要在用到(new)的时候才会给载入(依照需求载入),而基础类库在运行的时候就一次性全部加载到内存里面(预先载入)。由于基础类库是经常需要使用到的,因此一次性就载入。而自己写的类可能有成千上万个,按照需求载入防止内存一下子占用过多,这是java最初为小内存设备使用所考量的特点。
Java程序动态性的两种方法
JAVA提供的动态性
Class的forname进行动态加载类,其实内部是使用了ClassLoader.getCallerClassLoader()來取得载入调用他的类所使用的类加载器。(注意,ClassLoader.getCallerClassLoader()是一个private 的方法,所以我们无法自行调用,因此必须先new一個调用类的实体,再去取得类加载器)。
用显式的方法来达到动态性:直接使用类加载器
-
要直接使用类加载器帮我们加载类,首先必须先取得指向类加载器的对象,而要取得指向类加载器的对象之前,必须先取得某对象所属的类。
-
Object 里有一个名为getClass()的方法,用来取得某个特定实体所属类的对象,这个对象,指向的是一个名为Class类(Class.class)的对象。我们无法自行产生一个 Class 类的对象,因为它的构造器被声明成private,这个Class类的实体是在.class文件第一次加载内存时就建立的,以后在程序中产生任何该类的对象,这些对象的内部都会有一个字段记录着这个Class类别的所在位置。
-
Class 类的对象中,都会有字段记录着加载它的ClassLoader的对象。(有可能为null,这个代表它的类加载器是bootstrap)。我们取得了一个class对象,用getClassLoader()方法就能得到他的类加载器。
类别加载器的阶层体系
虚拟机一启动,会先做一些初始化的动作,比方说抓取系统参数等。
一旦初始化动作完成之后,就会产生第一个类加载器,即所谓的 Bootstrap Loader,Bootstrap Loader 是由 C++所撰写而成(所以以 Java的观点来看,逻辑上并不存在Bootstrap Loader 的类别实体,所以在Java程序代码里试图印出其内容的时候,我们会看到的输出为 null)。
这个 BootstrapLoader所做的初始工作中,除了也做一些基本的初始化动作之外,最重要的就是加载定义在 sun.misc 命名空间底下的Launcher.java 之中的ExtClassLoader(因为是inner class,所以编译之后会变成 Launcher$AppClassLoader.class),并设定其Parent为之前产生的ExtClassLoader实体。
注意,Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader所加载,所以Parent和由哪个类加载器加载没有关系。类加载器由谁加载(这句话有点诡异,类加载器也要由类加载器加载,这是因为除了Bootstrap Loader之外,其余的类加载器皆是由 Java 所写)。
类加载器的关系 加载顺序
AppClassLoader和ExtClassLoader 都是URLClassLoader的子类。由于它们都是 URLClassLoader的子类,所以它们也应该有URL。由原码中我们可以得知,AppClassLoader所参考的URL是从系统参数 java.class.path 取出的字符串所决定,而 java.class.path 则是由我们在执行 java.exe 时,利用 –cp 或-classpath 或 CLASSPATH 环境变量所决定。
AppClassLoader与 ExtClassLoader在整个虚拟机之中只会存有一份,一旦建立了,其内部所参考的搜寻路径将不再改变,也就是说,即使我们在程序里利用 System.setProperty()来改变系统参数的内容,仍然无法更动 AppClassLoader 与 ExtClassLoader 的搜寻路径。因此,执行时期动态更改搜寻路径的设定是不可能的事情。如果因为特殊需求,有些类别的所在路径并非在一开始时就能决定,那么除了产生新的类加载器来辅助我们加载所需的类之外,没有其他方法了。
委派模型 之所以有阶层体系的存在,是为了实现委派模型。所谓的委派模型,用简单的话来讲,就是类加载器有加载类的需求时,会先请其 Parent 使用其搜索路径帮忙加载,如果Parent找不到,那么才由自己依照自己的搜索路径搜寻。 类加载器可以看到parent所载入的类,但是反过来却不成立。 类加载器和其加载机制是一个非常复杂的系统,那么为何要设计这么复杂的系统呢? 除了可以达到动态性之外,其实最重要的原因莫过于安全性。我们以下面这张图来说明:
这张图说明了两件事情,第一假设我们利用 URLClassLoader到网络上的任何地方下载了其他的类,URLClassLoader 都不可能下载AppClassLoader、ExtClassLoader、或者Bootstrap Loader可以找到的同名类(指全名,套件名称+类别名称) ,因此,蓄意破坏者根本没有机会植入有问题的程序代码于我们的计算机之中(除非蓄意破坏者能潜入您的计算机,置换掉您计算机内的类,但是这已经不是Java 所涉及的安全问题了,而是操作系统本身的安全问题)。第二,类加载器无法看到其他相同阶层之类加载器所加载的类,如上图所示,图中绿色部分外是指从www.sun.com 下载程序代码的类加载器所能看到的类。告诉我们从www.sun.com 加载的类,无法看到www.xxx.com 加载的类别,这除了意味着不同的类加载器可以加载完全相同的类之外,也排除了误用或恶意使用别人程序代码的机会。