java 装载_java类装载器详细介绍

本文深入解析Java虚拟机中的类装载器子系统,包括启动类装载器与用户自定义装载器的区别,类装载流程、命名空间、委托加载机制及安全性设计。重点讲解了ClassLoader的使用场景、JVM运行时的ClassLoader结构,以及为何采用复杂设计以保障应用安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

af7540887099818b7c129cca846dcc89.png

Java类装载器子系统

在Java虚拟机中,负责查找并装载类型的那部分被称为类装载子系统。上图描述了,类装载器在JVM组成中的位置。

Java虚拟机有两种类装载器:启动类装载器和用户自定义类装载器。前者是Java虚拟机实现的一部分,后者则是Java程序的一部分。由不同的类装载器装载的类将被放在虚拟机内部的不同命名空间中。

类装载器子系统涉及Java虚拟机的其他几个组成部分,以及几个来自java.lang库的类。比如,用户自定义的类装载器是普通的Java对象,他的类必须派生自java.lang.ClassLoader类。ClassLoader中定义的方法为程序提供了访问类装载器机制的借口。此外,对于每一个被装载的类型,Java虚拟机都会为它创建一个java.lang.Class类的实例来代表该类型。和其他所有对象一样,用户自定义的类装载器以及Class类的实例都放在内存中的堆区,而装载的类型信息则都位于方法区。

装载、连接以及初始化

类装载器子系统出来要定位和导入二进制class文件外,还必须负责验证被导入类的正确性,为类变量分配并初始化内存,以及帮助解析符号引用。这些动作必须严格按以下顺序进行:1)装载(Load)-------查找并装载类型的二进制数据

2)连接(Link)-------执行验证,准备,以及解析(可选)。

验证(Verify)   确保被导入类型的正确性

准备(Prepare)  为类变量分配内存,并将其初始化为默认值

解析(Resolve)   把类型中的符号引用转换为直接引用

3)初始化(Initialize)------把类变量初始化为正确初始值

启动类装载器

只要符合Java class文件格式的二进制文件,Java虚拟机实现都必须能够从中辨别并装载其中的类和接口。某些  虚拟机实现也可以识别其他的非规范的二进制格式文件,但它必须能够辨别class文件。

每个java虚拟机实现都必须有一个启动类装载器,它知道怎么装载受信任的类,比如java api的class文件。Java虚拟机规范并未规定启动类装载器如何去寻找class文件,这又是一件保留给具体的实现设计者去决定的事情。

只要给定某个类型的全限定名,启动类装载器就必须能够以某种方式得到定义该类型的数据。比如,在Window 98平台上,Sun的JDK 1.1是这样工作的:首先它逐个搜索用户在CLASSPATH环境变量中定义的目录,直到找到一个名为”该类型名+.class“的文件为止。除非该类型属于某个未命名的包,否则启动类装载器会在CLASSPATH包含的目录的子目录中寻找这样一个文件,这些子目录的路径名是根据类型的包名称构建的。比如,如果启动类装载器正在搜索这样一个类:java.lang.Object,那么它将在每一个CLASSPATH包含的目录下,查找java\lang这样的一个字目录,以及其中的Object.class文件。

在Sun的JDK 1.2中,与1.1版本不同,启动类装载器将只在系统类(java api的类文件)的安装路径中查找要装入的类;而搜索CLASSPATH目录的任务,现在交给了系统类装载器-----它是一个自定义的类装载器,当虚拟机启动时就被自动创建。

用户自定义类装载器

尽管”用户自定义类装载器“本身是java程序的一部分,但类ClassLoader中的四个方法时通往Java虚拟机的通道:

protected Class> defineClass(String name, byte[] data, int off, int len) ;

protected Class> defineClass(String name, byte[] data, int off, int len, ProtectionDomain protectionDomain) ;

protected Class> findSystemClass(String name);

protected void resolveClass(Class> c) ;

任何Java虚拟机实现都必须把这些方法连到内部的类装载器子系统中。

两个被重载的defineClass()方法都要接受一个名为data的字节数组作为输入参数,并且在data[offset]到data[offset + length]之间的二进制数据必须符合Java class文件格式-------它表示一个新的可用类型。而name参数是个字符串,他指出该类型的全限定名。当使用第一个defineClass()时,该类型将被赋予默认的保护域。使用第二个defineClass()时,改类型的保护域将有他的protectionDomain参数指定。每个Java虚拟机实现都必须保证ClassLoader类的defineClass()方法能够把新类型导入到方法区中。

findSystemClass()方法接受一个字符串作为参数,他指出将被装入类型的全限定名。在版本1.0和版本1.1中,这个方法会通过启动类装载器来装载指定类型。如果启动类装载器装载完成,他会返回对Class对象(改对象描述了该类型)的引用。如果没有找到相应的class文件,他会抛出ClassNotFoundException异常。在版本1.2中,findSystemClass()方法使用系统类装载器类装载指定类型。任何Java虚拟机实现都必须保证fidnSystemClass()方法能够以这种方式调用启动类装载器(如果运行1.0或版本1.1),或者系统类装载器(如果运行版本1.2或以上)。

resolveClass()方法接受一个Class实例的引用作为参数,他将对该Class实例表示的类型执行连接动作。而前面提到的defineClass()方法则只负责装载。当defineClass()方法返回一个CLass实例时,也就表示指定的class文件已经被找到并装载到方法区了,但是却不一定被连接和初始化了。Java虚拟机必须保证ClassLoader类的resolveClass()方法能够让类装载器子系统执行连接动作。

关于Java虚拟机怎样执行装载、连接以及初始化动作,将在后续章节中阐述。

命名空间

每个类装载器都有自己的命名空间,其中维护着由它装载的类型。所以一个Java程序可以多次装载具有同一个全限定名的多个类型。这样一个类型的全限定名就不足以确定在一个Java程序可以多次装载具有同一个全限定名的多个类型。因此,当多个类装载器都装载了同名的类型时,为了唯一地标识该类型,还要在类型名称前面加上一个装载该类型(指出了他所位于的命名空间)的类装载器的标识。

java虚拟机中的命名空间,其实是解析过程的结果。对于每个被庄子啊的类型,Java虚拟机都会记录装载它的类装载器,当虚拟机解析一个类到另一个类的符号引用时,他需要被引用类的类装载器。

几点说明:

1. 自定义类装载器何时使用?

Java应用程序能够在运行时安装用户自定义的类装载器,这种类装载器能够使用自定义的方式来装载类,例如,从网络下载class文件,从数据库获取,甚至可以动态生成。尽管启动类装载器是虚拟机实现的本质部分,而用户自定义的类装载器不是;但是用户自定义的类装载器能够用java编写,能够被编译为class文件,能够被虚拟机装载,还能够像其他对象一样实例化。他们实际上只是运行中的java应用程序可以执行代码的一部分。至此应该明白自定义类装载器应该何时使用了。对于需要动态加载java文件,可以使用用户自定义类加载器。由于有自定义类装载器的存在,使得在运行时扩展java应用程序成为可能。

再多说两句,每一个类被装载的时候,Java虚拟机都件事这个类,看他到底是被启动类装载器还是被用户定义类装载器装载。当被装载的类引用了另外一个类时,虚拟机就会使用装载第一个类的类装载器装载被引用的类。

c0704b3fe0f1e88f0084739439f41af1.png

上图是Java类装载器的体系结构。

由于Java虚拟机采用这种方式进行类的装载,所以被装载的类默认情况下只能看到被同一个类装载器装载的别的类。通过这种方法,Java的体系结构允许在一个Java应用程序中建立多个命名空间。运行时的Java程序中的每个类装载器都有一个他自己的命名空间。

一个Java应用程序能够从同一个类或者多个类中实例化多个用户定义的类装载器。因此,需要多少个(或者多少种)用户自定义的类装载器,java应用程序就可以创建多少个(或多少种),被不同的类装载器装载的类存放在不同的命名空间中,他们不能互相访问,除非应用程序显示地允许这样做。当编写一个Java应用程序的时候,从不同源文件装载的类可以分割在不同的命名空间中。通过这种方法,就能够使用Java类装载器的体系结构来控制从不同源文件中装载的代码之间的相互影响,特别是能够阻止恶意代码获取访问和破坏善意代码的权限,可以看出Java类装载器的体系结构提供了对网络移动性的支持。

Web浏览器是一个动态扩展的例子,Web浏览器使用用户自定义的类装载器从网络上下载用于Java applet的class文件。web浏览器使用一个用来安装用户自定义类装载器的Java应用程序,这个用户定义的类装载器通常被称为Java applet类装载器,他知道如何向Http服务器请求class文件。Java applet可以作为动态扩展的例子,因为当浏览器遇到有Java applet的页面的时候,才决定是否需要下载class文件。

Web浏览器启动的Java应用程序通常为每个提供class文件的网络地址分步创建不同的用户自定义类装载器,因此,不同的用户自定义类装载器庄子不同来源的class文件。这就可以吧他们分别放置在Java主机应用程序的不同命名空间之中。由于不同来源的Java applet代码不会直接妨碍到从别的地方下载的class文件。

2.JVM运行时的ClassLoader介绍

JVM在运行时会产生三个ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.

其中,Bootstrap是用C++编写的,我们在Java中看不到它,是null。它用来加载核心类库,在JVM源代码中这样写道:

static const char classpathFormat[] =

"%/lib/rt.jar:"

"%/lib/i18n.jar:"

"%/lib/sunrsasign.jar:"

"%/lib/jsse.jar:"

"%/lib/jce.jar:"

"%/lib/charsets.jar:"

"%/classes";

Extension ClassLoader用来加载扩展类,即/lib/ext中的类。

最后AppClassLoader才是加载Classpath的。

ClassLoader加载类用的是委托模型。即先让Parent类(而不是Super,不是继承关系)寻找,Parent找不到才自己找。

三者的关系为:AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为Bootstrap ClassLoader。

加载一个类时,首先BootStrap先进行寻找,找不到再由ExtClassLoader寻找,最后才是AppClassLoader。

为什么要设计的这么复杂呢?其中一个重要原因就是安全性。比如在Applet中,如果编写了一个java.lang.String类并具有破坏性。

假如不采用这种委托机制,就会将这个具有破坏性的String加载到了用户机器上,导致破坏用户安全。但采用这种委托机制则不会出现这种情况。

因为要加载java.lang.String类时,系统最终会由Bootstrap进行加载,这个具有破坏性的String永远没有机会加载。

48ebd16dad04bb0c291f83361ef3441e.png

大小: 63.3 KB

951cd93a9e4136ccf021a6b88476b133.png

大小: 49.1 KB

d76a8c980c0f0e67beec549176291cf8.png

大小: 42.5 KB

20d3042159bbe0cd484d33e52263ee41.png

大小: 31.3 KB

9554f5522b4ed05da8ed6876c4ea5e9b.png

大小: 35.3 KB

分享到:

18e900b8666ce6f233d25ec02f95ee59.png

72dd548719f0ace4d5f9bca64e1d7715.png

2011-06-17 17:27

浏览 7333

评论

2 楼

boy00fly

2011-06-22

trunk 写道

楼主什么时候出个gc的详细分析啊 期待啊

966903dea4bcb507358d5dcce8b912e5.gif  gc的工作原理迟早会写篇文章介绍下的,哈哈。敬请期待吧!!

1 楼

trunk

2011-06-22

楼主什么时候出个gc的详细分析啊 期待啊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值