sun jre本身的classloader主要包含三个loader,分别是
1. Bootstrap class loader->C代码 (加载%java_home%/lib/*)
2. Extensions class loader->ExtClassLoader (%java_home%/lib/ext/*)
3. System class loader->AppClassLoader (加载classpath下的类)
为什么他们会加载这些路径下面的class呢?
ExtClassLoader和AppClassLoader的构造函数可以看出
String s = System.getProperty("java.class.path"); new ExtClassLoader(s, extcl); String s = System.getProperty("java.ext.dirs"); new AppClassLoader(s, extcl);
ExtClassLoader和AppClassLoader都继承于URLClassLoader,构造函数传递的path将来会作为加载class的路径范围
(注: ExtClassLoader和AppClassLoader不属于标准的java api,是sun.misc.Launcher的内部类,具体源码在openjdk中,在线地址为
http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/misc/Launcher.java
)
那如何加载字节码class文件呢?
以AppClassLoader为例,当需要它加载A.class时,会调用其loadClass(String name, boolean resolve)方法,
此方法会通过super.loadClass(name, resolve)交由父类,最终由java.lang.ClassLoader处理,代码如下
(java.lang.ClassLoader.loadClass方法)
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 = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
从这段代码可以看出加载class有一个委托的模型,AppClassLoader,ExtClassLoader, Bootstrap classoader在建立初期就创建了一个所谓的父子关系(并不是继承关系),
如图
其后在加载具体class的时候会有个委托parent classloader加载,parent加载不到,再由自己加载的过程,目的就是为了安全,防止java的核心类库被修改,如果用这种模型,java.lang.String类就不能由用户自己去实现,
因为他肯定最终还是Bootstrap classoader类去加载了,因为string.class是在%java_home%/lib/*下的rt.jar中
(注:如果你一定要自己写一个java.lang.String覆盖jre自己的,你可以自己实现classloader,然后违反委托模型,这是可以做到的)
可以看到上面的java.lang.ClassLoader.loadClass方法会调用 findClass(name)去真正得到一个class对象,此方法有ClassLoader的子类,ExtClassLoader和AppClassLoader的父类URLClassLoader实现,
具体方法实现如下(去掉不影响理解的代码)
(java.lang.URLClassLoader.findClass方法)
protected Class<?> findClass(final String name) { String path = name.replace('.', '/').concat(".class"); //ucp变量就是当初构造AppClassLoader的时候传递的路径,这里使用到 Resource res = ucp.getResource(path, false); return defineClass(name, res, true); }
上面会继续调用到defineClass方法
(java.lang.URLClassLoader.defineClass方法)
private Class defineClass(String name, Resource res, boolean verify) throws IOException { int i = name.lastIndexOf('.'); URL url = res.getCodeSourceURL(); // Now read the class bytes and define the class ByteBuffer bb = res.getByteBuffer(); byte[] bytes = ((bb == null)? res.getBytes() : null); // NOTE: Must read certificates AFTER reading bytes above. CodeSigner[] signers = res.getCodeSigners(); CodeSource cs = new CodeSource(url, signers); if (!verify) { // Need to use reflection since methods are private in super class Object[] args = { name, (bb == null? ByteBuffer.wrap(bytes) : bb), cs }; try { return (Class) defineClassNoVerifyMethod.invoke(this, args); } catch (IllegalAccessException iae) { // Should never happen; fall back to the regular defineClass } } return (bb != null? defineClass(name, bb, cs) : defineClass(name, bytes, 0, bytes.length, cs)); }
上面方法会先通过stream的方式读取字节码转为byte数组,然后最终交由ClassLoader的defineClass1方法
(java.lang.ClassLoader.defineClass1方法)
private native Class defineClass1(String name, byte[] b, int off, int len, ProtectionDomain pd, String source, boolean verify);
上面方法是native方法,这是加载字节码的最后一步,可以想象,这必须由jvm本身来实现
以上总结:加载class首先有个委托的过程,而加载本身又是经历根据路径寻找字节码,加载字节流,讲字节流转为Class对象的过程