Java-類載入機制
阿新 • • 發佈:2019-01-06
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-執行緒上下文載入器
用父載入器載入的無法直接去載入子載入器的內容。