本文摘自:《深入java虚拟机(第二版)》
Java 沙箱中 类装载器体系结构 第一道防线 。因为是由 类装载器 将代码(这个代码可能是恶意的或是有漏洞的)装人 Java虚拟机 中。 类装载器体系结构 三个方面 Java的沙箱 起作用:
A、它防止恶意代码去干涉善意的代码。
B、它守护了被信任的类库的边界。
C、它将代码归人某类(称为保护域),该类(保护域)确定了代码可以进行哪些操作。
类装载器体系结构 可以防止恶意的代码去干涉善意的代码 ,这是通过为由不同的类装载器装人的类提供不同的命名空间来实现的。命名空间由一系列唯一的名称组成,每一个被装载的类有一个名字,这个 命名空间 是由 Java 虚拟机 每一个类装载器 维护的。例如,一旦Java 虚拟机将一个名为volcano的类装人一个特定的命名空间,它就不能再装载名为Volcano的其他类到相同的命名空间了。可以把多个volcano类装人一个Java 虚拟机中,因为可以通过创建多个类装载器.
    命名空间 有助于安全的实现,因为你可以有效地在装入了不同命名空间的类之间设置一个 防护罩 在Java 虚拟机中,在同一个命名空间内的类可以直接进行交互,而不同的命名空间中的类甚至不能察觉彼此的存在,除非显式地提供了允许它们进行交互的机制 。一旦加载后,如果一个恶意的类被赋予权限访问其他虚拟机加载的当前类,它就可以潜在地知道一些它不应该知道的信息,或者干扰程序的正常运行。
       类装载器体系结构 守护了 被信任的类库的边界 这是通过 分别使用不同的类装载器装载可靠的包和不可靠的包来实现的 。虽然通过赋给成员受保护(或包访问)的访问限制,可以在同一个包中的类型间授子彼此访问的特殊权限,但这种特殊的权限只能授给在同一个包中的运行时成员,而且它们必须是由同一个 类装载器装载 的。
     用户 自定义类装载器 经常依赖 其他类装载器 (至少依赖 Java虚拟机 启动时创建的启动类装载器)来帮助它实现一些类装载请求。在java 1.2版本中, 类装载器 请求 其他类装载器 来装载类的过程被常态化,我们称之为 双亲委派模式 。从java 1.2版本开始,除了启动类装载器之外的类装载器,都有一个称之为双亲的类装载器。在某个特定的类装载器试图以自己的方式装载类以前,它会先默认地将这个任务“ 委派 “给它的双亲,请求它的 双亲 来装载这个类。这个双亲再依次请求它自己的双亲来装载这个类型;这个委派的过程一直向上继续,直到达到启动类装载器,通常 启动类装载器 是委派链中的最后一个类装载器。如果一个类装载器的 双亲类装载 器有能力来装载这个类,则首先由其 双亲类装载器 来装载这个类,否则,这个类装载器试图自己来装载这个类。
     在版本java 1.2 以前,大多数虚拟机的实现中,内置的类装载器〔 以后将称为原始类装载器)负责在 本地装载 可用的 class文件 .在版本java 1.2 中,装载本地可用的Class文件的工作被分配到 多个类装载器中 。对于刚才称为原始类装载器的内置的类装载器,我们重新命名为 启动类装载器 ,表示它现在只负责装载那些核心Java API的class.因为核心JAVA API的class文件是用于“启动”Java 虚拟机的class 文件,所义我们称它为 启动类装载器 .
         在版本1.2 中.由 自定义类装载器 (包括 系统自定义 用户 自定义装载器)来负责其他 class文件 的装载。这些 class文件 包括包括 标准扩展库的class文件 类路径中发现的类库class文件 应用程序本身的class文件 等等。1.2版本中,在应用程序启动以前, Java虚拟机 至少创建一个或多个 系统自定义类装载器 。所有这些 类装载器 被连接在一个 双亲->孩子 的关系链中,在这条链的顶端是启动类装载器,而在这条链的末端的类装载器,我称它为 系统 默认 类装载器 .它是指Java应用程序创建的、新的用户定义类装载器的默认委派双亲。 系统默认类装载器 它负责装载应用程序的初始类 ,它可以是任何 系统自定义类装载器 ,这是由实现Java平台的设计者决定的。例如,假设你写了一个Java应用程序,创建了一个类装载器,这个装载器是通过网络下载来装载class义件的.如果你在虚拟机上运行这个应用程序,那么在启动时,首先将实例化两个系统自定义类装载器: 一个"标准扩展"类装载器 一个“类路径”类装载器 。这些系统类装载器和启动类装载器一起联人一个双亲一孩了关系链中,如 图3-2 所示。类路径的类装载器的双亲是标准扩展"类装载器.而标准扩展"类装载器的双亲是启动类装载器。在 图3-2 中, 类路径类装载器 被设计成 系统默认类装载器 ,新的 用户自定义类装载器 的默认委派双亲将是 系统默认类装载器 (即 类路径类装载器 )。因此当应用程序实例化它的 网络类装载器 时,它将把 系统默认类装载器 作为它的 双亲
类装载器体系结构 - hubingforever - 民主与科学
 
      设想在运行Java 应用程序的时,首先向 类装载器 (这里是 网络类装载器 )发出一个装载Volcano类的请求, 类装载器 ( 网络类装载器 )必须先询问它的双亲(这里是 类路径类装载器 )来查找并装载这个类。这个 类路径类装载器 依次将向它的双亲发出同样的请求,这里它的双亲即为 标准扩展类装载器 标准扩展类装载器 也是首先将这个请求委派给它自己的双亲( 启动类装载器 )。假设Volcano类不是Java Apl 的一部分,也不是 标准安装扩展 的一部分,也不在 类路径 上,那么这些类装载器将返回而不会提供一个名为Volcano的己装载类。当 类路径类装载器 回答,它和所有它的双亲都不能装载这个类时, 网络类装载器 将试图用它自己特定的方式来装载Volcano类,它会通过网络下载Volcano。如果 网络类装载器 下载并装载volcano类成功的话,这样volcano类就可以在应用程序以后的执行过程中发挥作用。我们继续讨论这个例子,假设以后的某一时刻volcano类的一个方法首次被调用,并且那个方法引用了Java API中的java.util.HashMap,因为这个引用是首次被运行的程序使用,所以虚拟机会请求你的 类装载器 ( 网络类装载器 )来装载java.util.HashMap.就像以前一样,你的 类装载器 首先将请求传递给它的双亲类装载器,然后这个请求一路委派直到委派给 启动类装载器 。但是这一次, 启动类装载器 可以将java.util.HashMap 类返回给你的 类装载器 ,因为启动类装载器可以找到这个类,所以 标准扩展类装载器 就不必在标准扩展类库中查找这个类型, 类路径类装载器 也不必在类路径中查找这个类型,同样,你的类装载器也不必从网上下载这个类。所有这些类装载器仅需要返回由 启动类装载器 装载返回的类java.util.HashMap。从这一时刻开始,不管何时Volcano类引用名为java.util.HashMap的类,虚拟机就可以直接使用这个java.util.HashMap类了。
         在给出类装载器怎样工作的背景后,读者可能想知道如何使用类装载器来保护可信任类库。在有双亲委派模式的情况下, 启动类装载器 可以抢在 标准扩展类装载器 之前去装载类,而 标准扩展类装载器 可以抢在 类路径类装载器 之前去装载那个类, 类路径类装载器 又可以抢在 网络类装载器 之前去装载它。这样,在使用双亲一孩子委派链的方法中, 启动类装载器 会在最可信的类库(核心Java API)中首先检查每个被装载的class,然后,才依次到标准扩展、类路径上的本地文件中进行检查。所以如果网络类装载器装载的某段代码试图通过网络下载装载一个和JAVA API同门的class,比如 java.lang.Integer ,将不能成功。 java.lang.Integer class文件 在Java的API中已存在,它将被启动类装载器抢先装载,而 网络类装载器 将没有机会下载并装载名为java.lang.Integer的类,它只能使用由它的双亲返回的类,这个类是由 启动类装载器装载 的。用这种方法, 类装载器 的体系结构就可以防止不可靠的代码用它们自己的版本来替代可信任的类。
     但是如果这个移动代码不是去试图替换一个被信任的类,而是想在一个看似被信任的包中插入一个全新的类型 ,情况会怎样呢?想像一下,如果刚才那个例子中,要求 网络类装载器装载 一个名为java.lang.virus(病毒)的类时,将会发生什么?像以前一样,这个请求将被一路向上委派给 启动类装载器 ,虽然这个 启动类装载器 负责装载核心Java API的class,但是在核心的Java库中无法找到java.lang.virus,假设这个类在 标准扩展库 以及本地类路径中也找不到,你的 类装载器 将试图从网络上下载这个类。假设你的 类装载器 成功地下载并定义了这个名为java.long.virus的类. Java允许在同一个包中的类拥有彼此 包可见 的这个特殊权限,而这个包外的类则没有这个权限。所以,因为你的类装载器装载了一个名为java.lang.Virus的类,这个类假装是Java API 的一部分,希望得到访间核心java.lang中 包可见 的特殊访问权限,希望使用这个特殊的访问权限达到不可告人的目的。类装载器机制可以防止这个代码得到访问核心java.lang包中 包可见 的访问权限,因为 Java虚拟机 只把彼此包可见访问的特殊权限授子由 同一个类装载器装载到同一个包中 的class。因为Java API的java.lang中的类(被信任类)是由 启动类装载器装载 的,而恶意的类java.lang.virus是由 网络类装载器装载 的,所以这些类型不属于同一个 运行时包 运行时包 这个名词,是在Java虚拟机第2版规范中第一次出现的,它指由 同一个类装载器装载的属于同一个包的多个class的集合 。在允许两个class之间对包内可见的成员(声明为受保护的或包访问的成员)进行访问前, 虚拟机不但要确定这两个类型属于同一个包,还必须确认它们属于同一个运行时包 它们必须是由同一个类装载器装载的 。这样,因为java.lang.virus和来自核心Java API的java.lang的类不属于同一个运行时包,java.lang.virus就不能访间Java API的java.lang包中的包可见类,包可见方法及包内可见的成员。
      运行时包的概念是针对使用不同的 类装载器装载 不同的类而提出的。 启动类装载器 装载核心Java API的class文件,这些Class文件是最可信的. 标准扩展类装载器 装载来自于任何标准扩展的class文件,标准扩展是非常可信的,但这个可信度是在一定程度上的可信度,它们不能简单地通过将新类型插人到Java API的包中来获得对包内可见成员的访问权,这是因为标准扩展是由不同于核心API的类装载器装载的。同样,由 类路径类装载器 在类路径中发现的代码不能访问标准扩展或Java API 中的包内可见成员。
      类装载器 可以 用另一种方法来保护被信任的类库的边界,它只需通过简单地拒绝装载特定的禁止类型就可以了 。例如,你可能己经安装了一些包,这些包中包含了应用程序需要装载的类,这些类必须是由 网络类装载器 的双亲(类路径类装载器)装载的,而不是由 网络类装载器装载 的。假设己经创建了一个名为absollltoPower的包,并且将它安装在了本地类路径中的某个地方,在这里它可以被 类路径类装载器 访问到。而且假设你不想让 类装载器装载 absollltoPower包中的任何类。在这种情况下,你需要自己编写一个类装载器,让它检查请求装载的类是否是absollltoPower包中的类。如果是的话,你的类装载器就应该抛出一个安全异常,而不是将这个请求传给 双亲类装载器 。类装载器要知道一个类是否来源于一个被禁止的包,直接检查它的类名(完整形式)就知道了。
   除了设置不同命名空间中的类以及保护被信任的类库的边界外,类装载器还起到另外的安全作用。 类装载器将每一个被装载的类放置在一个保护域中,并在保护域名中定义了在运行时它将得到怎样的权限
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐