Java ClassLoader机制分析 -- Tomcat commloader 例说
2015-01-08 22:56
411 查看
最终整理一下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,则调用自身该函 4000 数。 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; }
相关文章推荐
- Tomcat WebappClassLoader 类加载机制源码分析
- Classloader委任机制以及Tomcat中Classloader分析(整理)
- java classloader 机制
- 深入分析Java ClassLoader原理
- 启动tomcat出现java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener
- 深入分析Java ClassLoader原理
- Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC
- Tomcat启动报错“java.lang.ClassNotFoundException: org.apache.catalina.loader.DevLoader”
- Tomcat ClassLoader机制介绍
- 深入分析Java ClassLoader原理
- 深入分析Java ClassLoader原理
- java classLoader机制
- Java的反射机制, ClassLoader及OSGI
- java的classLoader分析与jettty的WebAppClassLoader
- Tomcat 启动报java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener 异常
- 深入分析Java ClassLoader原理
- java 反射机制中classloader的关系
- Java语言中的ClassLoader与Package机制
- Tomcat源码分析之ClassLoader部分的设计详细分析
- Java的ClassLoader分析与Jettty的WebAppClassLoader