1. 程式人生 > 實用技巧 >蒐集的一些面試題

蒐集的一些面試題

Java基礎部分(基礎語法、Java特性)

1、JDK 和 JRE 有什麼區別?
  • JDK:Java Development Kit 的簡稱,java 開發工具包,提供了 java 的開發環境和執行環境
  • JRE:Java Runtime Environment 的簡稱,java 執行環境,為 java 的執行提供了所需環境
2、== 和 equals 的區別是什麼?
  • == :判斷兩個地址是否相等,是否為同一個物件

    • 基本型別:比較的是值是否相同
    • 引用型別:比較的是引用是否相同
  • equals:判斷兩個物件是否相等

    • equals 本質上就是 ==
    • 不能用於比較基本資料型別
    • 存在兩種使用方法:
      • 類沒有被覆蓋,等價於 ==
      • 一般都覆蓋重寫 equals 方法,來讓equals()通過其它方式比較兩個物件是否相等。通常的做法是:若兩個物件的內容相等,則equals()方法返回true;否則,返回fasle。
3、兩個物件的 hashCode()相同,則 equals()也是否一定為 true?

不對,兩個物件的 hashCode()相同,equals()不一定為 true

如果hashset在對比的時候,同樣的hashcode有多個物件,會使用equals()判斷是否真的相同,也就是說hashcode只是用來縮小查詢成本

4、為什麼重寫equals還要重寫hashcode
  • 前提:hashCode() 的作用是獲取雜湊碼,也稱為雜湊碼;它實際上是返回一個 int 整數。這個雜湊碼的作用是確定該物件在雜湊表中的索引位置。hashCode() 定義在 JDK 的 Object.java 中,這就意味著 Java 中的任何類都包含有 hashCode() 函式。
  • 如果兩個物件相等,則 hashcode ⼀定也是相同的
  • 兩個物件相等,對兩個物件分別調⽤ equals ⽅法都返回 true
  • 兩個物件有相同的 hashcode 值,它們也不⼀定是相等的
  • 因此, equals ⽅法被覆蓋過,則 hashCode ⽅法也必須被覆蓋
  • hashCode() 的預設⾏為是對堆上的物件產⽣獨特值。如果沒有重寫 hashCode(),則該 class
    的兩個物件⽆論如何都不會相等(即使這兩個物件指向相同的資料)
5、final 在 java 中有什麼作用

final 關鍵字主要用在三個地方:變數、方法、類

  • final 修飾的類叫最終類,該類不能被繼承
    • final 類中的所有成員方法都會被隱式地指定為 final 方法
  • final 修飾的方法不能被重寫
  • final 修飾的變數叫常量,常量必須初始化,初始化之後值就不能被修改
    • 如果是基本資料型別的變數,則其數值一旦在初始化之後便不能更改
    • 如果是引用型別的變數,則在對其初始化之後便不能再讓其指向另一個物件
6、String 屬於基礎的資料型別嗎

String 不屬於基礎型別

基礎型別有 8 種:byte、boolean、char、short、int、float、long、double,而 String 屬於物件

7、 java 中操作字串都有哪些類?它們之間有什麼區別?(String StringBuffer 和 StringBuilder 的區別)

操作字串的類有:String、StringBuffer、StringBuilder

String 和 StringBuffer、StringBuilder 的區別在於 String 宣告的是不可變的物件,每次操作都會生成新的 String 物件,然後將指標指向新的 String 物件

而 StringBuffer、StringBuilder 可以在原有物件的基礎上進行操作,所以在經常改變字串內容的情況下最好不要使用 String

StringBuffer 和 StringBuilder 最大的區別在於,StringBuffer 是執行緒安全的,而 StringBuilder 是非執行緒安全的,但 StringBuilder 的效能卻高於 StringBuffer,所以在單執行緒環境下推薦使用 StringBuilder,多執行緒環境下推薦使用 StringBuffer

8、普通類和抽象類有哪些區別?
  • 普通類不能包含抽象方法,抽象類可以包含抽象方法
  • 抽象類不能直接例項化,普通類可以直接例項化
9、介面和抽象類有什麼區別?
  • 實現:抽象類的子類使用 extends 來繼承;介面必須使用 implements 來實現介面
  • 建構函式:抽象類可以有建構函式;介面不能有
  • main 方法:抽象類可以有 main 方法,並且我們能執行它;介面不能有 main 方法
  • 實現數量:類可以實現很多個介面;但是隻能繼承一個抽象類
  • 訪問修飾符:介面中的方法預設使用 public 修飾;抽象類中的方法可以是任意訪問修飾符
  • 從設計層面來說,抽象是對類的抽象,是一種模板設計,而介面是對行為的抽象,是一種行為的規範
10、成員變數與區域性變數的區別有哪些?
  • 從語法形式上看:成員變數是屬於類的,而區域性變數是在方法中定義的變數或是方法的引數;成員變數可以被 public,private,static 等修飾符所修飾,而區域性變數不能被訪問控制修飾符及 static 所修飾;但是,成員變數和區域性變數都能被 final 所修飾。

  • 從變數在記憶體中的儲存方式來看:如果成員變數是使用static修飾的,那麼這個成員變數是屬於類的,如果沒有使用static修飾,這個成員變數是屬於例項的。物件存於堆記憶體,如果區域性變數型別為基本資料型別,那麼儲存在棧記憶體,如果為引用資料型別,那存放的是指向堆記憶體物件的引用或者是指向常量池中的地址

  • 從變數在記憶體中的生存時間上看:成員變數是物件的一部分,它隨著物件的建立而存在,而區域性變數隨著方法的呼叫而自動消失

  • 成員變數如果沒有被賦初值,則會自動以型別的預設值而賦值(一種情況例外:被 final 修飾的成員變數也必須顯式地賦值),而區域性變數則不會自動賦值

11、簡述執行緒、程式、程序的基本概念以及相互之間的關係

執行緒與程序相似,但執行緒是一個比程序更小的執行單位。一個程序在其執行的過程中可以產生多個執行緒。與程序不同的是同類的多個執行緒共享同一塊記憶體空間和一組系統資源,所以系統在產生一個執行緒,或是在各個執行緒之間作切換工作時,負擔要比程序小得多,也正因為如此,執行緒也被稱為輕量級程序。

程式是含有指令和資料的檔案,被儲存在磁碟或其他的資料儲存裝置中,也就是說程式是靜態的程式碼。

程序是程式的一次執行過程,是系統執行程式的基本單位,因此程序是動態的。系統執行一個程式即是一個程序從建立,執行到消亡的過程。簡單來說,一個程序就是一個執行中的程式,它在計算機中一個指令接著一個指令地執行著,同時,每個程序還佔有某些系統資源如 CPU 時間,記憶體空間,檔案,輸入輸出裝置的使用權等等。換句話說,當程式在執行時,將會被作業系統載入記憶體中。 執行緒是程序劃分成的更小的執行單位。執行緒和程序最大的不同在於基本上各程序是獨立的,而各執行緒則不一定,因為同一程序中的執行緒極有可能會相互影響。從另一角度來說,程序屬於作業系統的範疇,主要是同一段時間內,可以同時執行一個以上的程式,而執行緒則是在同一程式內幾乎同時執行一個以上的程式段

Java集合

12、java 容器都有哪些?

13、Collection 和 Collections 有什麼區別?
  • java.util.Collection 是一個集合介面(集合類的一個頂級介面)。它提供了對集合物件進行基本操作的通用介面方法。Collection介面在Java 類庫中有很多具體的實現。Collection介面的意義是為各種具體的集合提供了最大化的統一操作方式,其直接繼承介面有List與Set。
  • Collections則是集合類的一個工具類/幫助類,其中提供了一系列靜態方法,用於對集合中元素進行排序、搜尋以及執行緒安全等各種操作。
14、List、Set、Map 之間的區別是什麼?

List(對付順序的好幫手): List介面儲存一組不唯一(可以有多個元素引用相同的物件),有序的物件

Set(注重獨一無二的性質): 不允許重複的集合。不會有多個元素引用相同的物件。

Map(用Key來搜尋的專家): 使用鍵值對儲存。Map會維護與Key有關聯的值。兩個Key可以引用相同的物件,但Key不能重複,典型的Key是String型別,但也可以是任何物件

15、 說一下 HashMap 的實現原理?

HashMap概述: HashMap是基於雜湊表的Map介面的非同步實現。此實現提供所有可選的對映操作,並允許使用null值和null鍵。此類不保證對映的順序,特別是它不保證該順序恆久不變。

HashMap的資料結構: 在java程式語言中,最基本的結構就是兩種,一個是陣列,另外一個是模擬指標(引用),所有的資料結構都可以用這兩個基本結構來構造的,HashMap也不例外。HashMap實際上是一個“連結串列雜湊”的資料結構,即陣列和連結串列的結合體。

當我們往Hashmap中put元素時,首先根據key的hashcode重新計算hash值,根絕hash值得到這個元素在陣列中的位置(下標),如果該陣列在該位置上已經存放了其他元素,那麼在這個位置上的元素將以連結串列的形式存放,新加入的放在鏈頭,最先加入的放入鏈尾.如果陣列中該位置沒有元素,就直接將該元素放到陣列的該位置上。

需要注意Jdk 1.8中對HashMap的實現做了優化,當連結串列中的節點資料超過八個之後,該連結串列會轉為紅黑樹來提高查詢效率,從原來的O(n)到O(logn)

16、說一下 HashSet 的實現原理?
  • HashSet底層由HashMap實現
  • HashSet的值存放於HashMap的key上
  • HashMap的value統一為PRESENT
17、ArrayList 和 LinkedList 的區別是什麼?
  • 是否保證執行緒安全: ArrayListLinkedList 都是不同步的,也就是不保證執行緒安全;
  • 底層資料結構:Arraylist 底層使用的是 Object 陣列LinkedList 底層使用的是 雙向連結串列 資料結構(JDK1.6之前為迴圈連結串列,JDK1.7取消了迴圈。注意雙向連結串列和雙向迴圈連結串列的區別)
  • 插入和刪除是否受元素位置的影響:
    • ArrayList 採用陣列儲存,所以插入和刪除元素的時間複雜度受元素位置的影響。 比如:執行add(E e) 方法的時候, ArrayList 會預設在將指定的元素追加到此列表的末尾,這種情況時間複雜度就是O(1)。但是如果要在指定位置 i 插入和刪除元素的話(add(int index, E element) )時間複雜度就為 O(n-i)。因為在進行上述操作的時候集合中第 i 和第 i 個元素之後的(n-i)個元素都要執行向後位/向前移一位的操作
    • LinkedList 採用連結串列儲存,所以對於add(E e)方法的插入,刪除元素時間複雜度不受元素位置的影響,近似 O(1),如果是要在指定位置i插入和刪除元素的話((add(int index, E element)) 時間複雜度近似為o(n))因為需要先移動到指定位置再插入。
  • 是否支援快速隨機訪問: LinkedList 不支援高效的隨機元素訪問,而 ArrayList 支援。快速隨機訪問就是通過元素的序號快速獲取元素物件(對應於get(int index) 方法)。
  • 記憶體空間佔用: ArrayList的空間浪費主要體現在在list列表的結尾會預留一定的容量空間,而LinkedList的空間花費則體現在它的每一個元素都需要消耗比ArrayList更多的空間(因為要存放直接後繼和直接前驅以及資料)。
18、如何實現陣列和 List 之間的轉換?
  • List轉換成為陣列:呼叫ArrayList的toArray方法。

  • 陣列轉換成為List:呼叫Arrays的asList方法

19、ArrayList 和 Vector 的區別是什麼?
  • Vector類的所有方法都是同步的。可以由兩個執行緒安全地訪問一個Vector物件、但是一個執行緒訪問Vector的話程式碼要在同步操作上耗費大量的時間
  • Arraylist不是同步的,所以在不需要保證執行緒安全時建議使用Arraylist
  • ArrayList更加通用,因為可以使用Collections工具類輕易地獲取同步列表和只讀列表
20、Array 和 ArrayList 有何區別?
  • Array可以容納基本型別和物件,而ArrayList只能容納物件。
  • Array是指定大小的,而ArrayList大小是固定的。
  • Array沒有提供ArrayList那麼多功能,比如addAll、removeAll和iterator等。
21、在 Queue 中 poll()和 remove()有什麼區別?

poll() 和 remove() 都是從佇列中取出一個元素,但是 poll() 在獲取元素失敗的時候會返回空,但是 remove() 失敗的時候會丟擲異常

22、哪些集合類是執行緒安全的?
  • vector:就比arraylist多了個同步化機制(執行緒安全),因為效率較低,現在已經不太建議使用。在web應用中,特別是前臺頁面,往往效率(頁面響應速度)是優先考慮的。
  • statck:堆疊類,先進後出。
  • hashtable:就比hashmap多了個執行緒安全。
  • enumeration:列舉,相當於迭代器。
23、迭代器 Iterator 是什麼?怎麼使用?有什麼特點?
  • 迭代器是一種設計模式,它是一個物件,它可以遍歷並選擇序列中的物件,而開發人員不需要了解該序列的底層結構。迭代器通常被稱為“輕量級”物件,因為建立它的代價小
  • Java中的Iterator功能比較簡單,並且只能單向移動:
    • 使用方法iterator()要求容器返回一個Iterator。第一次呼叫Iterator的next()方法時,它返回序列的第一個元素。注意:iterator()方法是java.lang.Iterable介面,被Collection繼承
    • 使用next()獲得序列中的下一個元素
    • 使用hasNext()檢查序列中是否還有元素
    • 使用remove()將迭代器新返回的元素刪除
    • Iterator是Java迭代器最簡單的實現,為List設計的ListIterator具有更多的功能,它可以從兩個方向遍歷List,也可以從List中插入和刪除元素

Java多執行緒

24、執行緒和程序的區別?

簡而言之,程序是程式執行和資源分配的基本單位,一個程式至少有一個程序,一個程序至少有一個執行緒

程序在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體資源,減少切換次數,從而效率更高

執行緒是程序的一個實體,是cpu排程和分派的基本單位,是比程式更小的能獨立執行的基本單位。同一程序中的多個執行緒之間可以併發執行

25、並行和併發有什麼區別?
  • 並行是指兩個或者多個事件在同一時刻發生;而併發是指兩個或多個事件在同一時間間隔發生。
  • 並行是在不同實體上的多個事件,併發是在同一實體上的多個事件。
  • 在一臺處理器上“同時”處理多個任務,在多臺處理器上同時處理多個任務。如hadoop分散式叢集。

所以併發程式設計的目標是充分的利用處理器的每一個核,以達到最高的處理效能

26、為什麼要使用多執行緒?

從計算機底層來說: 執行緒可以比作是輕量級的程序,是程式執行的最小單位,執行緒間的切換和排程的成本遠遠小於程序。另外,多核 CPU 時代意味著多個執行緒可以同時執行,這減少了執行緒上下文切換的開銷。

從當代網際網路發展趨勢來說: 現在的系統動不動就要求百萬級甚至千萬級的併發量,而多執行緒併發程式設計正是開發高併發系統的基礎,利用好多執行緒機制可以大大提高系統整體的併發能力以及效能

27、使用多執行緒可能帶來什麼問題?

併發程式設計的目的就是為了能提高程式的執行效率提高程式執行速度,但是併發程式設計並不總是能提高程式執行速度的,而且併發程式設計可能會遇到很多問題,比如:記憶體洩漏、上下文切換、死鎖還有受限於硬體和軟體的資源閒置問題

28、說說執行緒的生命週期和狀態?

Java 執行緒在執行的生命週期中的指定時刻只可能處於下面 6 種不同狀態的其中一個狀態

  • 新建狀態(New):當執行緒物件對建立後,即進入了新建狀態,如:Thread t = new MyThread();

  • 就緒狀態(Runnable):當呼叫執行緒物件的start()方法(t.start();),執行緒即進入就緒狀態。處於就緒狀態的執行緒,只是說明此執行緒已經做好了準備,隨時等待CPU排程執行,並不是說執行了t.start()此執行緒立即就會執行;

  • 執行狀態(Running):當CPU開始排程處於就緒狀態的執行緒時,此時執行緒才得以真正執行,即進入到執行狀態。注:就 緒狀態是進入到執行狀態的唯一入口,也就是說,執行緒要想進入執行狀態執行,首先必須處於就緒狀態中;

  • 阻塞狀態(Blocked):處於執行狀態中的執行緒由於某種原因,暫時放棄對CPU的使用權,停止執行,此時進入阻塞狀態,直到其進入到就緒狀態,才 有機會再次被CPU呼叫以進入到執行狀態。根據阻塞產生的原因不同,阻塞狀態又可以分為三種:

    • 等待阻塞:執行狀態中的執行緒執行wait()方法,使本執行緒進入到等待阻塞狀態;
    • 同步阻塞 -- 執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態;
    • 其他阻塞 -- 通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。
  • 死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期

執行緒建立之後它將處於 NEW(新建) 狀態,呼叫 start() 方法後開始執行,執行緒這時候處於 READY(可執行) 狀態。可執行狀態的執行緒獲得了 CPU 時間片(timeslice)後就處於 RUNNING(執行) 狀態

當執行緒執行 wait()方法之後,執行緒進入 WAITING(等待) 狀態。進入等待狀態的執行緒需要依靠其他執行緒的通知才能夠返回到執行狀態,而 TIME_WAITING(超時等待) 狀態相當於在等待狀態的基礎上增加了超時限制,比如通過 sleep(long millis)方法或 wait(long millis)方法可以將 Java 執行緒置於 TIMED WAITING 狀態。當超時時間到達後 Java 執行緒將會返回到 RUNNABLE 狀態。當執行緒呼叫同步方法時,在沒有獲取到鎖的情況下,執行緒將會進入到 BLOCKED(阻塞) 狀態。執行緒在執行 Runnable 的run()方法之後將會進入到 TERMINATED(終止) 狀態

29、在 java 程式中怎麼保證多執行緒的執行安全?

執行緒安全在三個方面體現:

  • 原子性:提供互斥訪問,同一時刻只能有一個執行緒對資料進行操作,(atomic,synchronized);
  • 可見性:一個執行緒對主記憶體的修改可以及時地被其他執行緒看到,(synchronized,volatile);
  • 有序性:一個執行緒觀察其他執行緒中的指令執行順序,由於指令重排序,該觀察結果一般雜亂無序,(happens-before原則)
30、什麼是上下文切換

多執行緒程式設計中一般執行緒的個數都大於 CPU 核心的個數,而一個 CPU 核心在任意時刻只能被一個執行緒使用,為了讓這些執行緒都能得到有效執行,CPU 採取的策略是為每個執行緒分配時間片並輪轉的形式。當一個執行緒的時間片用完的時候就會重新處於就緒狀態讓給其他執行緒使用,這個過程就屬於一次上下文切換。

概括來說就是:當前任務在執行完 CPU 時間片切換到另一個任務之前會先儲存自己的狀態,以便下次再切換回這個任務時,可以再載入這個任務的狀態。任務從儲存到再載入的過程就是一次上下文切換

上下文切換通常是計算密集型的。也就是說,它需要相當可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時間。所以,上下文切換對系統來說意味著消耗大量的 CPU 時間,事實上,可能是作業系統中時間消耗最大的操作。

Linux 相比與其他作業系統(包括其他類 Unix 系統)有很多的優點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少

31、什麼是執行緒死鎖?

執行緒死鎖描述的是這樣一種情況:多個執行緒同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放。由於執行緒被無限期地阻塞,因此程式不可能正常終止,是作業系統層面的一個錯誤

32、怎麼避免執行緒死鎖?

產生死鎖必須具備以下四個條件:

  • 互斥條件:該資源任意一個時刻只由一個執行緒佔用
  • 請求和保持條件:一個程序因請求資源而阻塞時,對已獲得的資源保持不放
  • 不可剝奪條件:執行緒已獲得的資源在末使用完之前不能被其他執行緒強行剝奪,只有自己使用完畢後才釋放資源
  • 迴圈等待條件:若干程序之間形成一種頭尾相接的迴圈等待資源關係

為了避免死鎖,只要破壞產生死鎖的四個條件中的其中一個就可以了。現在我們來挨個分析一下:

  1. 破壞互斥條件 :這個條件我們沒有辦法破壞,因為我們用鎖本來就是想讓他們互斥的(臨界資源需要互斥訪問)。
  2. 破壞請求與保持條件 :一次性申請所有的資源。
  3. 破壞不剝奪條件 :佔用部分資源的執行緒進一步申請其他資源時,如果申請不到,可以主動釋放它佔有的資源。
  4. 破壞迴圈等待條件 :靠按序申請資源來預防。按某一順序申請資源,釋放資源則反序釋放。破壞迴圈等待條件

所以,在系統設計、程序排程等方面注意如何不讓這四個必要條件成立,如何確 定資源的合理分配演算法,避免程序永久佔據系統資源

此外,也要防止程序在處於等待狀態的情況下佔用資源。因此,對資源的分配要給予合理的規劃

33、說說sleep()方法和wait()方法的區別和共同點
  • 兩者最主要的區別在於:sleep 方法沒有釋放鎖,而 wait 方法釋放了鎖
  • 兩者都可以暫停執行緒的執行。
  • Wait 通常被用於執行緒間互動/通訊,sleep 通常被用於暫停執行。
  • wait() 方法被呼叫後,執行緒不會自動甦醒,需要別的執行緒呼叫同一個物件上的 notify() 或者 notifyAll() 方法。sleep() 方法執行完成後,執行緒會自動甦醒。或者可以使用 wait(long timeout)超時後執行緒會自動甦醒
34、 為什麼呼叫 start() 方法時會執行 run() 方法,為什麼不能直接呼叫 run() 方法?

這是另一個非常經典的 java 多執行緒面試問題,而且在面試中會經常被問到。很簡單,但是很多人都會答不上來!

new 一個 Thread,執行緒進入了新建狀態;呼叫 start() 方法,會啟動一個執行緒並使執行緒進入了就緒狀態,當分配到時間片後就可以開始運行了。 start() 會執行執行緒的相應準備工作,然後自動執行 run() 方法的內容,這是真正的多執行緒工作。 而直接執行 run() 方法,會把 run 方法當成一個 main 執行緒下的普通方法去執行,並不會在某個執行緒中執行它,所以這並不是多執行緒工作。

總結: 呼叫 start 方法方可啟動執行緒並使執行緒進入就緒狀態,而 run 方法只是 thread 的一個普通方法呼叫,還是在主執行緒裡執行。

35、ThreadLocal 是什麼?有哪些使用場景?

執行緒區域性變數是侷限於執行緒內部的變數,屬於執行緒自身所有,不在多個執行緒間共享。Java提供ThreadLocal類來支援執行緒區域性變數,是一種實現執行緒安全的方式。但是在管理環境下(如 web 伺服器)使用執行緒區域性變數的時候要特別小心,在這種情況下,工作執行緒的生命週期比任何應用變數的生命週期都要長。任何執行緒區域性變數一旦在工作完成後沒有釋放,Java 應用就存在記憶體洩露的風險。

36、說一下 synchronized 底層實現原理?

synchronized可以保證方法或者程式碼塊在執行時,同一時刻只有一個方法可以進入到臨界區,同時它還可以保證共享變數的記憶體可見性

Java中每一個物件都可以作為鎖,這是synchronized實現同步的基礎:

  • 普通同步方法,鎖是當前例項物件
  • 靜態同步方法,鎖是當前類的class物件
  • 同步方法塊,鎖是括號裡面的物件
37、volatile關鍵字

講一下Java記憶體模型

38、執行緒池
為什麼要用執行緒池?

池化技術的思想主要是為了減少每次獲取資源的消耗,提高對資源的利用率

執行緒池提供了一種限制和管理資源(包括執行一個任務)。 每個執行緒池還維護一些基本統計資訊,例如已完成任務的數量。

使用執行緒池的好處

  • 降低資源消耗。通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗
  • 提高響應速度。當任務到達時,任務可以不需要的等到執行緒建立就能立即執行
  • 提高執行緒的可管理性。執行緒是稀缺資源,如果無限制的建立,不僅會消耗系統資源,還會降低系統的穩定性,使用執行緒池可以進行統一的分配,調優和監控
39、Atomic 原子類

Java反射

40、什麼是反射?
  • 反射主要是指程式可以訪問、檢測和修改它本身狀態或行為的一種能力
  • Java反射:
    • 在Java執行時環境中,對於任意一個類,能否知道這個類有哪些屬性和方法?對於任意一個物件,能否呼叫它的任意一個方法
  • Java反射機制主要提供了以下功能:
    • 在執行時判斷任意一個物件所屬的類
    • 在執行時構造任意一個類的物件
    • 在執行時判斷任意一個類所具有的成員變數和方法
    • 在執行時呼叫任意一個物件的方法
41、 什麼是 java 序列化?什麼情況下需要序列化?

簡單說就是為了儲存在記憶體中的各種物件的狀態(也就是例項變數,不是方法),並且可以把儲存的物件狀態再讀出來。雖然你可以用你自己的各種各樣的方法來儲存object states,但是Java給你提供一種應該比你自己好的儲存物件狀態的機制,那就是序列化。

什麼情況下需要序列化:

  • 當你想把的記憶體中的物件狀態儲存到一個檔案中或者資料庫中時候
  • 當你想用套接字在網路上傳送物件的時候
  • 當你想通過RMI傳輸物件的時候;
42、動態代理是什麼?有哪些應用?

動態代理:

當想要給實現了某個介面的類中的方法,加一些額外的處理。比如說加日誌,加事務等。可以給這個類建立一個代理,故名思議就是建立一個新的類,這個類不僅包含原來類方法的功能,而且還在原來的基礎上添加了額外處理的新類。這個代理類並不是定義好的,是動態生成的。具有解耦意義,靈活,擴充套件性強。

動態代理的應用:

  • Spring的AOP
  • 加事務
  • 加許可權
  • 加日誌
43、怎麼實現動態代理?

首先必須定義一個介面,還要有一個InvocationHandler(將實現介面的類的物件傳遞給它)處理類

再有一個工具類Proxy(習慣性將其稱為代理類,因為呼叫他的newInstance()可以產生代理物件,其實他只是一個產生代理物件的工具類)

利用到InvocationHandler,拼接代理類原始碼,將其編譯生成代理類的二進位制碼,利用載入器載入,並將其例項化產生代理物件,最後返回

物件拷貝

44、為什麼要使用克隆?

想對一個物件進行處理,又想保留原有的資料進行接下來的操作,就需要克隆了,Java語言中克隆針對的是類的例項。

45、如何實現物件克隆?

有兩種方式:

  • 實現Cloneable介面並重寫Object類中的clone()方法
  • 實現Serializable介面,通過物件的序列化和反序列化實現克隆,可以實現真正的深度克隆
46、深拷貝和淺拷貝區別是什麼?
  • 淺拷貝只是複製了物件的引用地址,兩個物件指向同一個記憶體地址,所以修改其中任意的值,另一個值都會隨之變化,這就是淺拷貝(例:assign())
  • 深拷貝是將物件及值複製過來,兩個物件修改其中任意的值另一個值不會改變,這就是深拷貝(例:JSON.parse()和JSON.stringify(),但是此方法無法複製函式型別)

JVM

47、說一下 jvm 的主要組成部分及其作用?
  • 類載入器(ClassLoader)
  • 執行時資料區(Runtime Data Area)
  • 執行引擎(Execution Engine)
  • 本地庫介面(Native Interface)

元件的作用: 首先通過類載入器(ClassLoader)會把 Java 程式碼轉換成位元組碼,執行時資料區(Runtime Data Area)再把位元組碼載入到記憶體中,而位元組碼檔案只是 JVM 的一套指令集規範,並不能直接交個底層作業系統去執行,因此需要特定的命令解析器執行引擎(Execution Engine),將位元組碼翻譯成底層系統指令,再交由 CPU 去執行,而這個過程中需要呼叫其他語言的本地庫介面(Native Interface)來實現整個程式的功能

48、介紹下 Java 記憶體區域(執行時資料區)
  • 程式計數器
  • 虛擬機器棧
  • 本地方法棧
  • 方法區

執行緒私有的:

  • 程式計數器
  • 虛擬機器棧
  • 本地方法棧

執行緒共享的:

  • 方法區
  • 直接記憶體(非執行時資料區的一部分)

程式計數器

程式計數器是一塊較小的記憶體空間,可以看作是當前執行緒所執行的位元組碼的行號指示器。位元組碼直譯器工作時通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、跳轉、異常處理、執行緒恢復等功能都需要依賴這個計數器來完。

另外,為了執行緒切換後能恢復到正確的執行位置,每條執行緒都需要有一個獨立的程式計數器,各執行緒之間計數器互不影響,獨立儲存,我們稱這類記憶體區域為“執行緒私有”的記憶體。

從上面的介紹中我們知道程式計數器主要有兩個作用:

  1. 位元組碼直譯器通過改變程式計數器來依次讀取指令,從而實現程式碼的流程控制,如:順序執行、選擇、迴圈、異常處理。
  2. 在多執行緒的情況下,程式計數器用於記錄當前執行緒執行的位置,從而當執行緒被切換回來的時候能夠知道該執行緒上次執行到哪兒了。

注意:程式計數器是唯一一個不會出現 OutOfMemoryError 的記憶體區域,它的生命週期隨著執行緒的建立而建立,隨著執行緒的結束而死亡。

Java 虛擬機器棧

與程式計數器一樣,Java虛擬機器棧也是執行緒私有的,它的生命週期和執行緒相同,描述的是 Java 方法執行的記憶體模型,每次方法呼叫的資料都是通過棧傳遞的。

Java 記憶體可以粗糙的區分為堆記憶體(Heap)和棧記憶體(Stack),其中棧就是現在說的虛擬機器棧,或者說是虛擬機器棧中區域性變量表部分。 (實際上,Java虛擬機器棧是由一個個棧幀組成,而每個棧幀中都擁有:區域性變量表、運算元棧、動態連結、方法出口資訊。)

區域性變量表主要存放了編譯器可知的各種資料型別(boolean、byte、char、short、int、float、long、double)、物件引用(reference型別,它不同於物件本身,可能是一個指向物件起始地址的引用指標,也可能是指向一個代表物件的控制代碼或其他與此物件相關的位置)。

Java 虛擬機器棧會出現兩種異常:StackOverFlowError 和 OutOfMemoryError。

  • StackOverFlowError: 若Java虛擬機器棧的記憶體大小不允許動態擴充套件,那麼當執行緒請求棧的深度超過當前Java虛擬機器棧的最大深度的時候,就丟擲StackOverFlowError異常。
  • OutOfMemoryError: 若 Java 虛擬機器棧的記憶體大小允許動態擴充套件,且當執行緒請求棧時記憶體用完了,無法再動態擴充套件了,此時丟擲OutOfMemoryError異常。

Java 虛擬機器棧也是執行緒私有的,每個執行緒都有各自的Java虛擬機器棧,而且隨著執行緒的建立而建立,隨著執行緒的死亡而死亡。

擴充套件:那麼方法/函式如何呼叫?

Java 棧可用類比資料結構中棧,Java 棧中儲存的主要內容是棧幀,每一次函式呼叫都會有一個對應的棧幀被壓入Java棧,每一個函式呼叫結束後,都會有一個棧幀被彈出。

Java方法有兩種返回方式:

  1. return 語句。
  2. 丟擲異常。

不管哪種返回方式都會導致棧幀被彈出。

本地方法棧

和虛擬機器棧所發揮的作用非常相似,區別是: 虛擬機器棧為虛擬機器執行 Java 方法 (也就是位元組碼)服務,而本地方法棧則為虛擬機器使用到的 Native 方法服務。 在 HotSpot 虛擬機器中和 Java 虛擬機器棧合二為一。

本地方法被執行的時候,在本地方法棧也會建立一個棧幀,用於存放該本地方法的區域性變量表、運算元棧、動態連結、出口資訊。

方法執行完畢後相應的棧幀也會出棧並釋放記憶體空間,也會出現 StackOverFlowError 和 OutOfMemoryError 兩種異常。

Java 虛擬機器所管理的記憶體中最大的一塊,Java 堆是所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時建立。此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件例項以及陣列都在這裡分配記憶體。

Java 堆是垃圾收集器管理的主要區域,因此也被稱作GC堆(Garbage Collected Heap).從垃圾回收的角度,由於現在收集器基本都採用分代垃圾收集演算法,所以Java堆還可以細分為:新生代和老年代:再細緻一點有:Eden空間、From Survivor、To Survivor空間等。進一步劃分的目的是更好地回收記憶體,或者更快地分配記憶體。

上圖所示的 eden區、s0區、s1區都屬於新生代,tentired 區屬於老年代。大部分情況,物件都會首先在 Eden 區域分配,在一次新生代垃圾回收後,如果物件還存活,則會進入 s0 或者 s1,並且物件的年齡還會加 1(Eden區->Survivor 區後物件的初始年齡變為1),當它的年齡增加到一定程度(預設為15歲),就會被晉升到老年代中。物件晉升到老年代的年齡閾值,可以通過引數 -XX:MaxTenuringThreshold 來設定。

方法區

方法區與 Java 堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。雖然Java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做 Non-Heap(非堆),目的應該是與 Java 堆區分開來。

49、說一下堆疊的區別?

  • 棧記憶體儲存的是區域性變數而堆記憶體儲存的是實體;
  • 棧記憶體的更新速度要快於堆記憶體,因為區域性變數的生命週期很短;
  • 棧記憶體存放的變數生命週期一旦結束就會被釋放,而堆記憶體存放的實體會被垃圾回收機制不定時的回收
50、說一下Java物件的建立過程

下圖便是 Java 物件的建立過程,最好是能默寫出來,並且要掌握每一步在做什麼

①類載入檢查: 虛擬機器遇到一條 new 指令時,首先將去檢查這個指令的引數是否能在常量池中定位到這個類的符號引用,並且檢查這個符號引用代表的類是否已被載入過、解析和初始化過。如果沒有,那必須先執行相應的類載入過程。

②分配記憶體:類載入檢查通過後,接下來虛擬機器將為新生物件分配記憶體。物件所需的記憶體大小在類載入完成後便可確定,為物件分配空間的任務等同於把一塊確定大小的記憶體從 Java 堆中劃分出來。分配方式“指標碰撞”“空閒列表” 兩種,選擇那種分配方式由 Java 堆是否規整決定,而Java堆是否規整又由所採用的垃圾收集器是否帶有壓縮整理功能決定

記憶體分配的兩種方式:(補充內容,需要掌握)

選擇以上兩種方式中的哪一種,取決於 Java 堆記憶體是否規整。而 Java 堆記憶體是否規整,取決於 GC 收集器的演算法是"標記-清除",還是"標記-整理"(也稱作"標記-壓縮"),值得注意的是,複製演算法記憶體也是規整的

記憶體分配併發問題(補充內容,需要掌握)

在建立物件的時候有一個很重要的問題,就是執行緒安全,因為在實際開發過程中,建立物件是很頻繁的事情,作為虛擬機器來說,必須要保證執行緒是安全的,通常來講,虛擬機器採用兩種方式來保證執行緒安全:

  • CAS+失敗重試: CAS 是樂觀鎖的一種實現方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。虛擬機器採用 CAS 配上失敗重試的方式保證更新操作的原子性。
  • TLAB: 為每一個執行緒預先在Eden區分配一塊兒記憶體,JVM在給執行緒中的物件分配記憶體時,首先在TLAB分配,當物件大於TLAB中的剩餘記憶體或TLAB的記憶體已用盡時,再採用上述的CAS進行記憶體分配

③初始化零值: 記憶體分配完成後,虛擬機器需要將分配到的記憶體空間都初始化為零值(不包括物件頭),這一步操作保證了物件的例項欄位在 Java 程式碼中可以不賦初始值就直接使用,程式能訪問到這些欄位的資料型別所對應的零值。

④設定物件頭: 初始化零值完成之後,虛擬機器要對物件進行必要的設定,例如這個物件是那個類的例項、如何才能找到類的元資料資訊、物件的雜湊嗎、物件的 GC 分代年齡等資訊。 這些資訊存放在物件頭中。 另外,根據虛擬機器當前執行狀態的不同,如是否啟用偏向鎖等,物件頭會有不同的設定方式。

⑤執行 init 方法: 在上面工作都完成之後,從虛擬機器的視角來看,一個新的物件已經產生了,但從 Java 程式的視角來看,物件建立才剛開始,方法還沒有執行,所有的欄位都還為零。所以一般來說,執行 new 指令之後會接著執行 方法,把物件按照程式設計師的意願進行初始化,這樣一個真正可用的物件才算完全產生出來。

51、物件的訪問定位有哪兩種方式?【空】
52、說一下堆記憶體中物件的分配的基本策略【空】
53、常見的垃圾回收器有哪些?
  • Serial:最早的單執行緒序列垃圾回收器
  • Serial Old:Serial 垃圾回收器的老年版本,同樣也是單執行緒的,可以作為 CMS 垃圾回收器的備選預案
  • ParNew:是 Serial 的多執行緒版本
  • Parallel 和 ParNew 收集器類似是多執行緒的,但 Parallel 是吞吐量優先的收集器,可以犧牲等待時間換取系統的吞吐量
  • CMS:一種以獲得最短停頓時間為目標的收集器,非常適用 B/S 系統
  • G1:一種兼顧吞吐量和停頓時間的 GC 實現,是 JDK 9 以後的預設 GC 選項
54、新生代垃圾回收器和老生代垃圾回收器都有哪些?有什麼區別?
  • 新生代回收器:Serial、ParNew、Parallel Scavenge
  • 老年代回收器:Serial Old、Parallel Old、CMS
  • 整堆回收器:G1

新生代垃圾回收器一般採用的是複製演算法,複製演算法的優點是效率高,缺點是記憶體利用率低;老年代回收器一般採用的是標記-整理的演算法進行垃圾回收

55、簡述分代垃圾回收器是怎麼工作的?

分代回收器有兩個分割槽:老生代和新生代,新生代預設的空間佔比總空間的 1/3,老生代的預設佔比是 2/3。

新生代使用的是複製演算法,新生代裡有 3 個分割槽:Eden、To Survivor、From Survivor,它們的預設佔比是 8:1:1,它的執行流程如下:

  • 把 Eden + From Survivor 存活的物件放入 To Survivor 區;
  • 清空 Eden 和 From Survivor 分割槽;
  • From Survivor 和 To Survivor 分割槽交換,From Survivor 變 To Survivor,To Survivor 變 From Survivor。

每次在 From Survivor 到 To Survivor 移動時都存活的物件,年齡就 +1,當年齡到達 15(預設配置是 15)時,升級為老生代。大物件也會直接進入老生代。

老生代當空間佔用到達某個值之後就會觸發全域性垃圾收回,一般使用標記整理的執行演算法。以上這些迴圈往復就構成了整個分代垃圾回收的整體執行流程。

56、類載入過程
知道類載入的過程嗎?

類載入過程:載入->連線->初始化

連線過程又可分為三步:驗證->準備->解析

載入這一步做了什麼?
知道哪些類載入器?
雙親委派模型知道嗎?能介紹一下嗎?

計算機基礎

計算機網路

資料庫

MySQL

1、什麼是事務?

事務是邏輯上的一組操作,要麼都執行,要麼都不執行。

事務最經典也經常被拿出來說例子就是轉賬了。假如小明要給小紅轉賬1000元,這個轉賬會涉及到兩個關鍵操作就是:將小明的餘額減少1000元,將小紅的餘額增加1000元。萬一在這兩個操作之間突然出現錯誤比如銀行系統崩潰,導致小明餘額減少而小紅的餘額沒有增加,這樣就不對了。事務就是保證這兩個關鍵操作要麼都成功,要麼都要失敗。

2、事物的四大特性(ACID)
  1. 原子性(Atomicity): 事務是最小的執行單位,不允許分割。事務的原子性確保動作要麼全部完成,要麼完全不起作用;
  2. 一致性(Consistency): 執行事務前後,資料保持一致,多個事務對同一個資料讀取的結果是相同的;
  3. 隔離性(Isolation): 併發訪問資料庫時,一個使用者的事務不被其他事務所幹擾,各併發事務之間資料庫是獨立的;
  4. 永續性(Durability): 一個事務被提交之後。它對資料庫中資料的改變是持久的,即使資料庫發生故障也不應該對其有任何影響
3、併發事務帶來哪些問題?

在典型的應用程式中,多個事務併發執行,經常會操作相同的資料來完成各自的任務(多個使用者對同一資料進行操作)。併發雖然是必須的,但可能會導致以下的問題。

  • 髒讀(Dirty read): 當一個事務正在訪問資料並且對資料進行了修改,而這種修改還沒有提交到資料庫中,這時另外一個事務也訪問了這個資料,然後使用了這個資料。因為這個資料是還沒有提交的資料,那麼另外一個事務讀到的這個資料是“髒資料”,依據“髒資料”所做的操作可能是不正確的。
  • 丟失修改(Lost to modify): 指在一個事務讀取一個數據時,另外一個事務也訪問了該資料,那麼在第一個事務中修改了這個資料後,第二個事務也修改了這個資料。這樣第一個事務內的修改結果就被丟失,因此稱為丟失修改。 例如:事務1讀取某表中的資料A=20,事務2也讀取A=20,事務1修改A=A-1,事務2也修改A=A-1,最終結果A=19,事務1的修改被丟失。
  • 不可重複讀(Unrepeatableread): 指在一個事務內多次讀同一資料。在這個事務還沒有結束時,另一個事務也訪問該資料。那麼,在第一個事務中的兩次讀資料之間,由於第二個事務的修改導致第一個事務兩次讀取的資料可能不太一樣。這就發生了在一個事務內兩次讀到的資料是不一樣的情況,因此稱為不可重複讀。
  • 幻讀(Phantom read): 幻讀與不可重複讀類似。它發生在一個事務(T1)讀取了幾行資料,接著另一個併發事務(T2)插入了一些資料時。在隨後的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱為幻讀。

不可重複讀和幻讀區別:

不可重複讀的重點是修改比如多次讀取一條記錄發現其中某些列的值被修改,幻讀的重點在於新增或者刪除比如多次讀取一條記錄發現記錄增多或減少了

4、事務隔離級別有哪些?MySQL的預設隔離級別是?

SQL 標準定義了四個隔離級別:

  • READ-UNCOMMITTED(讀取未提交): 最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致髒讀、幻讀或不可重複讀
  • READ-COMMITTED(讀取已提交): 允許讀取併發事務已經提交的資料,可以阻止髒讀,但是幻讀或不可重複讀仍有可能發生
  • REPEATABLE-READ(可重複讀): 對同一欄位的多次讀取結果都是一致的,除非資料是被本身事務自己所修改,可以阻止髒讀和不可重複讀,但幻讀仍有可能發生
  • SERIALIZABLE(可序列化): 最高的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執行,這樣事務之間就完全不可能產生干擾,也就是說,該級別可以防止髒讀、不可重複讀以及幻讀