Java-类加载机制
摘要
本文简要介绍Java加载机制,还会介绍双亲委派机制的破坏,线程上下文加载器,以及JDBC Driver是如何自动加载的。
未完成
0x01 Java类加载机制
1.1 简介
当前版本jdk是采用双亲委派机制:
子ClassLoader总是会让父ClassLoader尝试加载,如果不行,才会自己尝试加载。
1.1.1 BootstrapClassLoader
BootstrapClassLoader
是启动类加载器。
通过以下代码打出BootstrapClassLoader
加载的文件:
public class BootStrapTest
{
public static void main(String[] args)
{
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i].toExternalForm());
}
}
}
结果如下:
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/resources.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/rt.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/sunrsasign.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jsse.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jce.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/charsets.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/lib/jfr.jar
file:/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home/jre/classes
可以看到除了rt.jar
以外还加载了一些其他文件。
1.2 双亲委派的意义
比如java.lang.Object
,用户也自定义一个同样权限定名的类。但在加载时,会首先用BootstrapClassLoader
加载rt.jar
中的该类。而用户自定义的Object类,也会因为AppClassLoader
往上寻找到祖先BootstrapClassLoader
类来加载该类,但会发现该类已经被加载过导致报错。
0x02 双亲委派机制的破坏
2.1 破坏1
JDK1.2之前没有双亲委派模型,所以之前的开发者继承java.lang.ClassLoader
后要做的就是重写loadClass()
方法,编写自定义的类加载逻辑。该loadClass
方法代码如下:
// 父加载器
private final ClassLoader parent;
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 先在ClassLoader获取该ClassName对应的同步锁对象
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 先检查该类全限定名是否已经被加载到JVM
Class<?> c = findLoadedClass(name);
if (c == null) {
// 此时没有加载该类
long t0 = System.nanoTime();
try {
if (parent != null) {
// 存在父加载器(不是BootStrapClassLoader)
// 就尝试让父加载器加载类,但不连接
c = parent.loadClass(name, false);
} else {
// 否则尝试用BootStrapClassLoader加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 此时还没有加载该类
long t1 = System.nanoTime();
// 就调用findClass方法来查找该类
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;
}
}
但在JDK1.2之后,不再提倡重写loadClass()
,而是应该重写新加入的findClass
方法:
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 该方法默认没有实现,只是抛出一个ClassNotFoundException
throw new ClassNotFoundException(name);
}
启动类加载器BootstrapClassLoader
是用C++实现,而扩展类加载器ExtClassLoader
和应用程序类加载器AppClassLoader
都继承自URLClassLoader
。findClass
方法直接用的URLClassLoader
的findClass
方法:
// 要加载的类的全限定名
// 比如是demos.classInitialization.classloader.order.EntityC
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
// 那么这里path为demos/classInitialization/classloader/order/EntityC.class
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// 使用从指定Resource获取的类字节来转为Class对象
// 必须先连接,然后生成的类才能使用它。
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
// 如果结果为空,直接抛ClassNotFoundException
throw new ClassNotFoundException(name);
}
// 返回得到的Class对象
return result;
}
接着看看URLClassLoader
的defineClass
方法:
/*
* 使用从指定的资源中获取的class bytes 来定义一个Class对象
* 最终得到的Class必须在使用前被解析
*/
private Class<?> defineClass(String name, Resource res) throws IOException {
long t0 = System.nanoTime();
int i = name.lastIndexOf('.');
// file:/xxx/javaDemos/target/classes/
URL url = res.getCodeSourceURL();
if (i != -1) {
String pkgname = name.substring(0, i);
// 检查包是否已经加载过
Manifest man = res.getManifest();
//
definePackageInternal(pkgname, man, url);
}
// 从class字节码中读取数据并转为Class
java.nio.ByteBuffer bb = res.getByteBuffer();
if (bb != null) {
// 直接使用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);
}
}
2.2 破坏2-线程上下文加载器
用父加载器加载的无法直接去加载子加载器的内容。