文档章节

ClassLoader

shanxi_liyong
 shanxi_liyong
发布于 2015/02/06 21:40
字数 2682
阅读 56
收藏 0

一、当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构:


       bootstrap classloader
                |
       extension classloader
                |

       system classloader

(1)bootstrap classloader -引导(也称为原始)类加载器,它负责加载Java的核心类。在Sun的JVM中,在执行java的命令中使用-Xbootclasspath选项或使用 - D选项指定sun.boot.class.path系统属性值可以指定附加的类。这个加载器的是非常特殊的,它实际上不是 java.lang.ClassLoader的子类,而是由JVM自身实现的。大家可以通过执行以下代码来获得bootstrap classloader加载了那些核心类库:

URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
 for (int i = 0; i < urls.length; i++) {
 System.out.println(urls[i].toExternalForm());
 }

在我的计算机上的结果为:

file:/E:/Java/jdk1.7.0_67/jre/lib/sunrsasign.jar
file:/E:/Java/jdk1.7.0_67/jre/lib/jsse.jar
file:/E:/Java/jdk1.7.0_67/jre/lib/jce.jar
file:/E:/Java/jdk1.7.0_67/jre/lib/charsets.jar
file:/E:/Java/jdk1.7.0_67/jre/lib/jfr.jar
file:/E:/Java/jdk1.7.0_67/jre/classes

这时大家知道了为什么我们不需要在系统属性CLASSPATH中指定这些类库了吧,因为JVM在启动的时候就自动加载它们了。

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

System.out.println(System.getProperty("java.ext.dirs"));
		ClassLoader extensionClassloader = ClassLoader.getSystemClassLoader().getParent();
		System.out.println("the parent of extension classloader : " + extensionClassloader.getParent());

输出结果:

E:\Java\jdk1.7.0_67\jre\lib\ext;C:\WINDOWS\Sun\Java\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.如果没有特别指定,则用户自定义的任何类加载器都将该类加载器作为它的父加载器。可以从源代码看出这一点:

protected ClassLoader() {
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkCreateClassLoader();
	}
	this.parent = getSystemClassLoader();
	initialized = true;
    }

classpath路径:

System.out.println(System.getProperty("java.class.path"))

结果为:

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.
ClassLoader类中
protected synchronized Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException
    {
	// First, check if the class has already been loaded
	Class c = findLoadedClass(name);
	if (c == null) {
	    try {
		if (parent != null) {
		    c = parent.loadClass(name, false);
		} else {
		    c = findBootstrapClass0(name);
		}
	    } catch (ClassNotFoundException e) {
	        // If still not found, then invoke findClass in order
	        // to find the class.
	        c = findClass(name);
	    }
	}
	if (resolve) {
	    resolveClass(c);
	}
	return c;
    }

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

查看sun.misc.Launcher的源代码

public Launcher() {
	// Create the extension class loader
	ClassLoader extcl;
	try {
	    extcl = ExtClassLoader.getExtClassLoader();
	} catch (IOException e) {
	    throw new InternalError(
		"Could not create extension class loader");
	}
	// Now create the class loader to use to launch the application
	try {
	    loader = AppClassLoader.getAppClassLoader(extcl);
	} catch (IOException e) {
	    throw new InternalError(
		"Could not create application class loader");
	}
	// Also set the context class loader for the primordial thread.
	Thread.currentThread().setContextClassLoader(loader);
        // 其他
}

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

ExtClassLoader无parent,而AppClassLoader的parent为 ExtClassLoader

Launcher的getClassLoader()方法

public ClassLoader getClassLoader() { return loader; } 只返回AppClassLoader

ExtClassLoader

static class ExtClassLoader extends URLClassLoader {
	private File[] dirs;
	/**
	 * create an ExtClassLoader. The ExtClassLoader is created
	 * within a context that limits which files it can read
	 */
	public static ExtClassLoader getExtClassLoader() throws IOException
	{
	    final File[] dirs = getExtDirs();
	    try {
		// Prior implementations of this doPrivileged() block supplied 
		// aa synthesized ACC via a call to the private method
		// ExtClassLoader.getContext().
		return (ExtClassLoader) AccessController.doPrivileged(
		     new PrivilegedExceptionAction() {
			public Object run() throws IOException {
                            int len = dirs.length;
                            for (int i = 0; i < len; i++) {
                                MetaIndex.registerDirectory(dirs[i]);
                            }
                            return new ExtClassLoader(dirs);
			}
		    });
	    } catch (java.security.PrivilegedActionException e) {
		    throw (IOException) e.getException();
	    }
	}
......
private static File[] getExtDirs() {
	    String s = System.getProperty("java.ext.dirs");
	    File[] dirs;
	    if (s != null) {
		StringTokenizer st = 
		    new StringTokenizer(s, File.pathSeparator);
		int count = st.countTokens();
		dirs = new File[count];
		for (int i = 0; i < count; i++) {
		    dirs[i] = new File(st.nextToken());
		}
	    } else {
		dirs = new File[0];
	    }
	    return dirs;
	}

AppClassLoader

static class AppClassLoader extends URLClassLoader {
	public static ClassLoader getAppClassLoader(final ClassLoader extcl)
	    throws IOException
	{
	    final String s = System.getProperty("java.class.path");
	    final File[] path = (s == null) ? new File[0] : getClassPath(s);
	    // Note: on bugid 4256530
	    // Prior implementations of this doPrivileged() block supplied 
	    // a rather restrictive ACC via a call to the private method
	    // AppClassLoader.getContext(). This proved overly restrictive
	    // when loading  classes. Specifically it prevent
	    // accessClassInPackage.sun.* grants from being honored.
	    //
	    return (AppClassLoader) 
		AccessController.doPrivileged(new PrivilegedAction() {
		public Object run() {
		    URL[] urls =
			(s == null) ? new URL[0] : pathToURLs(path);
		    return new AppClassLoader(urls, extcl);
		}
	    });
	}
......

Launcher类的getBootstrapClassPath()方法

public static URLClassPath getBootstrapClassPath() {
	String prop = (String)AccessController.doPrivileged(new GetPropertyAction("sun.boot.class.path"));
	URL[] urls;
	if (prop != null) {
	    final String path = prop;
	    urls = (URL[])AccessController.doPrivileged(
		new PrivilegedAction() {
		    public Object run() {
                        File[] classPath = getClassPath(path);
                        int len = classPath.length;
                        Set seenDirs = new HashSet();
                        for (int i = 0; i < len; i++) {
                            File curEntry = classPath[i];
                            // Negative test used to properly handle
                            // nonexistent jars on boot class path
                            if (!curEntry.isDirectory()) {
                                curEntry = curEntry.getParentFile();
                            }
                            if (curEntry != null && seenDirs.add(curEntry)) {
                                MetaIndex.registerDirectory(curEntry);
                            }
                        }
                        return pathToURLs(classPath);
		    }
		}
	    );
	} else {
	    urls = new URL[0];
	}
	return new URLClassPath(urls, factory);
    }

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

运行下列程序:

System.out.println(sun.misc.Launcher.getLauncher().getClass().getClassLoader());

结果为:

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可以载入在上述三个路径中没有的类

示例:
自定义ClassLoader,从c根目录中读class
package Test;
public class TestClassLoader extends ClassLoader {
        protected Map<String, Class> cache = new HashMap<String, Class>();
	
	public TestClassLoader() {
		super();
	}
	
	protected Class<?> findClass(String name) throws ClassNotFoundException {
                if (cache.get(name) != null) {
			return cache.get(name);
		}
		try {
			String tname = name.replace('.', '/');
			File file = new File("c://" + tname + ".class");
			FileInputStream in = new FileInputStream(file);
			BufferedInputStream bufIn = new BufferedInputStream(in);
			ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
			BufferedOutputStream bufOut = new BufferedOutputStream(byteOut);
			byte[] buffer = new byte[4096];
			int len = -1;
			while((len = bufIn.read(buffer)) != -1) {
				bufOut.write(buffer, 0, len);
			}
			bufOut.flush();
			byteOut.flush();
			byte[] data = byteOut.toByteArray();
			Class cls = defineClass(name, data, 0, data.length);
			cache.put(name, cls);
			return cls;		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}
class TestLoader {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		ClassLoader cl = Thread.currentThread().getContextClassLoader();
		System.out.println(Thread.currentThread().toString() + "  " + cl.toString());
		new Thread() {
			public void run() {
				ClassLoader cl = Thread.currentThread().getContextClassLoader();
				System.out.println(Thread.currentThread().toString() + "  " + cl.toString());
				
				TestClassLoader loader = new TestClassLoader();
				Thread.currentThread().setContextClassLoader(loader);
				new Thread() {
					public void run() {
						try {
							ClassLoader cl = Thread.currentThread().getContextClassLoader();
							System.out.println(Thread.currentThread().toString() + "  " + cl.toString());
	//ClassLoader						
          AbstractTestA test = (AbstractTestA) cl.loadClass("Test.TestAImp1").newInstance();
        //Class.forName
	//AbstractTestA test = (AbstractTestA) Class.forName("Test.TestAImp1", true, cl).newInstance();						test.test();
						} catch (InstantiationException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (IllegalAccessException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						} catch (ClassNotFoundException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}.start();
			}
		}.start();
	}
}
//抽象类
package Test;
public abstract class AbstractTestA {
	public String a;
	public void print() {
		System.out.println("a=" + a);
	}
	
	abstract public void test();
}
//编译后放入c根目录
package Test;
public class TestAImp1 extends AbstractTestA {
	public void test() {
		System.out.println(Thread.currentThread().toString());
		System.out.println("Test");
	}
}

运行结果为:

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)为包访问权限

package java.lang;
public class TestPackage {
	public static void main(String[] args) {
		char[] c = "1234567890".toCharArray();
		String s = new String(0, 10, c);
	}
}



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

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

package Test;
import java.io.*;
import java.util.*;
public class TestClassLoader extends ClassLoader {
	
	protected Map<String, Class> cache = new HashMap<String, Class>();
	
	public TestClassLoader() {
		super();
	}
	
	protected Class<?> findClass(String name) throws ClassNotFoundException {
		if (cache.get(name) != null) {
			return cache.get(name);
		}
		try {
			String tname = name.replace('.', '/');
			File file = new File("c://" + tname + ".class");
			FileInputStream in = new FileInputStream(file);
			BufferedInputStream bufIn = new BufferedInputStream(in);
			ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
			BufferedOutputStream bufOut = new BufferedOutputStream(byteOut);
			byte[] buffer = new byte[4096];
			int len = -1;
			while((len = bufIn.read(buffer)) != -1) {
				bufOut.write(buffer, 0, len);
			}
			bufOut.flush();
			byteOut.flush();
			byte[] data = byteOut.toByteArray();
			Class cls = defineClass(name, data, 0, data.length);
			cache.put(name, cls);
			return cls;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}
class TestLoaderA {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
		TestClassLoader loader = new TestClassLoader();
		System.out.println(TestAImp1.class.getClassLoader());
		System.out.println(loader.findClass("Test.TestAImp1").getClassLoader());
		TestAImp1 test = (TestAImp1)loader.findClass("Test.TestAImp1").newInstance();
		test.test();
		
	}
}
public class TestAImp1 extends AbstractTestA {
	public void test() {
		System.out.println(Thread.currentThread().toString());
		System.out.println("Test");
	}
}

运行以上代码结果:

sun.misc.Launcher$AppClassLoader@197d257 
Test.TestClassLoader@1b90b39 
Exception in thread "main" java.lang.ClassCastException: Test.TestAImp1
 at Test.TestLoaderA.main(TestClassLoader.java:84)




















© 著作权归作者所有

共有 人打赏支持
shanxi_liyong
粉丝 10
博文 106
码字总数 37225
作品 0
太原
程序员
图解classloader加载class的流程及自定义ClassLoader

java应用环境中不同的class分别由不同的ClassLoader负责加载。 一个jvm中默认的classloader有Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader,分别各司其职: Bootstrap Cl...

zhengguogaun
2013/06/19
0
0
理解 Java ClassLoader 机制

再次阅读这篇文章时,有了更深的体会,特转载之。 理解Java ClassLoader机制 当JVM(Java虚拟机)启动时,会形成由三个类加载器组成的初始类加载器层次结构: bootstrap classloader | exte...

鉴客
2011/07/28
746
5
JDK中提供的ClassLoader

作为一枚java猿,了解类加载器是有必要的,无论是针对面试还是自我学习。 本文从JDK提供的ClassLoader、委托模型以及如何编写自定义的ClassLoader三方面对ClassLoader做一个简要的总结。 1....

281824088
2016/04/08
0
0
Spring源码学习之:ClassLoader学习(3)

ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回 这个类的class对象。 1.1 几个相关概念ClassLoader负责载入系统的所有Res...

无信不立
2016/11/07
0
0
ClassLoader 详解及用途(写的不错)

ClassLoader主要对类的请求提供服务,当JVM需要某类时,它根据名称向ClassLoader要求这个类,然后由ClassLoader返回这个类的class对象。 1.1 几个相关概念ClassLoader负责载入系统的所有Res...

yuanhotel
2015/09/21
2.8K
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

JS三元运算示例

1. topFlag=topFlag ==0?1:0; 等于 if(topFlag=00){ topFlag=1; }else if(topFlag == 1){ topFlag=0; } 2. 5>3?alert('5大'):alert('3大'); 即 if(5>3){alert('5大')}else{alert('3大')}; 注......

森火
55分钟前
0
0
利用Slf4j的MDC跟踪方法调用链

why? 一个web项目通常提供很多URL访问地址, 项目一般都是分层处理,例如Controller——>Service——>DAO。 如果想根据日志查看用户一次请求都走了哪些方法(多数是查错误)。 如果系统是多人...

杨春炼
今天
5
0
Maven介绍及安装

Maven介绍及安装 以下内容是本人早期学习时的笔记,可能比较详实繁琐,现在复习一下Maven,顺便将内容抛出来,供大家一起学习进步。 一、Maven简介 Maven是Apache旗下的一款项目管理工具,是...

星汉
今天
0
0
小程序Aes解密

主要步骤: 1、下载AES源码(JS版) 2、在小程序中新建一个公共的文件夹,把AES源码拷贝进去(注意:需要暴露接口 module.exports = CryptoJS;) 3、添加一个用于加密解密的公共JS,可取名为...

Mr_Tea伯奕
今天
0
0
Go实现文件传输(基本传输可用)

发送端 package mainimport ("fmt""os""net""io")func SendFile(path string, connect net.Conn){file, oerr :=os.Open(path)if oerr !=nil{fmt.Println("Open", oerr)......

CHONGCHEN
今天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部