现在的位置: 首页 > 综合 > 正文

Java ClassLoader深入研究

2013年12月02日 ⁄ 综合 ⁄ 共 13786字 ⁄ 字号 评论关闭
文章目录

参考文章:

http://blog.csdn.net/lovingprince/archive/2009/06/03/4238695.aspx

http://www.yesky.com/243/1840743.shtml

 

Java为了提供平台无关性,在操作系统之上加入了一层JVM来隔离操作系统特定实现,使所编写的java代码在任何平台都能运行,但是JVM是特定于某一操作系统的

 

 

一、当JVM启动时,由三个类加载器对类进行加载:

1.bootstrap classloader

2.extension classloader
3.system classloader

 

(1)bootstrap classloader[
引导类加载器]

是由JVM实现的,不是java.lang.ClassLoader的子类
,它负责加载Java的核心类,其加载的类由
sun.boot.class.path指定,或者在
执行java命令时使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值

 

说明:

-Dproperty_name=property_value  指定属性的值;

-Xbootclasspath   改变虚拟机装载缺省系统运行包rt.jar,而从-Xbootclasspath中设定的搜索路径中装载系统运行类

 

输出加载的核心类库:

输出结果:

file:/E:/MyEclipse%205.1.1%20GA/jre/lib/rt.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/i18n.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/sunrsasign.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/jsse.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/jce.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/lib/charsets.jar
file:/E:/MyEclipse%205.1.1%20GA/jre/classes

 

(2)
extension classloader[扩展类加载器]
,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或者由java.ext.dirs系统属性指定的)中JAR的类包。这为引入除Java核心类以外的新功能提供了一个标准机制。在这个实例上调用方法getParent()总是返回空值null,因为引导加载器bootstrap classloader不是一个真正的ClassLoader实例。

输出结果:

E:/MyEclipse 5.1.1 GA/jre/lib/ext
the parent of extension classloader : null


以上这段代码也表明了下列父子关系或加载顺序:

bootstrap classloader(因其不是ClassLoader的子类,null)-->extension classloader-->system classloader

这也表明了jvm在加载类的顺序,当加载一个类时(假设其未加载),先找到最顶层的classloader,如果其可以加载这个类(或者已经加载了这个类),则返回这个类;如果其不能加载类(换个说法:在相应路径中搜索不到相应类),则用其子classloader加载,直到这个类被加载或者抛出相应的异常

这个顺序保证了越重要的类,越先加载;因为一个类只被加载一次(cache),所以如java.lang.System这个类,不能被用户替换(因为是按照bootstrap-->extension-->system的顺序,当要加载java.lang.System类时,其首先从bootstrap的搜索路径中找类)


(3)system/application classloader [系统(应用)类加载器]
,加载来自-classpath或者java.class.path系统属性或者CLASSPATH操作系统属性所指定的JAR类包和类路径。

a.可以通过静态方法ClassLoader.getSystemClassLoader()找到该类加载器。

b.如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。可以从源代码看出这一点:

classpath路径:


结果为:

E:/dev/java/eclipse/TestClassLoader/classes;E:/dev/java/lib/commons-logging-1.1.jar

 

重要:

classloader加载类用的是全盘负责委托机制。

a.全盘负责,即是当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入

b.委托机制则是先让parent类加载器 (而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。此外类加载还采用了cache机制,也就是如果 cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。

二、ClassLoader加载Class的过程

1.检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2
2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
3.请求parent classloader载入,如果成功到8,不成功到5
4.请求jvm从bootstrap classloader中载入,如果成功到8
5.寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
6.从文件中载入Class,到8.
7.抛出ClassNotFoundException.
8.返回Class.

 

源代码如下:

可以看到当在parent链classloader中和bootstrap classloader中都找不到相应的类时,会调用findClass方法,因此ClassLoader的子类可以重写这个方法,定义自己的找到类的方法


 

以下是一些类的ClassLoader示例:

输出:

null        其路径在sun.boot.class.path中,由bootstrap classloader加载,所以返回为null(因其不是ClassLoader的子类)

null       
其路径在sun.boot.class.path中,由bootstrap classloader加载

sun.misc.Launcher$ExtClassLoader@7259da      路径在java.ext.dirs中,由extension classloader加载
sun.misc.Launcher$AppClassLoader@197d257   由classpath指定,由system classloader加载

 

查看sun.misc.Launcher的源代码

可以看到是由Launcher这个类初始化ExtClassLoader和AppClassLoader类的

ExtClassLoader无parent,而AppClassLoader的parent为
ExtClassLoader

 

Launcher的getClassLoader()方法

 

ExtClassLoader

 

AppClassLoader

 

Launcher类的getBootstrapClassPath()方法

从以上代码中可以看到bootstrap classloader使用sun.boot.class.path来加载类,extension classloader使用java.ext.dirs来加载类,而system classloader使用java.class.path来加载类

 

运行下列程序:

结果为:

null

表明Launcher是由bootstrap classloader来加载的

 

三、关于Context ClassLoader

在上面Launcher的构造函数中有这么一句:
Thread.currentThread().setContextClassLoader(loader); 这句是设置当前线程的classloader,默认是使用的AppClassLoader

这个有什么作用呢?

当线程需要用到某个类,contextClassLoader被请求来载入该类

注意:

(1)Class.forName(String name)载入的是在系统中已经加载入sun.boot.class.path、
java.ext.dirs、
java.class.path路径中的类,而在这几个路径中未加入的类不能载入(报异常)

(2)Class.forName(String name, boolean initialize, ClassLoader loader)可以载入上述三个路径中没有的类,只要指定你的classloader即可

(3)利用ClassLoader可以载入在上述三个路径中没有的类

示例:

运行结果为:

Thread[main,5,main]  sun.misc.Launcher$AppClassLoader@197d257
Thread[Thread-0,5,main]  sun.misc.Launcher$AppClassLoader@197d257
Thread[Thread-1,5,main]  Test.TestClassLoader@1004901
Thread[Thread-1,5,main]
Test

这也表明了线程间ContextClassLoader的继承性:

(1)main线程默认的
ContextClassLoader为AppClassLoader

(2)新启动线程从原线程处继承
ContextClassLoader

 

四、关于ClassLoader和Package

其中:String(int offset, int len, char[] arr)为包访问权限

此代码可以编译通过,但是运行时出现下列错误:

java.lang.SecurityException: Prohibited package name: java.lang
 at java.lang.ClassLoader.preDefineClass(Unknown Source)
 at java.lang.ClassLoader.defineClass(Unknown Source)
 at java.security.SecureClassLoader.defineClass(Unknown Source)
 at java.net.URLClassLoader.defineClass(Unknown Source)
 at java.net.URLClassLoader.access$100(Unknown Source)
 at java.net.URLClassLoader$1.run(Unknown Source)
 at java.security.AccessController.doPrivileged(Native Method)

 

这表明:

Java语言规定,在同一个包中的class,如果没有修饰符,默认为Package权限,包内的class都可以访问。但是这还不够准确。确切的说,只有由同一个ClassLoader装载的class才具有以上的Package权限。比如Bootstrap classloader装载了java.lang.String,AppClassLoader装载了我们自己写的java.lang.TestPackage,它们不能互相访问对方具有Package权限的方法。这样就阻止了恶意代码访问核心类的Package权限方法。


 

五、关于两个ClassLoader载入同一个类

注意:由两个不同的ClassLoader载入的同一个类,其是不同类型的,因此如果进行赋值会报ClassCastException

示例:

 运行以上代码结果:

sun.misc.Launcher$AppClassLoader@197d257

Test.TestClassLoader@1b90b39

Exception in thread "main" java.lang.ClassCastException: Test.TestAImp1
 at Test.TestLoaderA.main(TestClassLoader.java:84)

抱歉!评论已关闭.