談談雙親委派模型的第四次破壞-模塊化

前言

JDK9引入了Java模塊化系統(Java Platform Moudle System)來實現可配置的封裝隔離機制,同時JVM對類加載的架構也做出了調整,也就是雙親委派模型的第四次破壞。前三次破壞分別是:雙親委派模型推出之前,SPI機制,以及OSGI為代表的熱替換機制,這裡不細說。

雙親委派模型

簡介

在JDK9引入之前,絕大多數Java程序會用下面三個類加載器進行加載

  • 啟動類加載器(Bootstrap Class Loader):由C++編寫,負責加載<java>\\jre\\lib目錄下的類,例如最基本的Object,Integer,這些存在於rt.jar文件中的類,一般這些類都是Java程序的基石。/<java>
  • 擴展類加載器(Extension Class Loader):負責加載<java>\\jre\\lib\\ext目錄下的類,在JDK9之前我們可以將通用性的類庫放在ext目錄來擴展JAVA的功能,但實際的工程都是通過maven引入jar包依賴。並且在JDK9取消了這一類加載器,取而代之的是平臺類加載器(Platform Class Loader),下面會對其介紹。/<java>
  • 應用類加載器(Application Class Loader):負責加載ClassPath路徑下的類,通常工程師編寫的大部分類都是由這個類加載器加載。

工作順序

解釋

如果一個ClassLoader收到了類加載的請求,他會先首先將請求委派給父類加載器完成,只有父類加載器加載不了,子加載器才會完成加載。

談談雙親委派模型的第四次破壞-模塊化

源代碼

下面代碼保留了核心邏輯,並添加了註釋,主要是2個步驟

  1. 如果父類加載器不為空則用父類加載器加載
  2. 父類加載器加載不成功則本身再加載
<code>            Class> c = findLoadedClass(name);
//如果該類沒加載過
if (c == null) {
try {
//如果有父類加載器
if (parent != null) {
//使用父類加載器加載
c = parent.loadClass(name, false);
...
}
}
if (c == null) {
...
//父類加載器沒有加載成功則調用自身的findClass進行加載
c = findClass(name);
\t\t\t\t\t\t\t\t\t\t...
}
}
/<code>

值得注意的是這裡的parent並不是繼承上的父子關係,而是組合關係的父子,parent只是類加載器的一個參數。

圖示

如果覺得上面的解釋比較抽象可以看看下面比較形象的圖示,這裡的敵人就是我們要加載的jar包


談談雙親委派模型的第四次破壞-模塊化

缺點

通過上面的漫畫不言而喻,當真正的敵人來了,靠這種低效的傳達機制,怎麼可能打一場勝仗呢?

  • 啟動類加載器負責加載<java>\\jre\\lib目錄/<java>
  • 擴展類加載器負責加載<java>\\jre\\lib\\ext目錄/<java>
  • 應用類加載器負責加載ClassPath目錄。

既然一切都是各司其職,為什麼不能加載類的時候一步到位呢?

通過分析JDK9的類加載器源碼,我發現最新的類加載器結構在一定程度上是緩解了這種情況的


JDK的模塊化

在JDK9之前,JVM的基礎類以前都是在rt.jar這個包裡,這個包也是JRE運行的基石。這不僅是違反了單一職責原則,同樣程序在編譯的時候會將很多無用的類也一併打包,造成臃腫。

在JDK9中,整個JDK都基於模塊化進行構建,以前的rt.jar, tool.jar被拆分成數十個模塊,編譯的時候只編譯實際用到的模塊,同時各個類加載器各司其職,只加載自己負責的模塊。


談談雙親委派模型的第四次破壞-模塊化

模塊化加載源碼

<code>  Class> c = findLoadedClass(cn);
if (c == null) {
// 找到當前類屬於哪個模塊
LoadedModule loadedModule = findLoadedModule(cn);
if (loadedModule != null) {
//獲取當前模塊的類加載器
BuiltinClassLoader loader = loadedModule.loader();
//進行類加載
c = findClassInModuleOrNull(loadedModule, cn);
} else {

// 找不到模塊信息才會進行雙親委派
if (parent != null) {
c = parent.loadClassOrNull(cn);
}
}/<code>

上面代碼就是破壞雙親委派模型的“鐵證”,而當我們繼續跟進findLoadedModule,會發現是根據路徑名找到對應的模塊,而維護這一數據結構的就是下面這個Map。

<code>Map<string> packageToModule
= new ConcurrentHashMap<>(1024);/<string>/<code>

可以看到LoadedModule裡面不僅有該模塊的loader信息,還有用於描述依賴模塊,對外暴露模塊的信息的mref,LoadedModule也是模塊化實現封裝隔離機制的一塊重要實現。

談談雙親委派模型的第四次破壞-模塊化

每一個module信息都有一個BuiltinClassloader,這個類有三個子類,我們通過源碼分析他們的父子關係

談談雙親委派模型的第四次破壞-模塊化

在ClassLoaders類中可以發現,PlatformClassLoader的parent是BootClassLoader,而AppClassLoader的parent則是PlatformClassLoader。

<code>public class ClassLoaders {

// the built-in class loaders
private static final BootClassLoader BOOT_LOADER;
private static final PlatformClassLoader PLATFORM_LOADER;
private static final AppClassLoader APP_LOADER;

static {
BOOT_LOADER =

new BootClassLoader((append != null && !append.isEmpty())
? new URLClassPath(append, true)
: null);

PLATFORM_LOADER = new PlatformClassLoader(BOOT_LOADER);
...
APP_LOADER = new AppClassLoader(PLATFORM_LOADER, ucp);
}
}/<code>

結論

  1. 經過破壞後的雙親委派模型更加高效,減少了很多類加載器之間不必要的委派操作
  2. JDK9的模塊化可以減少Java程序打包的體積,同時擁有更好的隔離線與封裝性
  3. 每個moudle擁有專屬的類加載器,程序在併發性上也會更加出色


分享到:


相關文章: