您的位置:首页 > 运维架构 > Tomcat

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  tomat java 语言