1. 从JVM加载类的过程再看类加载器
(1). 类加载过程的加载阶段要点回顾
[1]. 在类加载过程的加载阶段中,JVM的规范之一是“通过全类名获取定义此类的二进制字节(byte)流”。
[2]. 但是JVM并没有对这条规范中“从哪里获取此类的二进制流”并且“怎样获取此类的二进制流”做出明确的规定。
[3]. 进一步说:JVM设计团队将“通过全类名获取定义此类的二进制字节(byte)流”这个动作放到JVM的外部去实现,以便让应用程序自己决定如何去获取所需要的类。
(2). 通过加载阶段认识类加载器
再次理解类加载器:实现“通过全类名获取定义此类的二进制字节(byte)流”这个动作代码的模块称为类加载器
2. 从Java源码再看双亲委派模型
1). ClassLoader抽象类中loadClass的源码
(1). 供JVM直接调用的是public访问权限的loadClass()方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
在这个方法中,又一次调用了访问权限是protected的重载的loadClass方法。这个方法体现了类加载器的双亲委托模型
(2). 双亲委托机制的源码体现
[1]. 访问权限为protected的loadClass方法的调用者
默认的由JVM调用的public的loadClass调用这个方法在protected的loadClass的第二个参数的boolean resolve的位置传入的参数是false
[2]. loadClass源码的流程
【说明】代码中使用了含有缓存cache机制的双亲委托机制。首先在缓存cache中查找是否存在这个Class对象,如果存在就直接返回。否则再去使用双亲委托机制去加载这个Class对象。
step1. 检测此Class是否载被加载过( 即在cache中是否有此Class )
{1}. 如果已经被加载过并找到,去step7直接返回这个被加载过的Class对象
{2}. 如果没有,跳转到step2
step2. 检查这个类加载器的父classloader是否存在
{1}. 如果当前类加载器的父类加载器仍然存在,将调用父类类加载器的loadClass()方法对指定的类进行加载。
{2}. 如果当前类加载器的父类加载器不存在(没有parent,那parent一定是Bootstrap classloader ),跳转到step4
step3. 请求父类加载器对指定的类进行载入
{1}. 如果成功到step7
{2}. 不成功到step5
step4. 请求JVM从Bootstrap 类加载器对相应的类进行加载
{1}. 如果成功,跳转到step 7
{2}. 如果不成功,执行step5。
step5. 调用类加载器findClass( )方法对指定的类进行加载 (从与此classloader相关的类路径中寻找)
{1}. 如果找到则执行step7
{2}. 否则执行step6
step6. 抛出ClassNotFoundException.
step7. 返回Class.
[3]. loadClass源码
2). ExtClassLoader、AppClassLoader的真实继承体系
(1). SecureClassLoader类
[1]. 所处位置:java.security包
[2]. 源码声明
public class SecureClassLoader extends ClassLoader {…}
[3]. 特点
{1}. 直接继承了抽象类java.lang.ClassLoader
{2}. 这个类对java.lang.ClassLoader中的defineClass方法提供了重载的方法,增加了如下两个重载方法。【注意这两个重载的方法是final的,也就是不能被重写】
{2}1. protected final Class<?> defineClass(String name, byte[] b,int off, int len, CodeSource cs)
{2}2. protected final Class<?> defineClass(String name, java.nio.ByteBufferb, CodeSource cs)
【注意】这两个重载的方法内部实现都是对java.lang.ClassLoader的defineClass( )方法的简单封装。
{3}. 增加对系统默认策略检索的权限的支持【了解即可】
(2). URLClassLoader类
[1]. 所处位置:java.net包
[2]. 源码声明
public class URLClassLoader extendsSecureClassLoader implementsCloseable {
//字节码的检索路径
private final URLClassPath ucp;
//…
//重写了java.lang.ClassLoader类的findClass方法
protected Class<?> findClass(final String name) throws ClassNotFoundException{//…}
//…
}
[3]. URLClassLoader作用
{1}. 这个类可以从指定的搜索路径加载字节码文件或者资源文件。
{2}. 指定的路径可以是对JAR文件的引用,也可以是对普通路径的引用
[4]. 特点
{1}. 直接继承了类java.security.SecureClassLoader
{2}. 重载了defineClass( )方法, private访问属性
{3}. 重写了父类的findClass( )方法
[5]. URLClassLoader重写的父类defineClass( )源码
{1}. 作用
从指定的资源中获取Class类对象
{2}. 源码
private Class defineClass(String name, Resource res)throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
URLurl = res.getCodeSourceURL();
if (i != -1) {
String pkgname = name.substring(0, i);
// Check ifpackage already loaded.
Manifest man = res.getManifest();
if (getAndVerifyPackage(pkgname, man,url) == null) {
try {
if (man != null) {
definePackage(pkgname, man,url);
} else {
definePackage(pkgname, null, null, null, null, null, null, null);
}
} catch (IllegalArgumentException iae) {
// parallel-capable class loaders:re-verify in case of a
// race condition
if (getAndVerifyPackage(pkgname, man, url) == null) {
// Should never happen
throw new AssertionError("Cannot find package " +
pkgname);
}
}
}
}
// Now read the class bytes anddefine the class
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// Use(direct) ByteBuffer:
CodeSigner[] signers = res.getCodeSigners();
CodeSource cs = newCodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, bb, cs);
} else {
byte[] b = res.getBytes();
// must readcertificates AFTER reading bytes.
CodeSigner[] signers =res.getCodeSigners();
CodeSource cs = newCodeSource(url, signers);
sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
return defineClass(name, b, 0, b.length,cs);
}
}
[6]. URLClassLoader重写的父类findClass( )源码
{1}. 作用
根据指定的类名从URL路径中寻找并加载指定的Class。其中指定的URL路径被URLClassLoader的成员变量ucp所记录。
{2}. 源码
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<Class>() {
public Class run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res =ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
throw new ClassNotFoundException(name);
}
}
}, acc);
} catch (java.security.PrivilegedActionExceptionpae) {
throw (ClassNotFoundException)pae.getException();
}
}
【结论】可以看到,findClass方法就是通过拼接路径字符串来获取最终的class文件所在的路径,并进行加载 String path = name.replace('.', '/').concat(".class");
(3). Launcher$AppClassLoader类
[1]. 位置:sun.misc.Launcher的内部
[2]. AppClassLoader源码 (从Launcher内部摘取)
由于AppClassLoader是静态内部类,依附于Launcher这个外部类,以下是源码。
static class AppClassLoader extendsURLClassLoader{
public static ClassLoader getAppClassLoader(ClassLoader paramClassLoader)
throws IOException{
String str = System.getProperty("java.class.path");
File[] arrayOfFile = str == null ? new File[0] : Launcher.access$200(str);
return (ClassLoader)AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile,paramClassLoader)
{
public Launcher.AppClassLoader run() {
URL[] arrayOfURL = this.val$s == null ? new URL[0] : Launcher.access$300(this.val$path);
return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);
}
});
}
AppClassLoader(URL[] paramArrayOfURL, ClassLoader paramClassLoader){
super(paramClassLoader, Launcher.factory);
}
public Class loadClass(String paramString, boolean paramBoolean) throws ClassNotFoundException{
int i = paramString.lastIndexOf('.');
if (i != -1) {
SecurityManager localSecurityManager = System.getSecurityManager();
if (localSecurityManager != null) {
localSecurityManager.checkPackageAccess(paramString.substring(0, i));
}
}
return super.loadClass(paramString, paramBoolean);
}
protected PermissionCollection getPermissions(CodeSourceparamCodeSource){
PermissionCollection localPermissionCollection = super.getPermissions(paramCodeSource);
localPermissionCollection.add(new RuntimePermission("exitVM"));
return localPermissionCollection;
}
private void appendToClassPathForInstrumentation(StringparamString){
assert (Thread.holdsLock(this));
super.addURL(Launcher.getFileURL(new File(paramString)));
}
private static AccessControlContext getContext(File[]paramArrayOfFile)throws MalformedURLException{
PathPermissions localPathPermissions = new PathPermissions(paramArrayOfFile);
ProtectionDomain localProtectionDomain = new ProtectionDomain(newCodeSource(localPathPermissions.getCodeBase(), (Certificate[])null), localPathPermissions);
AccessControlContext localAccessControlContext = new AccessControlContext(new ProtectionDomain[] { localProtectionDomain});
return localAccessControlContext;
}
static
{
ClassLoader.registerAsParallelCapable();
}
}
[3]. Launcher$AppClassLoader类的特点
{1}, 直接父类是URLClassLoader,而不是ExtClassLoader类
{2}. 重写了父类的loadClass方法
public Class loadClass(String paramString, boolean paramBoolean) throws ClassNotFoundException{
int i = paramString.lastIndexOf('.');
if (i != -1) {
SecurityManager localSecurityManager = System.getSecurityManager();
if (localSecurityManager != null) {
localSecurityManager.checkPackageAccess(paramString.substring(0, i));
}
}
return super.loadClass(paramString, paramBoolean);
}
【注意】尽管是重写父类的public的方法,但是仅仅做了一些类名上的检查就再一次调用了父类的loadClass方法,没有实质性的改变。调用了父类的loadClass就代表着在查找类的时候采用的是双亲委托机制进行查找。
(4). Launcher$ExtClassLoader类
[1]. 位置:sun.misc.Launcher的内部
[2]. ExtClassLoader源码 (从Launcher内部摘取)
static class ExtClassLoader extendsURLClassLoader
{
public static ExtClassLoader getExtClassLoader() throws IOException{
File[] arrayOfFile = getExtDirs();
try{
return(ExtClassLoader)AccessController.doPrivileged(new PrivilegedExceptionAction(arrayOfFile)
{
public Launcher.ExtClassLoader run() throws IOException {
int i = this.val$dirs.length;
for (int j = 0; j < i; j++) {
MetaIndex.registerDirectory(this.val$dirs[j]);
}
return new Launcher.ExtClassLoader(this.val$dirs);
} } );
}catch (PrivilegedActionExceptionlocalPrivilegedActionException) {
}
throw((IOException)localPrivilegedActionException.getException());
}
void addExtURL(URL paramURL)
{
super.addURL(paramURL);
}
public ExtClassLoader(File[] paramArrayOfFile)
throws IOException
{
super(null, Launcher.factory);
}
private static File[] getExtDirs() {
String str = System.getProperty("java.ext.dirs");
File[] arrayOfFile;
if (str != null) {
StringTokenizer localStringTokenizer = new StringTokenizer(str, File.pathSeparator);
int i =localStringTokenizer.countTokens();
arrayOfFile = new File[i];
for (int j = 0; j < i; j++)
arrayOfFile[j] = newFile(localStringTokenizer.nextToken());
}
else {
arrayOfFile = new File[0];
}
return arrayOfFile;
}
private static URL[] getExtURLs(File[] paramArrayOfFile) throws IOException {
Vector localVector = new Vector();
for (int i = 0; i < paramArrayOfFile.length; i++){
String[] arrayOfString = paramArrayOfFile[i].list();
if (arrayOfString != null) {
for (int j = 0; j < arrayOfString.length; j++) {
if (!arrayOfString[j].equals("meta-index")) {
File localFile = newFile(paramArrayOfFile[i], arrayOfString[j]);
localVector.add(Launcher.getFileURL(localFile));
}
}
}
}
URL[] arrayOfURL = newURL[localVector.size()];
localVector.copyInto(arrayOfURL);
return arrayOfURL;
}
public String findLibrary(String paramString)
{
paramString = System.mapLibraryName(paramString);
URL[] arrayOfURL = super.getURLs();
Object localObject = null;
for (int i = 0; i < arrayOfURL.length; i++)
{
File localFile1 = newFile(arrayOfURL[i].getPath()).getParentFile();
if ((localFile1 != null) &&(!localFile1.equals(localObject)))
{
String str = VM.getSavedProperty("os.arch");
if (str != null) {
localFile2 = new File(new File(localFile1, str), paramString);
if (localFile2.exists()) {
return localFile2.getAbsolutePath();
}
}
File localFile2 = newFile(localFile1, paramString);
if (localFile2.exists()) {
return localFile2.getAbsolutePath();
}
}
localObject = localFile1;
}
return null;
}
private static AccessControlContext getContext(File[]paramArrayOfFile)
throws IOException
{
PathPermissions localPathPermissions = new PathPermissions(paramArrayOfFile);
ProtectionDomain localProtectionDomain = new ProtectionDomain(new CodeSource(localPathPermissions.getCodeBase(),(Certificate[])null), localPathPermissions);
AccessControlContext localAccessControlContext = new AccessControlContext(new ProtectionDomain[] { localProtectionDomain});
return localAccessControlContext;
}
static
{
ClassLoader.registerAsParallelCapable();
}
}
[3].ExtClassLoader的特点
{1}. ExtClassLoader和AppClassLoader一样,都是静态内部类
{2}. ExtClassLoader和AppClassLoader一样,直接父类都是URLClassLoader类。
由此我们可以得到如下的Java类加载器的真实继承体系结构图
3). Java中ClassLoader继承体系
(1). ClassLoader及其子类的真实继承体系
【注意】java.lang.ClassLoader是一个抽象类
(2). ExtClassLoader和AppClassLoader之间的关系
ExtClassLoader和AppClassLoader之间的所谓的“父子关系”
AppClassLoader是通过继承ClassLoader基类内部聚合字段privatefinal ClassLoader parent; 来体现的ExtClassLoader是其“父类”。
【注意】由于Java仅仅支持单继承,同时AppClassLoader已经继承了URLClassLoader这个类,所以AppClassLoader就不能再使用extends继承ExtClassLoader这个类。
但是为了表达AppClassLoader和ExtClassLoader的关系,采用AppClassLoader的内部字段parent指向ExtClassLoader的实例。
(3). 双亲委托机制的细节
[1]. 当AppClassLoader被实例化时候,JVM调用AppClassLoader的loadClass方法对相应的类进行加载。
[2]. AppClassLoader重写了父类的loadClass方法,但是在源代码最后一行还是调用了super.loadClass( )。这证明了AppClassLoader使用的类加载的方式仍然是双亲委托模型。
[3]. 当调用父类的loadClass方法的时候,如果父类一直找不到要求加载的类,就会返回给子类进行加载。子类这个时候采用的是findClass方法进行类加载。
【注意】ClassLoader的默认findClass方法仅仅抛出异常,没有去查找这个类!!
【注意】由于AppClassLoader和ExtClassLoader继承的是URLClassLoader类,这个类却重写了ClassLoader这个类的findClass方法,所以调用AppClassLoader或者ExtClassLoader类的loadClass方法的时候,执行到findClass的时候,真正被JVM调用findClass是ClassLoader的子类URLClassLoader重写的findClass方法。