
一. Spring
1. 談?wù)勀銓?duì)Spring的理解
關(guān)鍵點(diǎn)
- 企業(yè)框架,目前最流行,沒有之一
- AOP、IOC、Spring MVC
2. Spring中用到了哪些設(shè)計(jì)模式
- 工廠模式,比如 BeanFactory
- 代理模式,在Aop實(shí)現(xiàn)中用到了JDK的動(dòng)態(tài)代理
- 單例模式,Bean的創(chuàng)建默認(rèn)就是單利的
3. IoC的啟動(dòng)過程
- Resource文件的定位,即找到bean的配置文件
- 通過特定的reader解析該bean配置文件,抽象成beanDefinition類
- 將beanDefinition向容器注冊(cè),寫入到一個(gè)大的HashMap中
4. BeanFactory 和 ApplicationContext 區(qū)別
- 功能,BeanFactory負(fù)責(zé)讀取bean配置,管理bean的加載,實(shí)例化,維護(hù)bean之間的依賴關(guān)系,負(fù)責(zé)bean的聲明周期;ApplicationContext除了提供上述BeanFactory所能提供的功能之外,還提供了國際化支持、資源訪問、事件傳遞、隊(duì)Web的支持等功能
- BeanFactroy采用的是延遲加載形式來注入Bean的,即只有在使用到某個(gè)Bean時(shí)(調(diào)用getBean()),才對(duì)該Bean進(jìn)行加載實(shí)例化;而ApplicationContext則相反,它是在容器啟動(dòng)時(shí),一次性創(chuàng)建了所有的Bean。
- BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但兩者之間的區(qū)別是:BeanFactory需要手動(dòng)注冊(cè),而ApplicationContext則是自動(dòng)注冊(cè)
5. Bean 的生命周期
- 實(shí)例化一個(gè)Bean,也就是我們通常說的new
- 按照Spring上下文對(duì)實(shí)例化的Bean進(jìn)行配置,也就是IOC注入
- 如果這個(gè)Bean實(shí)現(xiàn)了BeanNameAware接口,會(huì)調(diào)用它實(shí)現(xiàn)的setBeanName(String beanId)方法,此處傳遞的是Spring配置文件中Bean的ID
- 如果這個(gè)Bean實(shí)現(xiàn)了BeanFactoryAware接口,會(huì)調(diào)用它實(shí)現(xiàn)的setBeanFactory(),傳遞的是Spring工廠本身(可以用這個(gè)方法獲取到其他Bean)
- 如果這個(gè)Bean實(shí)現(xiàn)了ApplicationContextAware接口,會(huì)調(diào)用setApplicationContext(ApplicationContext)方法,傳入Spring上下文,該方式同樣可以實(shí)現(xiàn)步驟4,但比4更好,以為ApplicationContext是BeanFactory的子接口,有更多的實(shí)現(xiàn)方法
- 如果這個(gè)Bean關(guān)聯(lián)了BeanPostProcessor接口,將會(huì)調(diào)用postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor經(jīng)常被用作是Bean內(nèi)容的更改,并且由于這個(gè)是在Bean初始化結(jié)束時(shí)調(diào)用After方法,也可用于內(nèi)存或緩存技術(shù)
- 如果這個(gè)Bean在Spring配置文件中配置了init-method屬性會(huì)自動(dòng)調(diào)用其配置的初始化方法
- 如果這個(gè)Bean關(guān)聯(lián)了BeanPostProcessor接口,將會(huì)調(diào)用postAfterInitialization(Object obj, String s)方法 注意:以上工作完成以后就可以用這個(gè)Bean了,那這個(gè)Bean是一個(gè)single的,所以一般情況下我們調(diào)用同一個(gè)ID的Bean會(huì)是在內(nèi)容地址相同的實(shí)例
- 當(dāng)Bean不再需要時(shí),會(huì)經(jīng)過清理階段,如果Bean實(shí)現(xiàn)了DisposableBean接口,會(huì)調(diào)用其實(shí)現(xiàn)的destroy方法
- 最后,如果這個(gè)Bean的Spring配置中配置了destroy-method屬性,會(huì)自動(dòng)調(diào)用其配置的銷毀方法
6. Bean的作用域
- singleton(默認(rèn)): 在Spring的IoC容器中只存在一個(gè)對(duì)象實(shí)例,所有該對(duì)象的引用都共享這個(gè)實(shí)例。Spring 容器只會(huì)創(chuàng)建該bean定義的唯一實(shí)例,這個(gè)實(shí)例會(huì)被保存到緩存中,并且對(duì)該bean的所有后續(xù)請(qǐng)求和引用都將返回該緩存中的對(duì)象實(shí)例,一般情況下,無狀態(tài)的bean使用該scope。
- prototype:每次對(duì)該bean的請(qǐng)求都會(huì)創(chuàng)建一個(gè)新的實(shí)例,一般情況下,有狀態(tài)的bean使用該scope。
- request:每次http請(qǐng)求將會(huì)有各自的bean實(shí)例,類似于prototype。
- session:在一個(gè)http session中,一個(gè)bean定義對(duì)應(yīng)一個(gè)bean實(shí)例。
- global session:在一個(gè)全局的http session中,一個(gè)bean定義對(duì)應(yīng)一個(gè)bean實(shí)例。典型情況下,僅在使用portlet context的時(shí)候有效。
7. AOP
實(shí)現(xiàn)原理:默認(rèn),接口基于JDK動(dòng)態(tài)代理,類為cglib
注意事項(xiàng)
- 嵌套失效問題
- final類直接報(bào)錯(cuò)問題(動(dòng)態(tài)代理的實(shí)現(xiàn)原理)
8. Spring MVC
請(qǐng)求過程
- 用戶請(qǐng)求DispatchServlet
- DispatchServlet根據(jù)請(qǐng)求路徑調(diào)用具體HandlerMapping返回一個(gè)HandlerExcutionChain
- DispatchServlet調(diào)用HandlerAdapter適配器
- HandlerAdapter調(diào)用具體的Handler處理業(yè)務(wù)
- Handler處理結(jié)束返回一個(gè)具體的ModelAndView給適配器
- 適配器將ModelAndView給DispatchServlet
- DispatchServlet把視圖名稱給ViewResolver視圖解析器
- ViewResolver返回一個(gè)具體的視圖給DispatchServlet
- 渲染視圖,展示給用戶
二. JVM
1. 內(nèi)存劃分
JVM規(guī)范,將內(nèi)存分為 程序計(jì)數(shù)器、Java棧,也叫虛擬機(jī)棧、本地方法棧、方法區(qū)、堆
- 程序計(jì)數(shù)器 程序指令保存,從當(dāng)前指令到下一個(gè)指令,從程序計(jì)數(shù)器獲取下一個(gè)指令的地址,直到執(zhí)行所有的指令;線程私有
- Java棧(虛擬機(jī)棧) 保存方法棧幀,當(dāng)調(diào)用一個(gè)方法,則新創(chuàng)建一個(gè)棧幀,當(dāng)前的方法始終保持在棧幀的頂部;線程私有
- 本地方法棧 保存本地方法棧幀,當(dāng)調(diào)用一個(gè)本地方法,則新創(chuàng)建一個(gè)棧幀,當(dāng)前的方法始終保持在棧幀的頂部;線程私有
- 方法區(qū) 保存類信息、靜態(tài)常量、常量;線程共享
- 堆 保存對(duì)象,最最主要的垃圾收集之處;線程共享
2. 對(duì)象存活
- 引用計(jì)數(shù)算法:對(duì)對(duì)象引用進(jìn)行計(jì)數(shù),引用則計(jì)數(shù)器 +1,引用失效 -1;計(jì)數(shù)為 0 時(shí)候認(rèn)為不在引用,可以進(jìn)行回收;不能處理對(duì)象循環(huán)引用問題
- 可達(dá)性分析算法:主要采取此方式,采用虛擬機(jī)棧、類引用對(duì)象、常量對(duì)象、本地方法引用對(duì)象作為根,判斷對(duì)象到根是否存在引用關(guān)系,不可達(dá)則認(rèn)為不在引用,可以進(jìn)行回收;
3. GC回收算法
- 標(biāo)記-清除算法:對(duì)要回收的對(duì)象,先進(jìn)行標(biāo)志,后進(jìn)行清除,你懂得;但是,久之,會(huì)存在內(nèi)存不連續(xù),比如,一次垃圾回收,回收了,(0,1)和(0,3)和(0,5)三個(gè)位置,但是沒有回收(0,2)和(0,4),那下次的內(nèi)存,就無法使用(0,1)-(0,5)的連續(xù)空間;
- 復(fù)制算法:為改進(jìn)上面的內(nèi)存碎片問題而產(chǎn)生,對(duì)內(nèi)存分為兩部分,交替回收其中一部分,存活的對(duì)象復(fù)制到另一部分空間,你懂得;但是,有點(diǎn)浪費(fèi),比如,內(nèi)存分為,(0,1)-(0,3)和(0,3)-(0,5)兩個(gè)部分,某次回收(0,1)-(0,3)空間,將存活對(duì)象拷貝到(0,3)-(0,5),而后在(0,1)-(0,3)分配對(duì)象;
- 標(biāo)記-整理算法:為改進(jìn)上面的內(nèi)存浪費(fèi)問題而產(chǎn)生,對(duì)要回收的對(duì)象,先進(jìn)行標(biāo)志,后進(jìn)行清除,你懂得,然后,將存活的對(duì)象,進(jìn)行整理,移動(dòng)到邊界位置,似的剩余空間連續(xù);比如,一次垃圾回收,回收了,(0,1)和(0,3)和(0,5)三個(gè)位置,但是沒有回收(0,2)和(0,4),然后,將(0,2)和(0,4)移動(dòng)到(0,1)和(0,2),使得(0,3)-(0,5)的空間連續(xù);
4. 類加載過程
jvm中class類的加載過程,大致分為這幾個(gè)步驟
- 加載(load)
- 根據(jù)全類名,加載類的二進(jìn)制字節(jié)流
- 將字節(jié)流轉(zhuǎn)存方法區(qū)
- 生成Class對(duì)象作為訪問入口
- 驗(yàn)證(verify)
- class文件的格式驗(yàn)證,驗(yàn)證是否符合JVM規(guī)范
- class中的元數(shù)據(jù)驗(yàn)證,驗(yàn)證是否符合Java規(guī)范
- class的字節(jié)碼驗(yàn)證,驗(yàn)證數(shù)據(jù)流控制流不會(huì)危害JVM環(huán)境
- 準(zhǔn)備(prepare)
- 給變量分配內(nèi)存
- 初始化零值(比如int默認(rèn)為0,boolean默認(rèn)為false)
- final變量直接賦值
- 解析
- 符號(hào)引用變?yōu)橹苯右?/li>
- 類、字段、方法、接口方法解析
- 初始化
- 初始化變量
- 構(gòu)造函數(shù)
- static塊
5. 雙親委派機(jī)制
Java中,大概有三種類型加載器,啟動(dòng)類加載器(Bootstrap)<- 標(biāo)準(zhǔn)擴(kuò)展類加載器(Extension)<- 應(yīng)用程序類加載器(Application )<- 上下文類加載器(Custom),從右到左,盡量父類進(jìn)行加載,當(dāng)父類無法進(jìn)行加載時(shí)候,才會(huì)使用子類進(jìn)行加載
- 意義
- 防止同一個(gè)JVM,內(nèi)存中出現(xiàn)兩份class二進(jìn)制字節(jié)碼
- 加載過程
- 從已加載的類查找是否已經(jīng)存在,存在不需要再次加載
- 若不存在,則去parent中查找,存在不需要再次加載
- 若不存在,遞歸在parent中查找,直到找到為止
- 若找遍所有parent均不存在,且當(dāng)前加載器已經(jīng)沒有parent加載器,則調(diào)用當(dāng)前類加載器的findClass方法,如果能加載,結(jié)束
- 如果不能,則遞歸返回child類加載器,繼續(xù)調(diào)用findClass方法,如果能加載,結(jié)束
- 如果找遍所有child的findClass方法,還是不能加載,則拋出異常
- 破壞雙親委派機(jī)制
- 將parent設(shè)為null
- 重寫load(String,boolean)方法,改變類的查找機(jī)制。
三. 多線程
1. 死鎖的四個(gè)條件
- 互斥
- 請(qǐng)求與保持
- 不剝奪
- 循環(huán)等待
2. 檢查死鎖
- Jconsole查看死鎖
- Jstack查看死鎖
3. volatile
- JMM
- 內(nèi)存可見性
- 防止指令重排序
- 不能保證原子性
4. synchronized
作用
- 互斥訪問
- 內(nèi)存可見性
- 防止指令重排序
用法
- 修飾普通方法
- 修飾靜態(tài)方法
- 修飾代碼塊
注意點(diǎn)
- 當(dāng)一個(gè)線程在訪問對(duì)象的 synchronized 方法,因?yàn)閷?duì)象只有一把鎖,其他線程無法獲取該對(duì)象的鎖,所以無法訪問該對(duì)象的其他synchronized實(shí)例方法,但是其他線程還是可以訪問該實(shí)例對(duì)象的其他非synchronized方法
- 實(shí)現(xiàn)原理為,對(duì)象監(jiān)視器,Monitor
5. volatile vs synchronized vs lock
- 來源差異:volatile、synchronized為Java關(guān)鍵字; lock是Java類
- 代價(jià)開銷:volatile不是鎖,代價(jià)最?。?lock是一般基于AQS,相對(duì)比synchronized代價(jià)?。?synchronized代價(jià)最大
- 簡單性:volatile、synchronized為Java關(guān)鍵字,JVM全權(quán)幫忙維護(hù),只要我們能正確使用,不需要我們太多關(guān)心維護(hù); lock是Java類,有很多方法可以調(diào)用,靈活性最好,但是需要自己控制鎖的獲取、釋放
6. 進(jìn)程間通信
- 管道pipe
- 命名管道FIFO
- 消息隊(duì)列MessageQueue
- 共享存儲(chǔ)SharedMemory
- 信號(hào)量Semaphore
- 套接字Socket
- 信號(hào) ( sinal )
7. 原子操作類
-
CountDownLatch:
- 某線程需要等待多個(gè)線程執(zhí)行完畢,再執(zhí)行。
- 實(shí)現(xiàn)多個(gè)線程共同等待,同時(shí)開始執(zhí)行任務(wù),不可重用。(此類似于CyclicBarrier,可重用)
-
CyclicBarrier:
- CyclicBarrier允許一組線程互相等待,直到到達(dá)某個(gè)公共屏障點(diǎn)。
- 與CountDownLatch不同的是該barrier在釋放等待線程后可以重用,所以稱它為循環(huán)(Cyclic)的屏障
-
CountDownLatch、CyclicBarrier差別:
- CountDownLatch和CyclicBarrier都能夠?qū)崿F(xiàn)線程之間的等待,只不過它們側(cè)重點(diǎn)不同: CountDownLatch一般用于某個(gè)線程A等待若干個(gè)其他線程執(zhí)行完任務(wù)之后,它才執(zhí)行; 而CyclicBarrier一般用于一組線程互相等待至某個(gè)狀態(tài),然后這一組線程再同時(shí)執(zhí)行;
- CountDownLatch是不能夠重用的,而CyclicBarrier是可以重用的。
-
Semaphore:
- Semaphore是一個(gè)計(jì)數(shù)信號(hào)量,它的本質(zhì)是一個(gè)”共享鎖”。
- 信號(hào)量維護(hù)了一個(gè)信號(hào)量許可集。線程可以通過調(diào)用acquire()來獲取信號(hào)量的許可;當(dāng)信號(hào)量中有可用的許可時(shí),線程能獲取該許可;否則線程必須等待,直到有可用的許可為止。 線程可以通過release()來釋放它所持有的信號(hào)量許可。
- Semaphore其實(shí)和鎖有點(diǎn)類似,它一般用于控制對(duì)某組資源的訪問權(quán)限。
-
Exchanger:
- 用于進(jìn)行線程間的數(shù)據(jù)交換。
- 兩個(gè)線程通過exchange方法交換數(shù)據(jù)
- 該工具類的線程對(duì)象是成對(duì)的
-
ThreadLocal:
- 每個(gè)Thread 維護(hù)一個(gè) ThreadLocalMap 映射表,這個(gè)映射表的 key 是 ThreadLocal實(shí)例本身,value 是真正需要存儲(chǔ)的 Object。
- ThreadLocal 本身并不存儲(chǔ)值,它只是作為一個(gè) key 來讓線程從 ThreadLocalMap 獲取 value。值得注意的是圖中的虛線,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作為 Key 的,弱引用的對(duì)象在 GC 時(shí)會(huì)被回收。
- ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個(gè)ThreadLocal沒有外部強(qiáng)引用來引用它,那么系統(tǒng) GC 的時(shí)候,這個(gè)ThreadLocal勢必會(huì)被回收,這樣一來,ThreadLocalMap中就會(huì)出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會(huì)一直存在一條強(qiáng)引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠(yuǎn)無法回收,造成內(nèi)存泄漏。
- ThreadLocal里面使用了一個(gè)存在弱引用的map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個(gè)threadlocal實(shí)例。這個(gè)Map的確使用了弱引用,不過弱引用只是針對(duì)key。每個(gè)key都弱引用指向threadlocal。 當(dāng)把threadlocal實(shí)例置為null以后,沒有任何強(qiáng)引用指向threadlocal實(shí)例,所以threadlocal將會(huì)被gc回收。 但是,我們的value卻不能回收,而這塊value永遠(yuǎn)不會(huì)被訪問到了,所以存在著內(nèi)存泄露。因?yàn)榇嬖谝粭l從current thread連接過來的強(qiáng)引用。只有當(dāng)前thread結(jié)束以后,current thread就不會(huì)存在棧中,強(qiáng)引用斷開,Current Thread、Map value將全部被GC回收。最好的做法是將調(diào)用threadlocal的remove方法,這也是等會(huì)后邊要說的。
- 使用static的ThreadLocal,延長了ThreadLocal的生命周期,可能導(dǎo)致內(nèi)存泄漏。
- 分配使用了ThreadLocal又不再調(diào)用get(),set(),remove()方法,那么就會(huì)導(dǎo)致內(nèi)存泄漏,因?yàn)檫@塊內(nèi)存一直存在。
寫在最后
限于篇幅,本文只收錄Spring、JVM、多線程的部分面試題;完整的面試題包含Kafka、Mysql、Tomcat、Docker、Spring、MyBatis、Nginx、Netty、Dubbo、Redis、Netty、Spring cloud、分布式、高并發(fā)、性能調(diào)優(yōu)、微服務(wù)等架構(gòu)技。筆者已經(jīng)整理打包好了!
需要的朋友點(diǎn)擊下方傳送門, 即可免費(fèi)領(lǐng)取面試資料和視頻學(xué)習(xí)資料
傳送門
以下是部分面試題截圖
