Java實(shí)戰(zhàn)和面試寶典
一、基礎(chǔ)篇
?1.包裝類(lèi)Boolean是值傳遞還是引用傳遞?
代碼:
public class TEST {
? ? public static void main(String[] args) {
? ? ? ? Boolean flag = new Boolean(true);
? ? ? ? setFalse(flag);
? ? ? ? System.out.println(flag);
? ? }
? ? private static void setFalse(Boolean flag) {
? ? ? ? flag = new Boolean(false);
? ? }
}
flag仍舊輸出true;
在Java中,所有的參數(shù)都是按值傳遞的,而不是按引用傳遞的。這意味著你在傳遞一個(gè)對(duì)象作為參數(shù)時(shí),實(shí)際上是將對(duì)象的副本傳遞給了函數(shù)。因此,在這個(gè)例子中,當(dāng)你調(diào)用setFalse()方法時(shí),它會(huì)接收到一個(gè)Boolean類(lèi)型的副本,并將其設(shè)置為false。但是原始的flag仍舊保持著原來(lái)的值,因?yàn)樗鼪](méi)有被直接修改。
如果你想在方法中更改原始變量的值,你需要使用一個(gè)可變對(duì)象類(lèi)型,例如一個(gè)數(shù)組或者一個(gè)自定義類(lèi)。你可以將Boolean對(duì)象包裝在一個(gè)數(shù)組中,然后將該數(shù)組作為參數(shù)傳遞給setFalse()方法。這樣就可以修改數(shù)組第一個(gè)元素,從而更改原始的Boolean值。
結(jié)論:當(dāng)你把整個(gè)對(duì)象作為引用賦值給參數(shù)對(duì)象時(shí),實(shí)際上只是傳遞進(jìn)來(lái)的一個(gè)拷貝,你修改這個(gè)拷貝并不影響這個(gè)被傳遞的引用對(duì)象本身,但它如果有內(nèi)部屬性的話,是可以被重新設(shè)置的。
?2. HashMap鏈表轉(zhuǎn)紅黑樹(shù)為什么是大于8?
?HashMap鏈表轉(zhuǎn)紅黑樹(shù)的操作,以提高查找的速度,紅黑樹(shù)的時(shí)間復(fù)雜度O(logn),而鏈表是O(n/2),因此只在O(logn)<O(n/2)時(shí)才會(huì)進(jìn)行轉(zhuǎn)換,也就是以8作為分界點(diǎn)。HashMap的鏈表是雙向的,尋找小于鏈表長(zhǎng)度一半的節(jié)點(diǎn)從頭節(jié)點(diǎn)檢索,否則從尾節(jié)點(diǎn)檢索。
3.內(nèi)存告警,但CPU不高,dump中的也只有30M,我們2核4G的機(jī)器上配置了3G的內(nèi)存
啟動(dòng)指令:
nohup nice java -jar -DfileEncoding=utf-8 -Dserver.port=$RUN_PORT -Xms3000m -Xmx3000m -XX:+UseParallelGC -XX:ParallelGCThreads=20 -XX:+PrintGCDetail -jar ${JAR_FULLNAME}>dev/null 2>&1 &
具體表現(xiàn):
普羅米修斯顯示堆內(nèi)存占用82%,但是老年代還沒(méi)達(dá)到GC的閾值,所以不能觸發(fā)FULL GC,
但是我們機(jī)器的物理內(nèi)存確占用大于96%,再下去可能宕機(jī)。
進(jìn)程在申請(qǐng)內(nèi)存時(shí)并不是直接分配物理內(nèi)存的,而是分配一塊虛擬空間,到真正堆這塊虛擬空間寫(xiě)入數(shù)據(jù)時(shí)才會(huì)通過(guò)缺頁(yè)異常(Page Fault)處理機(jī)制分配物理內(nèi)存,也就是我們看到的進(jìn)程Res指標(biāo)。
當(dāng)我們服務(wù)啟動(dòng)時(shí),物理內(nèi)存時(shí)一條上升的線,在不斷生成對(duì)象的過(guò)程中堆內(nèi)存達(dá)到-Xmx,然后物理內(nèi)存就不會(huì)釋放了,就會(huì)看到一條直線,而且FULL GC不會(huì)釋放物理內(nèi)存,因?yàn)轭l繁對(duì)內(nèi)存擴(kuò)容和縮容很耗費(fèi)性能。

結(jié)論:這個(gè)內(nèi)存使用變化是正常的,只是-Xmx設(shè)置的太大了,然后java對(duì)象占用的內(nèi)存達(dá)到之后這個(gè)值以后,物理內(nèi)存就下不來(lái)了,重新指定-Xmx的值就行了。
?4.MySQL索引最佳實(shí)踐有哪些?
①最左匹配原則
指的是查詢(xún)從索引的最左前列開(kāi)始并且不跳過(guò)索引中的列。
聯(lián)合索引在查詢(xún)的時(shí)候會(huì)先去比對(duì)第一個(gè)字段,第一個(gè)字段比對(duì)完成之后再去比對(duì)第二個(gè)字段,以此類(lèi)推,不根據(jù)索引順序查詢(xún)無(wú)法精確定位到索引樹(shù)的某個(gè)節(jié)點(diǎn),導(dǎo)致無(wú)法根據(jù)索引樹(shù)查找。

所以使用了聯(lián)合索引,查找條件必須要按照聯(lián)合索引字段出現(xiàn)的順序排列,否則會(huì)導(dǎo)致索引失效。
②不在索引列上做任何操作(計(jì)算、函數(shù)、自動(dòng)或者手動(dòng)類(lèi)型轉(zhuǎn)換),會(huì)導(dǎo)致索引失效而轉(zhuǎn)向全表掃描
SQL中對(duì)name字段取了前3位,索引樹(shù)中沒(méi)有執(zhí)行函數(shù)之后的值,所以根本走不到索引。
需要考慮使用函數(shù)的結(jié)果是否可以在索引樹(shù)中定位,而且還可以保持索引樹(shù)依然有序。
③存儲(chǔ)引擎不能使用索引中范圍條件右邊的列

SQL走了部分字段,key_len為78是name字段和age字段之和,position字段并沒(méi)有走索引。
范圍后面的字段都不會(huì)再走索引。如果是大于等于則能使用到后面的兩個(gè)字段索引。
④盡量使用覆蓋索引(索引列包含查詢(xún)列),減少select * 語(yǔ)句
查詢(xún)盡量指明具體的字段,且盡可能被聯(lián)合索引覆蓋,覆蓋索引可以避免回表查詢(xún)
⑤在使用<>,not in, not exists的時(shí)候無(wú)法使用到索引
當(dāng)使用>,<,<>這些時(shí),MySQL內(nèi)部?jī)?yōu)化器會(huì)根據(jù)檢索比例、表大小等多個(gè)因素整體評(píng)估是否使用索引
⑥is null,is not null一般情況下也無(wú)法使用索引
因?yàn)閚ull值比較特殊,在索引樹(shù)中的字段不好比對(duì)。一般建議將字段設(shè)置為not null,即便字段沒(méi)有值,也要設(shè)置一個(gè)默認(rèn)值,方便走索引,提高效率。
⑦like以通配符開(kāi)頭,mysql索引失效會(huì)變成全表掃描操作
%在前面相當(dāng)于查詢(xún)是跳過(guò)了前面的部分字符,截取部分去查詢(xún),這剩下的部分已經(jīng)不再有序了,所以不會(huì)用到索引。
而%在后面相當(dāng)于最左前綴,從最開(kāi)始的字符串匹配,在索引樹(shù)中的數(shù)據(jù)還是有序的,所以可以使用索引。
⑧字符串不加單引號(hào)索引失效
查詢(xún)的時(shí)候盡量使用相同類(lèi)型,否則mysql在檢測(cè)到類(lèi)型不匹配時(shí),底層可能會(huì)強(qiáng)轉(zhuǎn)類(lèi)型或者使用函數(shù)進(jìn)行轉(zhuǎn)換從而被放棄走索引。
二、框架篇
1.Spring如何解決循環(huán)依賴(lài)問(wèn)題?
Spring能解決的循環(huán)依賴(lài)場(chǎng)景是setter注入的單例Bean,Spring通過(guò)三級(jí)緩存方案進(jìn)行解決。
初始化的Bean先放入三級(jí)緩存(singletonFactory),填充依賴(lài)時(shí)從三級(jí)緩存中取出并放入二級(jí)緩存(early半成品),在此過(guò)程中可升級(jí)bean(SmartInstantiationAwareBeanPostProcessor.getEarlyBeanReference,如AOP中的AbstractAutoProxyCreator),最終完成填充后放入一級(jí)緩存(成型的singleton)。
還有一些失效場(chǎng)景:?jiǎn)⒂聾Async+@EnableAsync,A和B相互依賴(lài),但是A中含有@Async修飾的方法,按照緩存解決循環(huán)依賴(lài)的思路,A初始,填充B,B初始,填充時(shí)從三級(jí)緩存拿到A,并放到二級(jí)緩存(中途在getEarlyBeanReference中可以執(zhí)行增強(qiáng)操作),最終B完成放入一級(jí)緩存,再回到A的填充階段,一級(jí)緩存中拿到了B并填充,最終A填充完畢,執(zhí)行初始化動(dòng)作,因?yàn)楸籃Async修飾,因此需要被AOP動(dòng)態(tài)代理,需要用到Bean對(duì)象增強(qiáng)處理器(AsyncAnnotationBeanPostProcessor,后置處理器。它只在一處postProcessorAfterInitization()實(shí)現(xiàn)了對(duì)代理對(duì)象的創(chuàng)建,未實(shí)現(xiàn)getEarlyBeanReference),最終A被增強(qiáng)了,但是B填充的A是普通對(duì)象,因此就失效了。
參考:今天一定要搞清楚Spring怎么解決循環(huán)依賴(lài) - 掘金
2.Spring中@Lazy注解加在類(lèi)上不起作用?
@Lazy是復(fù)制階段的beanPostProcessor做的。
A依賴(lài)B期待B延遲初始化,即使B上面加了延遲初始化,它也會(huì)進(jìn)行初始化。因?yàn)锧Autowire沒(méi)有增加@Lazy注解去標(biāo)識(shí)這個(gè)屬性不需要注入,所以會(huì)強(qiáng)行注入。
@Lazy注入的是代理對(duì)象,只有真正調(diào)用它方法時(shí)才會(huì)創(chuàng)建對(duì)象。
很多解決循環(huán)依賴(lài)的方法,歸根到底都是推遲依賴(lài)bean的創(chuàng)建。
三、中間件篇
1.Redis預(yù)扣庫(kù)存在并發(fā)場(chǎng)景下怎么將庫(kù)存同步到數(shù)據(jù)庫(kù)
可以考慮兩種方式:
一是通過(guò)定時(shí)任務(wù)同步,redis預(yù)減在并發(fā)環(huán)境下是可靠的,然后每秒鐘同步一下,保證最終一致就行。
二是redis扣減后,發(fā)布MQ消息,然后再更新數(shù)據(jù)庫(kù),sql應(yīng)為set stock = stock - 1,通過(guò)行級(jí)鎖保證數(shù)據(jù)的準(zhǔn)確性。
2.RabbitMQ中防止消息被重復(fù)消費(fèi),方案:使用redis存儲(chǔ)已消費(fèi)的數(shù)據(jù),每次消費(fèi)時(shí)判斷redis中是否有該條數(shù)據(jù),如果有則不消費(fèi),沒(méi)有則消費(fèi),那么當(dāng)redis宕機(jī)了怎么辦判斷消息是否重復(fù)消費(fèi)呢?
一是在業(yè)務(wù)側(cè)處理時(shí),業(yè)務(wù)狀態(tài)做流轉(zhuǎn)變更,緩存判斷后再通過(guò)業(yè)務(wù)狀態(tài)進(jìn)行判斷。
二是保證消息可查,消息除緩存外做持久化,消息被消費(fèi)后重新進(jìn)行標(biāo)記,這樣可以對(duì)重復(fù)消息或者未消費(fèi)消息進(jìn)行最終一致性補(bǔ)償。
