classLoader的基本理解

先看下图:
[img]http://dl2.iteye.com/upload/attachment/0108/4746/d658fa28-2e0e-3dcb-80ac-55df7140e6e2.jpg[/img]

有四个不同的类加载器:
1,启动类加载器:顾名思义,是一个在jvm启动时候的类加载器,由于此时要加载JAVA_HOME/lib中的类(也可以指定-Xbootclasspath为加载路径),所以这玩意不可能本身是java写就的,没错,它是用C++开发的。
2,扩展类加载器:由sun.misc.Launcher$ExtClassLoader实现,负责加载JAVA_HOME/lib/ext中的类;
3,应用程序类加载器:由sun.misc.Launcher$AppClassLoader实现,一般程序员所写的程序都是通过这个来默认加载的,这个加载器我们可以通过ClassLoader的getSystemClassLoader()来获得;
4,用户自定义类加载器:程序员自己通过继承ClassLoader来实现自己的类加载器;

import java.net.URL;
import java.net.URLClassLoader;

/*
分析BootstrapClassLoader/ExtClassLoader/AppClassLoader的加载路径
*
*/

public class ClassPath_of_Bootstrap_Ext_AppClassLoader
{
public static void main(String[] args)
{
System.out.println("BootstrapClassLoader 的加载路径: ");

URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for(URL url : urls)
System.out.println(url);
System.out.println("----------------------------");

//取得扩展类加载器
URLClassLoader extClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader().getParent();

System.out.println(extClassLoader);
System.out.println("扩展类加载器 的加载路径: ");

urls = extClassLoader.getURLs();
for(URL url : urls)
System.out.println(url);

System.out.println("----------------------------");


//取得应用(系统)类加载器
URLClassLoader appClassLoader = (URLClassLoader)ClassLoader.getSystemClassLoader();

System.out.println(appClassLoader);
System.out.println("应用(系统)类加载器 的加载路径: ");

urls = appClassLoader.getURLs();
for(URL url : urls)
System.out.println(url);

System.out.println("----------------------------");
}
}


输出结果:
[color=red]BootstrapClassLoader 的加载路径:
file:/C:/jdk1.7.0_01/jre/lib/resources.jar
file:/C:/jdk1.7.0_01/jre/lib/rt.jar
file:/C:/jdk1.7.0_01/jre/lib/sunrsasign.jar
file:/C:/jdk1.7.0_01/jre/lib/jsse.jar
file:/C:/jdk1.7.0_01/jre/lib/jce.jar
file:/C:/jdk1.7.0_01/jre/lib/charsets.jar
file:/C:/jdk1.7.0_01/jre/classes
----------------------------
sun.misc.Launcher$ExtClassLoader@3e389405
扩展类加载器 的加载路径:
file:/C:/jdk1.7.0_01/jre/lib/ext/dnsns.jar
file:/C:/jdk1.7.0_01/jre/lib/ext/localedata.jar
file:/C:/jdk1.7.0_01/jre/lib/ext/sunec.jar
file:/C:/jdk1.7.0_01/jre/lib/ext/sunjce_provider.jar
file:/C:/jdk1.7.0_01/jre/lib/ext/sunmscapi.jar
file:/C:/jdk1.7.0_01/jre/lib/ext/zipfs.jar
----------------------------
sun.misc.Launcher$AppClassLoader@a200d0c
应用(系统)类加载器 的加载路径:
file:/E:/JAVA/JVM_Class_Reflect_Thread/ClassPath_of_Bootstrap_Ext_AppClassLoader/bin/
----------------------------[/color]

ClassLoader抽象类的几个关键方法:

(1) loadClass

此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从parent ClassLoader中寻找,如仍然没找到,则从System ClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法

(2) findLoadedClass

此方法负责从当前ClassLoader实例对象的缓存中寻找已加载的类,调用的为native的方法。

(3) findClass

此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。

(4) findSystemClass

此方法负责从System ClassLoader中寻找类,如未找到,则继续从Bootstrap ClassLoader中寻找,如仍然为找到,则返回null。

(5) defineClass

此方法负责将二进制的字节码转换为Class对象

(6) resolveClass

此方法负责完成Class对象的链接,如已链接过,则会直接返回。


看了上面balabala一段,好,问题来了:
1,sun.misc.Launcher是什么gui?
2,各个不同的类加载器之间到底有啥优先级或是什么关系?
3,如果各个类加载器间是有顺序的,那是如何实现这种关系的?
4,自己实现一个类加载器到底有什么意义?
5,如果类加载的时候有优先级,这种是一定的?还是只是一种推荐的原则?

1:这是 JRE 类库,JDK 的 src 中没有,需要到下面这个地址去下载 OpenJDK 6 的源代码:
http://download.java.net/openjdk/jdk6/
压缩文件有 42.17MB,解压后有 254MB,拥有 28000 多个文件。
假如解压到 %OPENJDK6_HOME% 目录中,sun.misc.Launcher 位于 %OPENJDK6_HOME%/jdk/src/share/classes/sun/misc 目录里。

主要代码:
public class Launcher  {
public Launcher() {
ExtClassLoader extclassloader;
try {
//初始化extension classloader
extclassloader = ExtClassLoader.getExtClassLoader();
} catch(IOException ioexception) {
throw new InternalError("Could not create extension class loader");
}
try {
//初始化system classloader,parent是extension classloader
loader = AppClassLoader.getAppClassLoader(extclassloader);
} catch(IOException ioexception1) {
throw new InternalError("Could not create application class loader");
}
//将system classloader设置成当前线程的context classloader(将在后面加以介绍)
Thread.currentThread().setContextClassLoader(loader);
......
}
public ClassLoader getClassLoader() {
//返回system classloader
return loader;
}
}

extension classloader的部分代码:
static class Launcher$ExtClassLoader extends URLClassLoader {

public static Launcher$ExtClassLoader getExtClassLoader()
throws IOException
{
File afile[] = getExtDirs();
return (Launcher$ExtClassLoader)AccessController.doPrivileged(new Launcher$1(afile));
}
private static File[] getExtDirs() {
//获得系统属性“java.ext.dirs”
String s = System.getProperty("java.ext.dirs");
File afile[];
if(s != null) {
StringTokenizer stringtokenizer = new StringTokenizer(s, File.pathSeparator);
int i = stringtokenizer.countTokens();
afile = new File;
for(int j = 0; j < i; j++)
afile[j] = new File(stringtokenizer.nextToken());

} else {
afile = new File[0];
}
return afile;
}
}

system classloader的部分代码:
static class Launcher$AppClassLoader extends URLClassLoader
{

public static ClassLoader getAppClassLoader(ClassLoader classloader)
throws IOException
{
//获得系统属性“java.class.path”
String s = System.getProperty("java.class.path");
File afile[] = s != null ? Launcher.access$200(s) : new File[0];
return (Launcher$AppClassLoader)AccessController.doPrivileged(new Launcher$2(s, afile, classloader));
}
}


[color=red]extension classloader是使用系统属性“java.ext.dirs”设置类搜索路径的,并且没有parent。
system classloader是使用系统属性“java.class.path”设置类搜索路径的,并且有一个parent classloader。
Launcher初始化extension classloader,system classloader,并将system classloader设置成为context classloader,但是仅仅返回system classloader给JVM。[/color]

2:其实类加载器间有树状关系,这种关系又称为双亲委派模型,就是要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父加载器,当收到加载类的请求的时候,本身的类加载器不会自己去尝试加载这个类,而是把这个请求委派(代理)父类去加载,直到父加载器无法完成这个加载请求时,子加载器才会尝试自己去加载,但要注意的是,类加载器的父子概念不是通过继承来实现的,而是通过组合来复用父加载器代码(不明白是吧?下面要看一下loadClass方法的实现就可以了)

组合实现父子关系的解析:[color=red]父子关系在ClassLoader的实现中有一个 ClassLoader类型的属性[/color],我们可以在自己实现自定义的ClassLoader的时候初始化定义,而这三个系统定义的ClassLoader的父子关系分别是AppClassLoader————》(Parent)ExtClassLoader————————》(parent)BootClassLoader(null c++实现),就是为什么打印出来的ExtClassLoader的parent是null的原因,因为C++实现的bootClassLoader不算是java类

3:
protected synchronized Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {

//检查请求的类是否已经被加载过了
//JVM判断类是否被加载有两个条件:完整类名是否一样、ClassLoader是否是同一个。
Class c = findLoadedClass(name);
if(c == null){
try{
if(parent != null){
c = parent.loadClass(name,false);//丢给它爹
}else{
c = findBootstrapClassOrNull(name);//如果没爹,丢给老祖宗
}
}catch(ClassNotFoundException e){
//说明父类无法完成加载
}
if(c == null){ //只好自己加载了
c = find(name);
}
......
}
}


问题又来了:
6,什么时候会父加载器会加载失败?
暂时理解为每个类加载器都有一个指定的加载范围,也就是一个指定的加载目录,所以如果该请求加载的类超出了自己本身的加载目录范围,这个类加载器就会无能为力,让最初的子类自己加载了。


5:一般来说,父类优先的策略就足够好了。在某些情况下,可能需要采取相反的策略,即先尝试自己加载,找不到的时候再代理给父类加载器。这种做法在Java的Web容器中比较常见,也是Servlet规范推荐的做法。比如,Apache Tomcat为每个Web应用都提供一个独立的类加载器,使用的就是自己优先加载的策略。IBM WebSphere Application Server则允许Web应用选择类加载器使用的策略。

一个典型的例子是JNDI,JDBC,JCE,JAXB,JBI这些服务,因为需要调用由独立厂商实现并部署在应用程序的classPath下的JNDI提供者代码,就会出现父类加载器请求子类加载器去完成类加载动作。

热部署OSGI(未了解)

4,自己实现类加载器
类加载器及测试代码如下:

public class MyClassLoader extends ClassLoader {
private String path = "c:/bin/";

@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String namePath = name.replace(".", "/");
String classPath = path + namePath + ".class";
InputStream is = null;
ByteArrayOutputStream os = null;
try {
is = new FileInputStream(new File(classPath));
os = new ByteArrayOutputStream();
int b = 0;
while ((b = is.read()) != -1) {
os.write(b);
}
byte[] bytes = os.toByteArray();
os.flush();
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}finally{
try {
if(os!=null)
os.close();
if(is!=null)
is.close();
} catch (IOException e1) {
os=null;
is = null;
}
}
return null;
}

@SuppressWarnings("unchecked")
public static void main(String[] args) {
MyClassLoader myLoader = new MyClassLoader();
try {
Class myClass = myLoader.loadClass("com.ldh.loader.HelloWorld");
Object obj = myClass.newInstance();
Method method = myClass.getMethod("say", null);
method.invoke(obj, null);
} catch (Exception e) {
e.printStackTrace();
}
}
}

public class HelloWorld {
public void say(){
System.out.println("hello,world");
}
}


HelloWorld类放在c:/bin/目录下.

[color=red] 最后谈一下loadClass()和forName()的区别.
从上可以看出调用loadClass(name),相当于调用loadClass(name,false),表示只加载类,不连接初始化类,调用newInstance()才真正完成连接初始化操作.
Class.forName("xxxx")等同于Class.forName("xxxx",true,loader).true,表示载入实例的同时也载入静态初始化区块;false,表示只会加载该类别,但不会调用其静态初始化区块,只有等到整个程序第一次实例化某个类时,静态初始化区块才会被调用
在大多情况下loadClass()和forName()可以互用, 可以把ClassLoader.loadClass()看成是更底层的操作.在某些必须初始化类的场合,比如加载JDBC驱动,只能使用forName()方法了[/color]


资料:
[url]http://blog.csdn.net/vking_wang/article/details/17162327[/url]
[url]http://www.infoq.com/cn/articles/cf-Java-class-loader/[/url]
[url]http://blog.csdn.net/irelandken/article/details/7048817[/url]
[url]http://www.cnblogs.com/mailingfeng/archive/2012/07/02/2573419.html[/url]
[url]http://xjtom78.iteye.com/blog/898882[/url]
[url]http://liudeh-009.iteye.com/blog/1463388[/url]
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值