本週,個人秋招正式進入到了面試階段,不過如今來看,效果不盡人意,字節跳動一面開始的問題還比較簡單,但卻掛在了算法上,而面試阿里的時候的時候卻感到了本身知識的深度與廣度都還不夠。其中一個問題即是:什麼是雙親委派模型?嗯,這個背過。面試官接着又是一問,如何打破?這可沒學過……html
咱們知道,java的類加載過程是:加載->驗證->準備->解析->初始化->使用->卸載。java
加載過程能夠簡單經過ClassLoader將.class文件加載jvm中。c++
同時,咱們要知道,在Java中任意一個類都是由這個類自己和加載這個類的類加載器來肯定這個類在JVM中的惟一性。也就是你用你A類加載器加載的com.aa.ClassA
和你A類加載器加載的com.aa.ClassA
它們是不一樣的,也就是用instanceof
這種對比都是不一樣的。因此即便都來自於同一個class文件可是由不一樣類加載器加載的那就是兩個獨立的類。git
首先須要說明的是,雙親委派這個名字是很容易讓人誤會的,由於並不能幫助人理解後面的加載過程,反而讓人感受暈。雙親委派的原文是"parents delegate"。parents在英文中是「父母」、「雙親」的意思,但其實表達的是「父母這一輩」的人的意思。實際上這個模型中,只是表達「父母這一輩」的class loader而已,因此所謂的雙親委派就是:github
若是一個類加載器收到了加載某個類的請求,則該類加載器並不會去加載該類,而是把這個請求委派給父類加載器,每個層次的類加載器都是如此,所以全部的類加載請求最終都會傳送到頂端的啓動類加載器;只有當父類加載器在其搜索範圍內沒法找到所需的類,並將該結果反饋給子類加載器,子類加載器會嘗試去本身加載。
在java中,ClassLoader有四種web
虛擬機只有在兩個類的類名相同且加載該類的加載器均相同的狀況下才斷定這是一個類。若不採用雙親委派機制,同一個類有可能被多個類加載器加載,這樣該類會被識別爲兩個不一樣的類,相互賦值時會有問題。面試
雙親委派機制能保證多加載器加載某個類時,最終都是由一個加載器加載,確保最終加載結果相同。算法
其次是考慮到安全因素。假設經過網絡傳遞一個名爲java.lang.Integer的類,經過雙親委託模式傳遞到啓動類加載器,而啓動類加載器在覈心Java API發現這個名字的類,發現該類已被加載,並不會從新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣即可以防止核心API庫被隨意篡改。數據庫
雙親委派模型不是一種強制性約束,也就是你不這麼作也不會報錯怎樣的,它是一種JAVA設計者推薦使用類加載器的方式。
再想一想雙親委派是怎麼加載的,給父類,那咱們自定義一個ClassLoader不給父類不就好了嗎……當時的本身居然沒想到,仍是學的不夠透徹。tomcat
咱們能夠複寫loadClass(),讓其不向上委派
public class UnDelegationClassLoader extends ClassLoader { private String classpath; public UnDelegationClassLoader(String classpath) { super(null); this.classpath = classpath; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { Class<?> clz = findLoadedClass(name); if (clz != null) { return clz; } // jdk 目前對"java."開頭的包增長了權限保護,這些包咱們仍然交給 jdk 加載 if (name.startsWith("java.")) { return ClassLoader.getSystemClassLoader().loadClass(name); } return findClass(name); } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { InputStream is = null; try { String classFilePath = this.classpath + name.replace(".", "/") + ".class"; is = new FileInputStream(classFilePath); byte[] buf = new byte[is.available()]; is.read(buf); return defineClass(name, buf, 0, buf.length); } catch (IOException e) { throw new ClassNotFoundException(name); } finally { if (is != null) { try { is.close(); } catch (IOException e) { throw new IOError(e); } } } } }
對本問題的詳細研究能夠看——阿里面試題:JDBC、Tomcat爲何要破壞雙親委派模型
JDBC的Driver接口定義在JDK中,其實現由各個數據庫的服務商來提供,好比MySQL驅動包。DriverManager 類中要加載各個實現了Driver接口的類,而後進行管理,可是DriverManager位於 JAVA_HOME中jre/lib/rt.jar 包,由BootStrap類加載器加載,而其Driver接口的實現類是位於服務商提供的 Jar 包,根據類加載機制,當被裝載的類引用了另一個類的時候,虛擬機就會使用裝載第一個類的類裝載器裝載被引用的類。也就是說BootStrap類加載器還要去加載jar包中的Driver接口的實現類。咱們知道,BootStrap類加載器默認只負責加載 JAVA_HOME中jre/lib/rt.jar 裏全部的class,因此須要由子類加載器去加載Driver實現,這就破壞了雙親委派模型。
這個子類加載器是經過 Thread.currentThread().getContextClassLoader() 獲得的線程上下文加載器。
在 sun.misc.Launcher 初始化的時候,會獲取AppClassLoader,而後將其設置爲上下文類加載器,因此線程上下文類加載器默認狀況下就是系統加載器。
每一個Tomcat的webappClassLoader加載本身的目錄下的class文件,不會傳遞給父類加載器。
事實上,tomcat之因此造了一堆本身的classloader,大體是出於下面三類目的:
webapp
中的 class
和 lib
,須要相互隔離,不能出現一個應用中加載的類庫會影響另外一個應用的狀況,而對於許多應用,須要有共享的lib以便不浪費資源。jvm
同樣的安全性問題。使用單獨的 classloader
去裝載 tomcat
自身的類庫,以避免其餘惡意或無心的破壞;tomcat
修改文件不用重啓就自動從新裝載類庫而驚歎吧。面完字節,感受本身算法太弱了,得增強算法,面完阿里,感受本身太弱了,咋這麼多不會……秋招之路,任重道遠。
但願學弟學妹們對算法仍是重視起來,沒事的時候能夠刷刷leetcode,畢竟,若是想進大廠,手撕代碼是免不了的。
對此次阿里面試的一個簡單面經。
本文做者: 河北工業大學夢雲智開發團隊 - 李宜衡