網際網路大廠java面試題一京東

碼農彬哥 發佈 2022-02-06T20:43:22+00:00

l GB2312:雙字節編碼,總編碼範圍是 A1-A7,A1-A9 是符號區,包含 682 個字符, B0-B7 是 漢字區,包含 6763 個漢字;

1、哪些情況下的對象會被垃圾回收機制處理掉?

利用可達性分析算法,虛擬機會將一些對象定義為 GCRoots,從 GCRoots 出發沿著引用鏈向下尋找,如果某個對象不能通過 GCRoots 尋找到,虛擬機就認為該對象可以被回收掉。

* 哪些對象可以被看做是 GCRoots 呢?

1)虛擬機棧(棧幀中的本地變量表)中引用的對象;

2)方法區中的類靜態屬性引用的對象,常量引用的對象;

3)本地方法棧中 JNI(Native 方法)引用的對象;

* 對象不可達,一定會被垃圾收集器回收麼?

即使不可達,對象也不一定會被垃圾收集器回收,

1)先判斷對象是否有必要執行 finalize()方法,對象必須重寫 finalize()方法且沒有被運行過。

2)若有必要執行,會把對象放到一個隊列中,JVM 會開一個線程去回收它們,這是對象最後一次可以逃逸清理的機會。

2、講一下常見編碼方式?

編碼的意義:計算機中存儲的最小單元是一個字節即 8bit,所能表示的字符範圍是 255 個, 而人類要表示的符號太多,無法用一個字節來完全表示,固需要將符號編碼,將各種語言翻 譯成計算機能懂的語言。

* ASCII 碼:總共 128 個,用一個字節的低 7 位表示,0〜31 控制字符如換回車刪除等;

32~126 是列印字符,可通過鍵盤輸入並顯示出來;

* ISO-8859-1,用來擴展 ASCII 編碼,256 個字符,涵蓋了大多數西歐語言字符。

* GB2312:雙字節編碼,總編碼範圍是 A1-A7,A1-A9 是符號區,包含 682 個字符, B0-B7 是 漢字區,包含 6763 個漢字;

* GBK 為了擴展 GB2312,加入了更多的漢字,編碼範圍是 8140~FEFE,有 23940 個 碼位,能 表示 21003 個漢字。

* UTF-16: ISO 試圖想創建一個全新的超語言字典,世界上所有語言都可通過這本字典Unicode 來相互翻譯,而 UTF-16 定義了 Unicode 字符在計算機中存取方法,用兩個字節來表 示 Unicode 轉化格式。不論什麼字符都可用兩字節表示,即 16bit,固叫UTF-16。

* utf-8:UTF-16 統一採用兩字節表示一個字符,但有些字符只用一個字節就可表示,浪費存儲空間,而 UTF-8 採用一種變長技術,每個編碼區域有不同的字碼長度。 不同類型的 字 符 可 以 由 1~6 個字節組成。

3、utf-8 編碼中的中文占幾個字節;int 型幾個字節?

utf-8 是一種變長編碼技術,utf-8 編碼中的中文占用的字節不確定,可能 2 個、3 個、4個,int 型占 4 個字節。

4、靜態代理和動態代理的區別,什麼場景使用?

代理是一種常用的設計模式,目的是:為其他對象提供一個代理以控制對某個對象的訪問,將兩個類的關係解耦。代理類和委託類都要實現相同的接口,因為代理真正調用的是委託類的方法。

區別:

* 靜態代理:由程式設計師創建或是由特定工具生成,在代碼編譯時就確定了被代理的類是哪一個是靜態代理。靜態代理通常只代理一個類;

* 動態代理:在代碼運行期間,運用反射機制動態創建生成。動態代理代理的是一個接口下的多個實現類;

實現步驟:

a.實現 InvocatIOnHandler 接口創建自己的調用處理器;

b.給 Proxy 類提供 Classloader 和代理接口類型數組創建動態代理類;

c.利用反射機製得到動態代理類的構造函數;

d.利用動態代理類的構造函數創建動態代理類對象;

使用場景:Retrofit 中直接調用接口的方法;Spring 的 AOP 機制;

5、簡述下 Java 的異常體系。

java 中 Throwable 是所有異常和錯誤的超類,兩個直接子類是 Error(錯誤)和Exception(異常):

* Error 是程序無法處理的錯誤,由 JVM 產生和拋出,如 OOM、ThreadDeath 等。這些異常 發生時,JVM 一般會選擇終止程序。

* Exception 是程序本身可以處理的異常

1).運行時異常(RuntimeException)也稱作未檢測的異常(unchecked exception),這表示這種異常不需要編譯器來檢測。RuntimeException是所有可以在運行時拋出的異常的父類。一個方法除要捕獲異常外,如果它執行的時候可能會拋出RuntimeException的子類,那麼它就不需要用throw語句來聲明拋出的異常。運行時異常有NullPointerException、IndexOutOfBoundsException 等。

2).非 運 行 時 異 常又叫受檢查異常(checked exception)是編譯器在編譯時進行校驗的,通過throws語句或者try{}cathch{} 語句塊來處理檢測異常。編譯器會分析哪些異常會在執行一個方法或者構造函數的時候拋出。,這些異常一般是由程序邏輯錯誤引起 的,應儘可能避免。非運行時異常有IOException\SQlException\FileNotFoundException 以及 由用戶自定義的Exception 異常等。

6、談談你對解析與分派的認識。

解析指方法在運行前,即編譯期間就可知的,有一個確定的版本,運行期間也不會改變。解析是靜態的,在類加載的解析階段就可將符號引用轉變成直接引用。

分派可分為靜態分派和動態分派,重載屬於靜態分派,覆蓋屬於動態分派。靜態分派是指在重載時通過參數的靜態類型而非實際類型作為判斷依據,在編譯階段,編譯器可根據參數的靜態類型決定使用哪一個重載版本。動態分派則需要根據實際類型來調用相應的方法。

7、修改對象 A 的 equals 方法的簽名,那麼使用 HashMap 存放這個對象實例的時候,會用哪個 equals 方法?

會調用對象對象的 equals 方法。

「==」如果是基本類型的話就是看他們的數據值是否相等就可以。 如果是引用類型的話,比較的是棧內存局部變量表中指向堆內存中的指針的值是否相等。 「equals」如果對象的equals 方法沒有重寫的話,equals 方法和「==」是同一種。hashcod 是返回對象實例內存地址的 hash 映射。 理論上所有對象的 hash 映射都是不相同的。

8、Java 中實現多態的機制是什麼?

多態是指程序中定義的引用變量所指向的具體類型和通過該引用變量發出的方法調用在編譯時不確定,在運行期間才確定,一個引用變量到底會指向哪個類的實例。這樣就可以不用 修改源程序,就可以讓引用變量綁定到各種不同的類實現上。Java 實現多態有三個必要條件:

繼承、重定、向上轉型,在多態中需要將子類的引用賦值給父類對象,只有這樣該引用才能夠具備調用父類方法和子類的方法。

9、如何將一個 Java 對象序列化到文件里?

ObjectOutputStream.writeObject()負責將指定的流寫入,ObjectInputStream.readObject()

從指 定流讀取序列化數據。

//寫 入

try {
        ObjectOutputStream os = new ObjectOutputStream(new
        FileOutputStream("D:/student.txt"));
        os.writeObject(studentlist);
        os.close();
      } catch (FileNotFoundExceptione) {
        e.printStackTrace();    
      } catch (IOExceptione) {
        e.printStackTrace();
}

10、說說你對 Java 反射的理解。

在運行狀態中,對任意一個類,都能知道這個類的所有屬性和方法,對任意一個對象,都能調用它的任意一個方法和屬性。這種能動態獲取信息及動態調用對象方法的功能稱為 java 語言的反射機制。

反射的作用:開發過程中,經常會遇到某個類的某個成員變量、方法或屬性是私有的,或只對系統應用開放,這裡就可以利用 java 的反射機制通過反射來獲取所需的私有成員或是方法。

* 獲取類的 Class 對象實例 Class clz=Class.forName("com.zhenai.api.Apple");

* 根據 Class 對象實例獲取 Constructor 對 象 Constructor appConstructor = clz.getConstructor();

* 使用 Constructor 對 象 的 newInstance 方 法 獲 取 反 射 類 對 象 Object appleObj = appConstructor.newInstance();

* 獲取方法的 Method 對象

MethodsetPriceMethod=clz.getMethod("setPrice",int.class);

* 利用 invoke 方法調用方法 setPriceMethod.invoke(appleObj,14);

* 通過 getFields()可以獲取 Class 類的屬性,但無法獲取私有屬性,而getDeclaredFields()可 以獲取到包括私有屬性在內的所有屬性。帶有 Declared 修飾的方法可以反射到私有的方法, 沒有 Declared 修飾的只能用來反射公有的方法,其他如Annotation\Field\Constructor 也是如此。

11、說說你對 Java 註解的理解。

註解是通過@interface 關鍵字來進行定義的,形式和接口差不多,只是前面多了一個@

public@interfaceTestAnnotation{

}

使用時@TestAnnotation 來引用,要使註解能正常工作,還需要使用元註解,它是可以註解到註解上的註解。

元標籤有@Retention、@Documented、@Target、@Inherited 和 @Repeatable 五種。

@Retention 說明註解的存活時間,取值有 RetentionPolicy.SOURCE 註解只在源碼階段保留,在編譯器進行編譯時被丟棄;RetentionPolicy.ClASS 註解只保留到編譯進行的時候,並不會被加載到 JVM 中。RetentionPolicy.RUNTIME 可以留到程序運行的時候,它會被加載進入到 JVM 中,所以在程序運行時可以獲取到它們。

@Documented 註解中的元素包含到 javadoc 中去。

@Target 限定註解的應用場景,ElementType.FIELD 給屬性進行註解;

ElementType.lOCAl_VARIABlE 可以給局部變量進行註解;ElementType.METHOD 可以給方法進行註解;ElementType.PACKAGE 可以給一個包進行註解 ElementType.TYPE

可以給一個類型進行註解,如類、接口、枚舉。

@Inherited 若一個超類被@Inherited 註解過的註解進行註解,它的子類沒有被任何註解應用的話,該子類就可繼承超類的註解;

註解的作用:

* 提供信息給編譯器:編譯器可利用註解來探測錯誤和警告信息

* 編譯階段:軟體工具可以利用註解信息來生成代碼、html 文檔或做其它相應處理;

* 運行階段:程序運行時可利用註解提取代碼

註解是通過反射獲取的,可以通過 Class 對象的 isAnnotationPresent()方法判斷它是否應用了某個註解,再通過 getAnnotation()方法獲取 Annotation 對象

12、說一下泛型原理,並舉例說明。

泛型就是將類型變成參數傳入,使得可以使用的類型多樣化,從而實現解耦。Java 泛型是在Java1.5 以後出現的,為保持對以前版本的兼容,使用了擦除的方法實現泛型。擦除是指在一定程度無視類型參數 T,直接從 T 所在的類開始向上 T 的父類去擦除,如調用泛型方法,傳入類型參數 T 進入方法內部,若沒在聲明時做類似publicTmethodName(TextendsFathert){},Java 就進行了向上類型的擦除,直接把參數 t 當做 Object 類來處理,而不是傳進去的 T。 即在有泛型的任何類和方法內部,它都無法知道自己的泛型參數,擦除和轉型都是在邊界上發生,即傳進去的參在進入類或方法時被擦除掉,但傳出來的時候又被轉成了我們設置的 T。在泛型類或方法內,任何涉及到具體類型(即擦除 後的類型的子類)操作都不能進行,如 newT(),或者 T.play()(play 為某子類的方法而不是擦除後的類的方法)。

13、談談你對 Java 中 string 的了解。

* String 類是 final 型,因此String 類不能被繼承,它的成員方法也都默認為 final 方法。

String 對象一旦創建就固定不變了,對 String 對象的任何改變都不影響到原對象,相關的任何改變 操作都會生成新的 String 對象。

* String 類是通過 char 數組來保存字符串的,String 對 equals 方法進行了重定,比較的是值相等。

String a="test";String b="test";String c=new String("test");

a、b 和字面上的 test 都是指向 JVM 字符串常量池中的"test"對象,他們指向同一個對象。而 new 關鍵字一定會產生一個對象 test,該對象存儲在堆中。所以 new String("test")產生了兩個對象,保存在棧中的 c 和保存在堆中的 test。而在 java 中根本就不存在兩個完全一模一樣的字符串對象,故在堆中的 test 應該是引用字符串常量池中的 test。

例:String str1="abc";//棧中開闢一塊空間存放引用 str1,str1 指向池中 String 常量"abc"

String str2="def";//棧中開闢一塊空間存放引用 str2,str2 指向池中 String 常量"def"

String str3=str1+str2;//棧中開闢一塊空間存放引用 str3//str1+str2 通過 StringBuilder 的最後一步

toString()方法返回一個新的 String 對象"abcdef"

//會在堆中開闢一塊空間存放此對象,引用 str3 指向堆中的(str1+str2)所返回的新 String 對象。

System.out.println(str3=="abcdef");//返回 false 因為 str3 指向堆中的"abcdef"對象,而"abcdef"是字符池中的對象,所以結果為 false。JVM 對

String str="abc"對象放在常量池是在編譯時做的 , 而 String str3=str1+str2 是在運行時才知道的,new 對象也是在運行時才做的。

14、String 為什麼要設計成不可變的?

* 字符串常量池需要 String 不可變。因為 String 設計成不可變,當創建一個 String 對象

時, 若此字符串值已經存在於常量池中,則不會創建一個新的對象,而是引用已經存在的

對象。 如果字符串變量允許必變,會導致各種邏輯錯誤,如改變一個對象會影響到另一個

獨立對象。

* String 對象可以緩存 hashCode。字符串的不可變性保證了 hash 碼的唯一性,因此可

以緩 存 String 的 hashCode,這樣不用每次去重新計算哈希碼。在進行字符串比較時,

可以直接比較 hashCode,提高了比較性能;

* 安全性。String 被許多 java 類用來當作參數,如 url 地址,文件 path 路徑,反射機

制所 需的 Strign 參數等,若 String 可變,將會引起各種安全隱患。

15、Redis 常見的幾種數據結構說一下?各自的使用場景?

string

介紹:string 數據結構是簡單的 key-value 類型。

使用場景: 一般常用在需要計數的場景,比如用戶的訪問次數、熱點文章的點讚轉發數量等等。

list

介紹:list 即是 鍊表

使用場景:發布與訂閱或者說消息隊列、慢查詢。

hash

介紹:hash 類似於 JDK1.8 前的 HashMap,內部實現也差不多(數組 + 鍊表)。

使用場景:系統中對象數據的存儲。

set

介紹:set 類似於 Java 中的 HashSet 。Redis 中的 set 類型是一種無序集合,集合中的元素沒有先後順序。當你需要存儲一個列表數據,又不希望出現重複數據時,set 是一個很好的選擇,並且 set 提供了判斷某個成員是否在一個 set 集合內的重要接口,這個也是 *ist 所不能提供的。可以基於 set 輕易實現交集、並集、差集的操作使用場景: 需要存放的數據不能重複以及需要獲取多個數據源交集和並集等場景。

sorted set

介紹:和 set 相比,sorted set 增加了一個權重參數 score,使得集合中的元素能夠按

score 進行有序排列,還可以通過 score 的範圍來獲取元素的列表。有點像是 Java 中HashMap 和 TreeSet 的結合體。

使用場景:需要對數據根據某個權重進行排序的場景。比如在直播系統中,實時排行信息包含直播間在線用戶列表,各種禮物排行榜,彈幕消息(可以理解為按消息維度的消息排行榜)等信息。

bitmap

介紹:bitmap 存儲的是連續的二進位數字(0 和 1),通過 bitmap, 只需要一個 bit 位來表示某個元素對應的值或者狀態,key 就是對應元素本身 。我們知道 8 個 bit 可以組成一個byte,所以 bitmap 本身會極大的節省儲存空間。

使用場景:適合需要保存狀態信息(比如是否簽到、是否登錄...)並需要進一步對這些信息進行分析的場景。比如用戶簽到情況、活躍用戶情況、用戶行為統計(比如是否點讚過某個視頻)。

16、談一談緩存穿透、緩存擊穿和緩存雪崩,以及各自的解決方案?

緩存穿透

* 問題:大量並發查詢不存在的 KEY,在緩存和資料庫中都不存在,同時給緩存和資料庫帶來壓力。

* 原因:一般而言,緩存穿透有 2 種可能性:業務數據被誤刪,導致緩存和資料庫中都沒有數據。惡意進行 ddos 攻擊。

* 分析:為什麼會多次透傳呢?不存在 一直為空,需要注意讓緩存能夠區分 KEY 不存在和查詢到一個空值。

* 解決辦法:緩存空值的 KEY,這樣第一次不存在也會被加載會記錄,下次拿到有這個KEY。Bloom 過濾或 RoaingBitmap 判斷 KEY 是否存在,如果布隆過濾器中沒有查到這個數據,就不去資料庫中查。在處理請求前增加惡意請求檢查,如果檢測到是惡意攻擊,則拒絕進行服務。完全以緩存為準,使用延遲異步加載的策略(異步線程負責維護緩存的數據,定期或根據條件觸發更新),這樣就不會觸發更新。

緩存擊穿

* 問題:某個 KEY 失效的時候,正好有大量並發請求訪問這個 KEY。

* 分析:跟穿透其實很像,屬於比較偶然的。

* 解決辦法:KEY 的更新操作添加全局互斥鎖。完全以緩存為準,使用延遲異步加載的策略(異步線程負責維護緩存的數據,定期或根據條件觸發更新),這樣就不會觸發更新。

緩存雪崩

* 問題:當某一時刻發生大規模的緩存失效的情況,導致大量的請求無法獲取數據,從而將流量壓力傳導到資料庫上,導致資料庫壓力過大甚至宕機。

* 原因:一般而言,緩存雪崩有 2 種可能性:大量的數據同一個時間失效:比如業務關係強相關的數據要求同時失效 Redis 宕機

* 分析:一般來說,由於更新策略、或者數據熱點、緩存服務宕機等原因,可能會導致緩存數據同一個時間點大規模不可用,或者都更新。所以,需要我們的更新策略要在時間上合適,數據要均勻分享,緩存伺服器要多台高可用。

* 解決辦法:更新策略在時間上做到比較平均。如果數據需要同一時間失效,可以給這批數據加上一些隨機值,使得這批數據不要在同一個時間過期,降低資料庫的壓力。使用的熱 數據儘量分散到不同的機器上。多台機器做主從複製或者多副本,實現高可用。做好主從的部署,當主節點掛掉後,能快速的使用從結點頂上。實現熔斷限流機制,對系統進行負載能力控制。對於非核心功能的業務,拒絕其請求,只允許核心功能業務訪問資料庫獲取數據。

服務降價:提供默認返回值,或簡單的提示信息。

17、講下 Kafka、RabbitMQ、RocketMQ 之間的區別是什麼?

性能

消息中間件的性能主要衡量吞吐量,Kafka 的吞吐量比 RabbitMQ 要高出 1~2 個數量級

RabbitMQ 的單機 QPS 在萬級別,Kafka 的單機 QPS 能夠達到百萬級別。RocketMQ

單機寫入 TPS 單實例約 7 萬條/秒,單機部署 3 個 Broker,可以跑到最高 12 萬條/秒,

消息大小 10 個字節,Kafka 如果開啟冪等、事務等功能,性能也會有所降低。

數據可靠性

Kafka 與 RabbitMQ 都具備多副本機制,數據可靠性較高。RocketMQ 支持異步實時刷盤,

同步刷盤,同步 Replication,異步 Replication。

服務可用性

Kafka 採用集群部署,分區與多副本的設計,使得單節點宕機對服務無影響,且支持消息容

量的線性提升。RabbitMQ 支持集群部署,集群節點數量有多種規格。RocketMQ 是分布式

架構,可用性高。

功能

Kafka 與 RabbitMQ 都是比較主流的兩款消息中間件,具備消息傳遞的基本功能,但在一些特殊的功能方面存在差異,RocketMQ 在阿里集團內部有大量的應用在使用。

18、Kafka 的架構說一下?

整個架構中包括三個角色。

* 生產者(Producer):消息和數據生產者。

* 代理(Broker):緩存代理,Kafka 的核心功能。

* 消費者(Consumer):消息和數據消費者。

Kafka 給 Producer 和 Consumer 提供註冊的接口,數據從 Producer 發送到 Broker,Broker 承擔一個中間緩存和分發的作用,負責分發註冊到系統中的 Consumer。

19、Kafka 怎麼保證消息是有序的?

消息在被追加到 Partition(分區)的時候都會分配一個特定的偏移量(offset)。Kafka 通過偏

移量(offset)來保證消息在分區內的順序性。發送消息的時候指定 key/Partition。

20、Kafka 怎麼保證消息不丟失?

生產者丟失消息的情況

生產者(Producer) 調用 send 方法發送消息之後,消息可能因為網絡問題並沒有發送過去。

為了確定消息是發送成功,我們要判斷消息發送的結果,Kafka 生產者(Producer) 使用

send 方法發送消息實際上是異步的操作,我們可以通過 get()方法獲取調用結果,但是這樣

也讓它變為了同步操作,可以採用為其添加回調函數的形式,示例代碼如下:

ListenableFuture<SendResult<String, Object>> future = kafkaTemplate.send(topic, o);

future.addCallback(result -> logger.info("生產者成功發送消息到 topic:{} partition:{}的消息", result.getRecordMetadata().topic(), result.getRecordMetadata().partition()), ex -> logger.error("生產者發送消失敗,原因:{}", ex.getMessage()));Producer 的 retries(重試次數)設置一個比較合理的值,一般是 3 ,但是為了保證消息不 丟失的話一般會設置比較大一點。設置完成之後,當出現網絡問題之後能夠自動重試消息發送, 避免消息丟失。另外,建議還要設置重試間隔,因為間隔太小的話重試的效果就不明顯了,網絡波動一次你 3 次一下子就重試完了,消費者丟失消息的情況當消費者拉取到了分區的某個消息之後,消費者會自動提交了 offset。自動提交的話會有一個問題,試想一下,當消費者剛拿到這個消息準備進行真正消費的時候,突然掛掉了,消息實際上並沒有被消費,但是 offset 卻被自動提交了。

解決辦法也比較粗暴,我們手動關閉自動提交 offset,每次在真正消費完消息之後再自己手動提交 offset 。 但是,細心的朋友一定會發現,這樣會帶來消息被重新消費的問題。比如你剛剛消費完消息之後,還沒提交 offset,結果自己掛掉了,那麼這個消息理論上就會被消費兩次。

Kafka 弄丟了消息試想一種情況:假如 leader 副本所在的 broker 突然掛掉,那麼就要從 follower 副本重新選出一個 leader ,但是 leader 的數據還有一些沒有被 follower 副本的同步的話,就會造成消息丟失。

當我們配置了 unclean.leader.election.enable = false 的話,當 leader 副本發生故障時就不會從 follower 副本中和 leader 同步程度達不到要求的副本中選擇出 leader ,這樣降低了消息丟失的可能性。

21、Kafka 怎麼解決重複消費?

* 生產者發送每條數據的時候,裡面加一個全局唯一的 id,消費到了之後,先根據這個 id 去比如 Redis 里查一下,之前消費過嗎,如果沒有消費過,就處理,然後這個 id 寫Redis。如果消費過就別處理了。

* 基於資料庫的唯一鍵來保證重複數據不會重複插入多條。因為有唯一鍵約束了,重複數據插入只會報錯,不會導致資料庫中出現髒數據。

22、介紹下 MySQL 聚簇索引與非聚簇索引的區別(InnoDB 與 Myisam 引擎)?

聚集索引是指資料庫表行中數據的物理順序與鍵值的邏輯(索引)順序相同。一個表只能有一個聚簇索引,因為一個表的物理順序只有一種情況,所以,對應的聚簇索引只能有一個。 聚簇索引的葉子節點就是數據節點,既存儲索引值,又在葉子節點存儲行數據。

Innodb 創建表後生成的文件有:

frm:創建表的語句

idb:表裡面的數據+索引文件

非聚集索引(MyISAM 引擎的底層實現)的邏輯順序與磁碟上行的物理存儲順序不同。非聚簇 索引的葉子節點仍然是索引節點,只不過有指向對應數據塊的指針。索引命中後,需要回表查詢。

Myisam 創建表後生成的文件有:

frm:創建表的語句 MYD:表裡面的數據文件(myisam data)

MYI:表裡面的索引文件(myisam index)

innodb 的次索引指向對主鍵的引用 (聚簇索引)

myisam 的次索引和主索引都指向物理行 (非聚簇索引)

23、然後給一個聯合索引(a,b)和一個語句,select * from table where b = 'xxx',

判斷是否能命中索引?為什麼?

不能命中。

對於查詢 SElECT * FROM TABLE WHERE a=xxx and b=xxx,顯然是可以使用(a,b)這個聯合索引的。

對於單個的 a 列查詢 SELECT * FROM TABLEWHERE a=xxx,也可以使用這個(a,b)

索引。

但對於 b 列的查詢 SELECT * FROM TABLE WHERE b=xxx,則不可以使用這棵 B+樹索引。

在 innoDb 數據引擎中,可以發現葉子節點上的 b 值為 1、2、1、4、1、2,顯然不是排序的,因此對於 b 列的查詢使用不到(a,b)的索引

24、Java 多線程有哪幾種實現方式?

* 通過繼承 Thread 類創建線程類

* 實現 Runnable 接口創建線程類

* 通過 Callable 和 Future 接口創建線程

25、用過 ConcurrentHashMap,講一下他和 HashTable 的不同之處?

* HashTable 就是實現了 HashMap 加上了 synchronized,而 ConcurrentHashMap

底層採用分段的數組+鍊表實現,線程安全

* ConcurrentHashMap 通過把整個 Map 分為 N 個 Segment,可以提供相同的線程安

全,但是效率提升 N 倍,默認提升 16 倍。

* 並且讀操作不加鎖,由於 HashEntry 的 value 變量是 volatile 的,也能保證讀取到最新的值。

* Hashtable 的 synchronized 是針對整張 Hash 表的,即每次鎖住整張表讓線程獨占,ConcurrentHashMap 允許多個修改操作並發進行,其關鍵在於使用了鎖分離技術

* 擴容:段內擴容(段內元素超過該段對應 Entry 數組長度的 75%觸發擴容,不會對整個Map 進行擴容),插入前檢測需不需要擴容,有效避免無效擴容26、Java 怎麼實現線程安全?

* 使用同步代碼塊

* 使用同步方法

* 使用 lock 鎖機制, 通過創建 lock 對象,採用 lock()加鎖,unlock()解鎖,來保護指定的代碼塊

27、描述 Threadlocal(線程本地變量)的底層實現原理及常用場景。

實現原理:

* 每個 Thread 線程內部都有一個 ThreadlocalMap;以線程作為 key,泛型作為 value,可以理解為線程級別的緩存。每一個線程都會獲得一個單獨的 map。

* 提供了 set 和 get 等訪問方法,這些方法為每個使用該變量的線程都存有一份獨立的副本,因此 get 方法總是返回由當前執行線程在調用 set 時設置的最新值。

應用場景:

* JDBC 連接

* Session 管理

* Spring 事務管理

* 調用鏈,參數傳遞

* AOP

Threadlocal 是一個解決線程並發問題的一個類,用於創建線程的本地變量,我們知道一個對象的所有線程會共享它的全局變量,所以這些變量不是線程安全的,我們可以使用同步技術。但是當我們不想使用同步的時候,我們可以選擇 Threadlocal 變量。例如,由於 JDBC 的連接對象不是線程安全的,因此,當多線程應用程式在沒有協同的情況下,使用全局變量時,就不是線程安全的。通過將 JDBC 的連接對象保存到 Threadlocal 中,每 個線程都會擁有屬於自己的連接對象副本。

28、介紹下 Spring Bean 都有哪些作用域 ?

* 單例 singleton : bean 在每個 Spring IOC 容器中只有一個實例。

* 原型 prototype:一個 bean 的定義可以有多個實例。

* request:每次 http 請求都會創建一個 bean。

* session:在一個 HTTP Session 中,一個 bean 定義對應一個實例。

* globalsession

* application

29、註解 @Autowired 和 @Resource 有什麼區別?

* Resource 是 JDK 提供的,而 Autowired 是 Spring 提供的

* Resource 不允許找不到 bean 的情況,而 Autowired 允許(@Autowired(required =false))

* 指定 name 的方式不一樣,@Resource(name ="baseDao"),@Autowired()@Qualifier("baseDao")

Resource 默認通過 name 查找,而 Autowired 默認通過 type 查找

(1)@Autowired 與@Resource 都可以用來裝配 bean,都可以寫在欄位或 setter 方法上

(2)@Autowired 默認按類型裝配,默認情況下必須要求依賴對象存在,如果要允許 null 值,可以設置它的 required 屬性為 false。如果想使用名稱裝配可以結合@Qualifier 註解進行使用。

(3)@Resource,默認按照名稱進行裝配,名稱可以通過 name 屬性進行指定,如果沒有指定 name 屬性,當註解寫在欄位上時,默認取欄位名進行名稱查找。如果註解寫在 setter 方法上默認取屬性名進行裝配。當找不到與名稱匹配的 bean 時才按照類型進行裝配。但是需要注意的是,如果 name 屬性一旦指定,就只會按照名稱進行裝配。

30、RPC 的實現基礎?

* 需要有非常高效的網絡通信,比如一般選擇 Netty 作為網絡通信框架;

* 需要有比較高效的序列化框架,比如谷歌的 Protobuf 序列化框架;

* 可靠的尋址方式(主要是提供服務的發現),比如可以使用 Zookeeper 來註冊服務等等;

* 如果是帶會話(狀態)的 RPC 調用,還需要有會話和狀態保持的功能;

31、CMS,G1 垃圾回收器中的三色標記了解嗎?

三色標記算法思想

三色標記法是一種垃圾回收法,它可以讓 JVM 不發生或僅短時間發生 STW(Stop The World),從而達到清除 JVM 內存垃圾的目的。

三色標記法將對象的顏色分為了黑、灰、白,三種顏色。

黑色:該對象已經被標記過了,且該對象下的屬性也全部都被標記過了。(程序所需要的對象);

灰色:對象已經被垃圾收集器掃描過了,但是對象中還存在沒有掃描的引用(GC 需要從此對象中去尋找垃圾);

白色:表示對象沒有被垃圾收集器訪問過,即表示不可達。

CMS 解決辦法:增量更新

在應對漏標問題時,CMS 使用了增量更新(Increment Update)方法來做,在一個未被標記的對象(白色對象)被重新引用後,引用它的對象若為黑色則要變成灰色,在下次二次標記時讓 GC 線程繼續標記它的屬性對象(但還是存在漏標的問題)。

CMS 另兩個致命缺陷

CMS 採用了 Mark-Sweep 算法,最後會產生許多內存碎片,當到一定數量時,CMS 無法清理這些碎片了,CMS 會讓 Serial Old 垃圾處理器來清理這些垃圾碎片,而 Serial Old 垃圾處理器是單線程操作進行清理垃圾的,效率很低。所以使用 CMS 就會出現一種情況,硬體升級了,卻越來越卡頓,其原因就是因為進行Serial Old GC 時,效率過低。

解決方案:使用 Mark-Sweep-Compact 算法,減少垃圾碎片

調優參數(配套使用):

-XX:+UseCMSCompactAtFullCollection 開啟 CMS 的壓縮

-XX:CMSFullGCsBeforeCompaction 默認為 0,指經過多少次 CMS FullGC 才進行壓縮

當 JVM 認為內存不夠,再使用 CMS 進行並發清理內存可能會發生 OOM 的問題,而不得不進行 Serial Old GC,Serial Old 是單線程垃圾回收,效率低

解決方案:降低觸發 CMS GC 的閾值,讓浮動垃圾不那麼容易占滿老年代

調優參數:

-XX:CMSInitiatingOccupancyFraction 92% 可以降低這個值,讓老年代占用率達到該值就進行 CMS GC

G1 解決辦法:SATB

SATB(Snapshot At The Beginning), 在應對漏標問題時,G1 使用了 SATB 方法來做,具體

流程:

* 在開始標記的時候生成一個快照圖標記存活對象

* 在一個引用斷開後,要將此引用推到 GC 的堆棧里,保證白色對象(垃圾)還能被 GC線程掃描到(在**write barrier(寫屏障)**里把所有舊的引用所指向的對象都變成非白的)

* 配合 Rset,去掃描哪些 Region 引用到當前的白色對象,若沒有引用到當前對象,則回收

G1 會不會進行 Full GC?

會,當內存滿了的時候就會進行 Full GC;且 JDK10 之前的 Full GC,為單線程的,所以使用 G1 需要避免 Full GC 的產生。

解決方案:

* 加大內存;

* 提高 CPU 性能,加快 GC 回收速度,而對象增加速度趕不上回收速度,則 Full GC 可以避免;

* 降低進行 Mixed GC 觸發的閾值,讓 Mixed GC 提早發生(默認 45%)


關鍵字: