Java ClassLoader机制分析 -- Tomcat commloader 例说

本文详细分析了Tomcat中ClassLoader的创建过程,从Bootstrap开始,逐步讲解了如何创建commonLoader,涉及URLClassLoader的构造及其父类加载器的设置。此外,还探讨了ExtClassLoader和AppClassLoader的创建,以及类加载的步骤,包括findLoadedClass、loadClass、findClass和defineClass等关键方法的作用。通过这个过程,我们理解了Tomcat如何加载自定义路径下的资源以及自定义类加载器的重要性。
最终整理一下Tomcat 的ClassLoader创建流程如下:

1.	入口
Bootstrap bootstrap = new Bootstrap();
bootstrap.init();
initClassLoaders();
	ClassLoader commonLoader = createClassLoader("common", null); 
createClassLoader("common", null) {
		String value = CatalinaProperties.getProperty("common.loader");
		if (value == null || “”) {
			 return commonLoader = parent. 即 null.
		} else {
			将value对应的目录和文件所在目录转成Repository的数组,并执行
			ClassLoaderFactory.createClassLoader(repositories, parent);
				将repository的jar文件(一般都是jar)转化成URL存入到数组中。执行:
				return new URLClassLoader(array);
			
		}
   }
   如果 commonLoader==null, 则 commonLoader=parent 亦为null,表示无需额外资源加载。

2.	new URLClassLoader(array) 函数
URLClassLoader(URL[] urls) 
	SecureClassLoader()
		ClassLoader() {
			this(checkCreateClassLoader(), getSystemClassLoader());
		}

这里前者就是check RuntimePermission("createClassLoader") 运行时权限
getSystemClassLoader()就是生成系统类 cloassloader. 主要调用 initSystemClassLoader(); 函数
initSystemClassLoader()
	sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
	Launcher实例化的时候调用下面代码:
	new Launcher() {
		// 创建扩展类加载器,主要加载System.getProperty("java.ext.dirs")目录下相关资源
 		// 例如: $JAVA_HOME/lib/ext, $JAVA_HOME/jre/lib/ext
		ClassLoader extcl = ExtClassLoader.getExtClassLoader();

		// 创建 “java类路径”加载器,主要加载 System.getProperty("java.class.path") 目录下相关资源
		// 可以通过 ps –ef | grep java 查看 –classpath 得到。
		ClassLoader loader = AppClassLoader.getAppClassLoader(extcl);
	}

上述也进一步说明编写自己 classloader 的必要行。
启动类加载器只加载JVM启动所必需的类,为C编写,加载的如 java.*,javax.*等文件。
扩展类加载器只加载 java.ext.dirs 目录下的资源
应用类加载器只加载 java.class.path  里面,即 –classpath 对应的资源。
那么 tomcat/lib 等资源如何加载呢? 这就是 tomcat commonClassLoader 的用途了。
同理 对于具体的webapp,没有目前的加载器没有加载,需要写自己的加载器加载 web/lib, web/classes等资源。
3.	ExtClassLoader.getExtClassLoader();
URLStreamHandlerFactory factory = new Factory();

File[] dirs = getExtDirs(); // System.getProperty("java.ext.dirs");
new ExtClassLoader(dirs);
	super(getExtURLs(dirs), null, factory); // 将dir中文件转成 java.net.URL[]
	即:
	URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
		回到了步骤2,但是这次带了parent=null, 和 factory.
		步骤而的 super() 变成 super(parent);

		这样最终的ClassLoader如下:
		protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
}
这里直接传入 parent classloader, 就不在实例化系统类加载器了。
	}


4.	ClassLoader 具体工作
ClassLoader有多个构造函数,但最终都是通过下面的构造函数工作。

	// 父类加载器,ext的为null.
	private final ClassLoader parent;
    // 设置父classloader和初始化一些属性。
	private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent; 
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }
其实这个类里比较有用的是 loadClss(), findClass(), 以及 defineClass() 等。
通过上述,将 ExtClassLoader 对象(extcl)创建出来了
下面继续AppClassLoader的class loader创建



5.	Launcher的构造函数有 ClassLoader loader = AppClassLoader.getAppClassLoader(extcl);
public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException {
			// 加载资源的路径为 classpath 路径
            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 AccessController.doPrivileged(
                new PrivilegedAction<AppClassLoader>() {
                    public AppClassLoader run() {
                    URL[] urls =
                        (s == null) ? new URL[0] : pathToURLs(path);
                    return new AppClassLoader(urls, extcl);
                }
            });
        }

6.	AppClassLoader的构造函数如下:
AppClassLoader(URL[] urls, ClassLoader parent) {
            super(urls, parent, factory);
        }

也是继承了 URLClassLoader,因此构造函数里面处理流程同步骤 3 的处理。

通过上述,将 AppClassLoader对象(loader)创建出来了,接下来做的工作就是设置当前线程的ContextClassLoader:
Thread.currentThread().setContextClassLoader(loader);


7.	上面都设置完成后,就该设置自定义类的classloader的属性了。主要为 ucp
public URLClassLoader(URL[] urls, ClassLoader parent) {
        super(parent);
        // this is to make the stack depth consistent with 1.1
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
        }
        ucp = new URLClassPath(urls);
        this.acc = AccessController.getContext();
    }
最终通过上述得到tomcat的commonLoader(即指定路径的URLClassLoader)

8.	ClassLoader加载class的步骤:
8.1	调用 findLoadedClass 查找是否已加载
protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }

    private native final Class findLoadedClass0(String name);

8.2	调用父classloader的 loadClass 加载该class,如果父classloader为null,则调用自身该函数。
public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
}

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                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.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

8.3	调用findClass 来加载class
protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
这里未提供具体实现,针对扩展类和应用类classloader,系统启动时候已经知道加载哪些资源,也不需要具体实现。针对用户自定义的加载非上述路径的资源时,则要做具体实现,以 URLClassLoader为例,代码如下。

8.4	一个用户自定义的ClassLoader加载class示例(URLClassLoader为例)。
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.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
    }

上述如果找到对应二进制文件,则 defineClass,否则抛出CNFE异常。
ClassLoader的defineClass是直接处理二进制的,如果提供的不是byte[],则可在用户自定义的classloader里将文件转化成byte[]后交给ClassLoader的defineClass统一处理。

8.5	URLClassLoader的defineClass()函数 – 主要将 class 文件转成 byte[] 过程。
private Class defineClass(String name, Resource res) throws IOException {
        long t0 = System.nanoTime();
        int i = name.lastIndexOf('.');
        URL url = res.getCodeSourceURL();
        if (i != -1) {
			// 首先要保证class所在的package已经 define了
            String pkgname = name.substring(0, i);
            // Check if package already loaded.
            Manifest man = res.getManifest();
            if (getAndVerifyPackage(pkgname, man, url) == null) {
                try {
					// define该package,即根据条件创建一个java.lang.Package对象
                    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);
                    }
                }
            }
        }

		// 文件转 Buffer,底层实现,文件通常为File, Jar or URL,如果
		// 		文件实现了Bytebuffered接口,则先将文件转为InputStream,
		// 		然后调用 ((ByteBuffered)in).getByteBuffer() 即可
		// 否则,
		//		直接读入文件字节码
		// 做一些额外的处理,如CodeSigner / CodeSource等
        // Now read the class bytes and define the class
        java.nio.ByteBuffer bb = res.getByteBuffer(); 
        if (bb != null) {
            // Use (direct) ByteBuffer:
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, bb, cs);
        } else {
            byte[] b = res.getBytes();
            // must read certificates AFTER reading bytes.
            CodeSigner[] signers = res.getCodeSigners();
            CodeSource cs = new CodeSource(url, signers);
            sun.misc.PerfCounter.getReadClassBytesTime().addElapsedTimeFrom(t0);
            return defineClass(name, b, 0, b.length, cs);
        }
    }

用户自定义的classloader的defineClass方法将文件转成 byte 数组后,调用父类的defineClass做具体的Class实例化工作。
return defineClass(name, bb, cs);  // ByteBuffer
or
return defineClass(name, b, 0, b.length, cs); // byte[]
8.6	SecurClassLoader的defineClass()函数处理。 主要是根据CodeSource处理ProtectDomain

8.6.1	处理 java.nio.ByteBffer的二进制文件。
// defineClass(name, bb, cs)
protected final Class<?> defineClass(String name, java.nio.ByteBuffer b, CodeSource cs) {
        return defineClass(name, b, getProtectionDomain(cs));
}

8.6.2	处理byte[]的二进制文件。
// defineClass(name, b, 0, b.length, cs);
protected final Class<?> defineClass(String name,
                                         byte[] b, int off, int len, CodeSource cs) {
        return defineClass(name, b, off, len, getProtectionDomain(cs));
    }

8.7	ClassLoader的defineClass()函数处理。
8.7.1	处理 java.nio.ByteBffer的二进制文件。
protected final Class<?> defineClass(String name, java.nio.ByteBuffer b,
        				ProtectionDomain protectionDomain) throws ClassFormatError {
        int len = b.remaining();

		// // 如果不是直接在硬盘上的二进制,说明是在Java heap中,这里测处理与父类是byte[]相同
        // Use byte[] if not a direct ByteBufer:
        if (!b.isDirect()) { 
            if (b.hasArray()) {
                return defineClass(name, b.array(),
                                   b.position() + b.arrayOffset(), len,
                                   protectionDomain);
            } else {
                // no array, or read-only array
                byte[] tb = new byte[len];
                b.get(tb);  // get bytes out of byte buffer.
                return defineClass(name, tb, 0, len, protectionDomain);
            }
        }
		// 反之,如果存在硬盘上
		// 检查文件名的合法行,非 java. 开头,CodeSource的证书和该class所在package的第一个加载
		// 进来的class的cert是相同的
        protectionDomain = preDefineClass(name, protectionDomain);

        Class c = null;
		// 得到 class 的路径,返回一个URL.toString(),例如:
		// eg: file:/D:/dev/workspace_luna/tomcat8015/target/classes/
        String source = defineClassSourceLocation(protectionDomain);

        try {
            c = defineClass2(name, b, b.position(), len, protectionDomain,
                             source);
        } catch (ClassFormatError cfe) {
            byte[] tb = new byte[len];
            b.get(tb);  // get bytes out of byte buffer.
            c = defineTransformedClass(name, tb, 0, len, protectionDomain, cfe,
                                       source);
        }

        postDefineClass(c, protectionDomain);
        return c;
    }

8.7.2	处理byte[]的二进制文件。
protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                      ProtectionDomain protectionDomain) throws ClassFormatError {
        // 检查文件名的合法行,非 java. 开头,CodeSource的证书和该class所在package的第一个加载
		// 进来的class的cert是相同的
        protectionDomain = preDefineClass(name, protectionDomain);

        Class c = null;
		// 得到 class 的路径
        String source = defineClassSourceLocation(protectionDomain);

        try {
			// C 语言本地加载
            c = defineClass1(name, b, off, len, protectionDomain, source);
        } catch (ClassFormatError cfe) {
            c = defineTransformedClass(name, b, off, len, protectionDomain, cfe,
                                       source);
        }

        postDefineClass(c, protectionDomain);
        return c;
    }

8.8	JVM native处理defineClass代码
8.8.1	defineClass1() 处理
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
                                        jobject loader,
                                        jstring name,
                                        jbyteArray data,
                                        jint offset,
                                        jint length,
                                        jobject pd,
                                        jstring source)
{
    jbyte *body;
    char *utfName;
    jclass result = 0;
    char buf[128];
    char* utfSource;
    char sourceBuf[1024];

    if (data == NULL) {
        JNU_ThrowNullPointerException(env, 0);
        return 0;
    }

    /* Work around 4153825. malloc crashes on Solaris when passed a
     * negative size.
     */
    if (length < 0) {
        JNU_ThrowArrayIndexOutOfBoundsException(env, 0);
        return 0;
    }
    // 分配内存空间
    body = (jbyte *)malloc(length);

    if (body == 0) { // 没有内存可分配,则抛出 OOM 异常
        JNU_ThrowOutOfMemoryError(env, 0);
        return 0;
    }
    
将Byte类型数组某一区域复制到缓冲区中,参数说明如下: 
env: JNI 接口指针 
data: Java 指针 
offset:: 起始下标 
length: 要复制的元素个数 
body: 目的缓存区 
    (*env)->GetByteArrayRegion(env, data, offset, length, body);

    if ((*env)->ExceptionOccurred(env))
        goto free_body;

if (name != NULL) { 
		// 将 name 转换为unicode 的字符串 
        utfName = getUTF(env, name, buf, sizeof(buf));
		// 类名为空,表示JVM内存分配失败,说明没有内存了,抛出OOM 异常
        if (utfName == NULL) { 
            JNU_ThrowOutOfMemoryError(env, NULL);
            goto free_body;
        }
        VerifyFixClassname(utfName);
    } else {
        utfName = NULL;
    }

if (source != NULL) {
		// 文件路径同样转unicode格式,且不成功就抛出OOM异常。 
        utfSource = getUTF(env, source, sourceBuf, sizeof(sourceBuf));
        if (utfSource == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            goto free_utfName;
        }
    } else {
        utfSource = NULL;
}
	// C定义的jclass即Java 的 Class对象 
    result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);

    if (utfSource && utfSource != sourceBuf)
        free(utfSource);

 free_utfName:
    if (utfName && utfName != buf)
        free(utfName);

 free_body:
    free(body);
    return result; // 返回jclass对象 
}

8.8.2	defineClass2() 处理与defineClass1()类似
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass2(JNIEnv *env,
                                        jobject loader,
                                        jstring name,
                                        jobject data,
                                        jint offset,
                                        jint length,
                                        jobject pd,
                                        jstring source)
{
    jbyte *body;
    char *utfName;
    jclass result = 0;
    char buf[128];
    char* utfSource;
    char sourceBuf[1024];

    assert(data != NULL); // caller fails if data is null.
    assert(length >= 0);  // caller passes ByteBuffer.remaining() for length, so never neg.
    // caller passes ByteBuffer.position() for offset, and capacity() >= position() + remaining()
    assert((*env)->GetDirectBufferCapacity(env, data) >= (offset + length));

    body = (*env)->GetDirectBufferAddress(env, data);

    if (body == 0) {
        JNU_ThrowNullPointerException(env, 0);
        return 0;
    }

    body += offset;

    if (name != NULL) {
        utfName = getUTF(env, name, buf, sizeof(buf));
        if (utfName == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            return result;
        }
        VerifyFixClassname(utfName);
    } else {
        utfName = NULL;
    }

    if (source != NULL) {
        utfSource = getUTF(env, source, sourceBuf, sizeof(sourceBuf));
        if (utfSource == NULL) {
            JNU_ThrowOutOfMemoryError(env, NULL);
            goto free_utfName;
        }
    } else {
        utfSource = NULL;
    }
    result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);

    if (utfSource && utfSource != sourceBuf)
        free(utfSource);

 free_utfName:
    if (utfName && utfName != buf)
        free(utfName);

    return result;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值