面試題匯總


sidebarDepth: 0

常見題

[[toc]]

基本題

java集合

1、List,Set區別

List
1.可以允許重復的對象。
2.可以插入多個null元素。
3.是一個有序容器,保持了每個元素的插入順序,輸出的順序就是插入的順序。
4.常用的實現類有 ArrayList、LinkedList 和 Vector。ArrayList 最為流行,它提供了使用索引的隨意訪問,而 LinkedList 則對于經常需要從 List 中添加或刪除元素的場合更為合適。

Set
1.不允許重復對象
2.無序容器,你無法保證每個元素的存儲順序,TreeSet通過 Comparator 或者 Comparable 維護了一個排序順序。
3.只允許一個 null 元素
4.Set 接口最流行的幾個實現類是 HashSet、LinkedHashSet 以及 TreeSet。最流行的是基于 HashMap 實現的 HashSet;TreeSet 還實現了 SortedSet 接口,因此 TreeSet 是一個根據其 compare() 和 compareTo() 的定義進行排序的有序容器。

avatar

2、HashSet 是如何保證不重復的

hash值計算和equals方式實現集合無重復元素:
1,如果hash碼值不相同,說明是一個新元素,存.如果沒有元素和傳入對象(也就是add的元素)的hash值相等,那么就認為這個元素在table中不存在,將其添加進table
2,如果hash碼值相同,且equles判斷相等,說明元素已經存在,不存.
3,如果hash碼值相同,且equles判斷不相等,說明元素不存在,存.
4,如果有元素和傳入對象的hash值相等,那么,繼續進行equles()判斷,如果仍然相等,那么就認為傳入元素已經存在,不再添加,結束,否則仍然添加.

hash計算: 通過對象中的成員來計算出來的結果;如果成員變量是基本數據類型的值, 那么用這個值 直接參與計算;如果成員變量是引用數據類型的值,那么獲取到這個成員變量的哈希碼值后,再參數計算

3、HashMap 是線程安全的嗎,為什么不是線程安全的(最好畫圖說明多線程環境下不安全)?
avatar

avatar

avatar

兩個可能出現線程不安全的地方:
1、put的時候導致的多線程數據不一致。
這個問題比較好想象,比如有兩個線程A和B,首先A希望插入一個key-value對到HashMap中,首先計算記錄所要落到的桶的索引坐標,然后獲取到該桶里面的鏈表頭結點,此時線程A的時間片用完了,而此時線程B被調度得以執行,和線程A一樣執行,只不過線程B成功將記錄插到了桶里面,假設線程A插入的記錄計算出來的桶索引和線程B要插入的記錄計算出來的桶索引是一樣的,那么當線程B成功插入之后,線程A再次被調度運行時,它依然持有過期的鏈表頭但是它對此一無所知,以至于它認為它應該這樣做,如此一來就覆蓋了線程B插入的記錄,這樣線程B插入的記錄就憑空消失了,造成了數據不一致的行為。
2、HashMap的get操作可能因為resize而引起死循環(cpu100%). 方法的功能是將原來的記錄重新計算在新桶的位置,然后遷移過去。
resize方法核心:

void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {

            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
avatar

我們假設有兩個線程同時需要執行resize操作,我們原來的桶數量為2,記錄數為3,需要resize桶到4,原來的記錄分別為:[3,A],[7,B],[5,C],在原來的map里面,我們發現這三個entry都落到了第二個桶里面。
假設線程thread1執行到了transfer方法的Entry next = e.next這一句,然后時間片用完了,此時的e = [3,A], next = [7,B]。線程thread2被調度執行并且順利完成了resize操作,需要注意的是,此時的[7,B]的next為[3,A]。此時線程thread1重新被調度運行,此時的thread1持有的引用是已經被thread2 resize之后的結果。線程thread1首先將[3,A]遷移到新的數組上,然后再處理[7,B],而[7,B]被鏈接到了[3,A]的后面,處理完[7,B]之后,就需要處理[7,B]的next了啊,而通過thread2的resize之后,[7,B]的next變為了[3,A],此時,[3,A]和[7,B]形成了環形鏈表,在get的時候,如果get的key的桶索引和[3,A]和[7,B]一樣,那么就會陷入死循環。
如果在取鏈表的時候從頭開始取(現在是從尾部開始取)的話,則可以保證節點之間的順序,那樣就不存在這樣的問題了。
綜合上面兩點,可以說明HashMap是線程不安全的。

4、HashMap 的擴容過程

擴容時機: 當向容器添加元素的時候,會判斷當前容器的元素個數,如果大于等于閾值-就要自動擴容.當map中包含的Entry的數量大于等于threshold = loadFactor * capacity的時候,且新建的Entry剛好落在一個非空的桶上,此刻觸發擴容機制,將其容量擴大為2倍。當size大于等于threshold的時候,并不一定會觸發擴容機制,但是會很可能就觸發擴容機制,只要有一個新建的Entry出現哈希沖突,則立刻resize
擴容(resize) 就是調用resize()方法,重新計算容量,將容量擴大為原來的2倍。

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {//最大容量為 1 << 30
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];//新建一個新表
        boolean oldAltHashing = useAltHashing;
        useAltHashing |= sun.misc.VM.isBooted() &&
                (newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
        boolean rehash = oldAltHashing ^ useAltHashing;//是否再hash
        transfer(newTable, rehash);//完成舊表到新表的轉移
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

    /**
     * Transfers all entries from current table to newTable.
     */
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {//遍歷同桶數組中的每一個桶
            while(null != e) {//順序遍歷某個桶的外掛鏈表
                Entry<K,V> next = e.next;//引用next
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);//找到新表的桶位置;原桶數組中的某個桶上的同一鏈表中的Entry此刻可能被分散到不同的桶中去了,有效的緩解了哈希沖突。
                e.next = newTable[i];//頭插法插入新表中
                newTable[i] = e;
                e = next;
            }
        }
    }

1.在對HashMap進行擴容的時候,HashMap的容量會變為原來的兩倍.通過以上我們知道HashMap的容量必須是2的冪,那么為什么要這么設計呢?答案當然是為了性能。在HashMap通過鍵的哈希值進行定位桶位置的時候,調用了一個indexFor(hash, table.length);方法。

/**
 * Returns index for hash code h.
 */
static int indexFor(int h, int length) {
    return h & (length-1);
}

可以看到這里是將哈希值h與桶數組的length-1(實際上也是map的容量-1)進行了一個與操作得出了對應的桶的位置,h & (length-1)。 通過限制length是一個2的冪數,h & (length-1)和h % length結果是一致的。這就是為什么要限制容量必須是一個2的冪的原因。
但是為什么不采用h % length這種計算方式呢? Java的%、/操作比&慢10倍左右,因此采用&運算會提高性能

2.擴容是一個特別耗性能的操作,所以當程序員在使用HashMap的時候,估算map的大小,初始化的時候給一個大致的數值,避免map進行頻繁的擴容.
3.負載因子是可以修改的,默認0.75, 也可以大于1,但是建議不要輕易修改,除非情況非常特殊.

5、HashMap 1.7 與 1.8 的 區別,說明 1.8 做了哪些優化,如何優化的?

1.hash方法不同了
2.jdk1.8刪除了indexFor方法
3.結點類名字由entry改成了node
4.put時,jdk1.8引入的紅黑樹,那就是存儲結構由以前單純的數組+鏈表,變成了數組+鏈表/紅黑樹
5.resize更加高效
6.初始化方式不同
詳解: https://juejin.im/post/5aa5d8d26fb9a028d2079264

6、final finally finalize

1.final(關鍵字). 用于申明屬性,方法和類,表示屬性不可變,方法不可以被覆蓋,類不可以被繼承。
2.finally(用于異常處理). 是異常處理語句結構中,表示總是執行的部分.
3.finallize(用于垃圾回收). 表示是object類一個方法,在垃圾回收機制中執行的時候會被調用被回收對象的方法。允許回收此前未回收的內存垃圾。所有object都繼承了 finalize()方法

7、強引用 、軟引用、 弱引用、虛引用
avatar

Java中4種引用的級別和強度由高到低依次為:強引用 -> 軟引用 -> 弱引用 -> 虛引用
當垃圾回收器回收時,某些對象會被回收,某些不會被回收。垃圾回收器會從根對象Object來標記存活的對象,然后將某些不可達的對象和一些引用的對象進行回收。

  1. 強引用(StrongReference)
    強引用是使用最普遍的引用。如果一個對象具有強引用,那垃圾回收器絕不會回收它。如下:
    Object strongReference = new Object();
    復制代碼當內存空間不足時,Java虛擬機寧愿拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具有強引用的對象來解決內存不足的問題。
    如果強引用對象不使用時,需要弱化從而使GC能夠回收,如下:
    strongReference = null;
    復制代碼顯式地設置strongReference對象為null,或讓其超出對象的生命周期范圍,則gc認為該對象不存在引用,這時就可以回收這個對象。具體什么時候收集這要取決于GC算法。
    public void test() {
    Object strongReference = new Object();
    // 省略其他操作
    }
    復制代碼在一個方法的內部有一個強引用,這個引用保存在Java棧中,而真正的引用內容(Object)保存在Java堆中。
    當這個方法運行完成后,就會退出方法棧,則引用對象的引用數為0,這個對象會被回收。
    但是如果這個strongReference是全局變量時,就需要在不用這個對象時賦值為null,因為強引用不會被垃圾回收。
    ArrayList的Clear方法:

在ArrayList類中定義了一個elementData數組,在調用clear方法清空數組時,每個數組元素被賦值為null。
不同于elementData=null,強引用仍然存在,避免在后續調用add()等方法添加元素時進行內存的重新分配。
使用如clear()方法內存數組中存放的引用類型進行內存釋放特別適用,這樣就可以及時釋放內存。

  1. 軟引用(SoftReference)
    如果一個對象只具有軟引用,則內存空間充足時,垃圾回收器就不會回收它;如果內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就可以被程序使用。

軟引用可用來實現內存敏感的高速緩存。

// 強引用
String strongReference = new String("abc");
// 軟引用
String str = new String("abc");
SoftReference<String> softReference = new SoftReference<String>(str);

復制代碼軟引用可以和一個引用隊列(ReferenceQueue)聯合使用。如果軟引用所引用對象被垃圾回收,JAVA虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
String str = new String("abc");
SoftReference<String> softReference = new SoftReference<>(str, referenceQueue);

str = null;
// Notify GC
System.gc();

System.out.println(softReference.get()); // abc

Reference<? extends String> reference = referenceQueue.poll();
System.out.println(reference); //null

復制代碼
注意:軟引用對象是在jvm內存不夠的時候才會被回收,我們調用System.gc()方法只是起通知作用,JVM什么時候掃描回收對象是JVM自己的狀態決定的。就算掃描到軟引用對象也不一定會回收它,只有內存不夠的時候才會回收。

當內存不足時,JVM首先將軟引用中的對象引用置為null,然后通知垃圾回收器進行回收:
if(JVM內存不足) {
// 將軟引用中的對象引用置為null
str = null;
// 通知垃圾回收器進行回收
System.gc();
}
復制代碼也就是說,垃圾收集線程會在虛擬機拋出OutOfMemoryError之前回收軟引用對象,而且虛擬機會盡可能優先回收長時間閑置不用的軟引用對象。對那些剛構建的或剛使用過的"較新的"軟對象會被虛擬機盡可能保留,這就是引入引用隊列ReferenceQueue的原因。
應用場景:
瀏覽器的后退按鈕。按后退時,這個后退時顯示的網頁內容是重新進行請求還是從緩存中取出呢?這就要看具體的實現策略了。

如果一個網頁在瀏覽結束時就進行內容的回收,則按后退查看前面瀏覽過的頁面時,需要重新構建;
如果將瀏覽過的網頁存儲到內存中會造成內存的大量浪費,甚至會造成內存溢出。

這時候就可以使用軟引用,很好的解決了實際的問題:
// 獲取瀏覽器對象進行瀏覽
Browser browser = new Browser();
// 從后臺程序加載瀏覽頁面
BrowserPage page = browser.getPage();
// 將瀏覽完畢的頁面置為軟引用
SoftReference softReference = new SoftReference(page);

// 回退或者再次瀏覽此頁面時
if(softReference.get() != null) {
    // 內存充足,還沒有被回收器回收,直接獲取緩存
    page = softReference.get();
} else {
    // 內存不足,軟引用的對象已經回收
    page = browser.getPage();
    // 重新構建軟引用
    softReference = new SoftReference(page);
}

復制代碼3. 弱引用(WeakReference)
弱引用與軟引用的區別在于:只具有弱引用的對象擁有更短暫的生命周期。在垃圾回收器線程掃描它所管轄的內存區域的過程中,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存。不過,由于垃圾回收器是一個優先級很低的線程,因此不一定會很快發現那些只具有弱引用的對象。
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
str = null;
復制代碼JVM首先將軟引用中的對象引用置為null,然后通知垃圾回收器進行回收:
str = null;
System.gc();
復制代碼
注意:如果一個對象是偶爾(很少)的使用,并且希望在使用時隨時就能獲取到,但又不想影響此對象的垃圾收集,那么你應該用Weak Reference來記住此對象。

下面的代碼會讓一個弱引用再次變為一個強引用:
String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);
// 弱引用轉強引用
String strongReference = weakReference.get();
復制代碼同樣,弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
簡單測試:
GCTarget.java
public class GCTarget {
// 對象的ID
public String id;

// 占用內存空間
byte[] buffer = new byte[1024];

public GCTarget(String id) {
    this.id = id;
}

protected void finalize() throws Throwable {
    // 執行垃圾回收時打印顯示對象ID
    System.out.println("Finalizing GCTarget, id is : " + id);
}

}
復制代碼GCTargetWeakReference.java
public class GCTargetWeakReference extends WeakReference<GCTarget> {
// 弱引用的ID
public String id;

public GCTargetWeakReference(GCTarget gcTarget,
          ReferenceQueue<? super GCTarget> queue) {
    super(gcTarget, queue);
    this.id = gcTarget.id;
}

protected void finalize() {
    System.out.println("Finalizing GCTargetWeakReference " + id);
}

}
復制代碼WeakReferenceTest.java
public class WeakReferenceTest {
// 弱引用隊列
private final static ReferenceQueue<GCTarget> REFERENCE_QUEUE = new ReferenceQueue<>();

public static void main(String[] args) {
    LinkedList<GCTargetWeakReference> gcTargetList = new LinkedList<>();

    // 創建弱引用的對象,依次加入鏈表中
    for (int i = 0; i < 5; i++) {
        GCTarget gcTarget = new GCTarget(String.valueOf(i));
        GCTargetWeakReference weakReference = new GCTargetWeakReference(gcTarget,
            REFERENCE_QUEUE);
        gcTargetList.add(weakReference);

        System.out.println("Just created GCTargetWeakReference obj: " +
            gcTargetList.getLast());
    }

    // 通知GC進行垃圾回收
    System.gc();

    try {
        // 休息幾分鐘,等待上面的垃圾回收線程運行完成
        Thread.sleep(6000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    // 檢查關聯的引用隊列是否為空
    Reference<? extends GCTarget> reference;
    while((reference = REFERENCE_QUEUE.poll()) != null) {
        if(reference instanceof GCTargetWeakReference) {
            System.out.println("In queue, id is: " +
                ((GCTargetWeakReference) (reference)).id);
        }
    }
}

}
復制代碼運行WeakReferenceTest.java,運行結果如下:

可見WeakReference對象的生命周期基本由垃圾回收器決定,一旦垃圾回收線程發現了弱引用對象,在下一次GC過程中就會對其進行回收。

  1. 虛引用(PhantomReference)
    虛引用顧名思義,就是形同虛設。與其他幾種引用都不同,虛引用并不會決定對象的生命周期。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
    應用場景:
    虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
    虛引用與軟引用和弱引用的一個區別在于:

虛引用必須和引用隊列(ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象的內存之前,把這個虛引用加入到與之關聯的引用隊列中。

String str = new String("abc");
ReferenceQueue queue = new ReferenceQueue();
// 創建虛引用,要求必須與一個引用隊列關聯
PhantomReference pr = new PhantomReference(str, queue);

復制代碼程序可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否將要進行垃圾回收。如果程序發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對象的內存被回收之前采取必要的行動。

8、Java反射

Java 反射機制在程序運行時,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個對象,都能夠調用它的任意一個方法和屬性。這種 動態的獲取信息 以及 動態調用對象的方法 的功能稱為 java 的反射機制。
反射機制很重要的一點就是“運行時”,其使得我們可以在程序運行時加載、探索以及使用編譯期間完全未知的 .class 文件。換句話說,Java 程序可以加載一個運行時才得知名稱的 .class 文件,然后獲悉其完整構造,并生成其對象實體、或對其 fields(變量)設值、或調用其 methods(方法)。
https://juejin.im/post/598ea9116fb9a03c335a99a4

9、Arrays.sort 實現原理和 Collection.sort 實現原理

排序時小數組使用快排(插入排序):Use Quicksort on small arrays
之后考慮歸并排序.
Arrays.sort
事實上Collections.sort方法底層就是調用的array.sort方法,

public static void sort(Object[] a) {
        if (LegacyMergeSort.userRequested)
            legacyMergeSort(a);
        else
            ComparableTimSort.sort(a, 0, a.length, null, 0, 0);
    }
//void java.util.ComparableTimSort.sort()
static void sort(Object[] a, int lo, int hi, Object[] work, int workBase, int workLen) {
        assert a != null && lo >= 0 && lo <= hi && hi <= a.length;

        int nRemaining  = hi - lo;
        if (nRemaining < 2)
            return;  // Arrays of size 0 and 1 are always sorted

        // If array is small, do a "mini-TimSort" with no merges
        if (nRemaining < MIN_MERGE) {
            int initRunLen = countRunAndMakeAscending(a, lo, hi);
            binarySort(a, lo, hi, lo + initRunLen);
            return;
        }

legacyMergeSort(a):歸并排序
ComparableTimSort.sort():Timsort排序

備注:
Timsort排序是結合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法。(參考:Timsort原理介紹)
Timsort的核心過程
TimSort 算法為了減少對升序部分的回溯和對降序部分的性能倒退,將輸入按其升序和降序特點進行了分區。排序的輸入的單位不是一個個單獨的數字,而是一個個的塊-分區。其中每一個分區叫一個run。針對這些 run 序列,每次拿一個 run 出來按規則進行合并。每次合并會將兩個 run合并成一個 run。合并的結果保存到棧中。合并直到消耗掉所有的 run,這時將棧上剩余的 run合并到只剩一個 run 為止。這時這個僅剩的 run 便是排好序的結果。
綜上述過程,Timsort算法的過程包括
(0)如何數組長度小于某個值,直接用二分插入排序算法
(1)找到各個run,并入棧
(2)按規則合并run

10、LinkedHashMap的應用

HashMap是無序的,當我們希望有順序地去存儲key-value時,就需要使用LinkedHashMap了
詳細 http://www.lxweimin.com/p/8f4f58b4b8ab

11、cloneable接口實現原理

Cloneable接口是Java開發中常用的一個接口, 它的作用是使一個類的實例能夠將自身拷貝到另一個新的實例中,注意,這里所說的“拷貝”拷的是對象實例,而不是類的定義,進一步說,拷貝的是一個類的實例中各字段的值。
如果一個類不實現該接口就直接調用clone()方法的話,即便已將clone()方法重寫為public,那還是會拋出“不支持拷貝”異常。因此,要想使一個類具備拷貝實例的功能,那么除了要重寫Object類的clone()方法外,還必須要實現Cloneable接口。
在開發過程中,拷貝實例是常見的一種操作,如果一個類中的字段較多,而我們又采用在客戶端中逐字段復制的方法進行拷貝操作的話,將不可避免的造成客戶端代碼繁雜冗長,而且也無法對類中的私有成員進行復制,而如果讓需要具備拷貝功能的類實現Cloneable接口,并重寫clone()方法,就可以通過調用clone()方法的方式簡潔地實現實例拷貝功能。

12、異常分類以及處理機制

avatar

在 Java 中,所有的異常都有一個共同的祖先 Throwable(可拋出)。Throwable 指定代碼中可用異常傳播機制通過 Java 應用程序傳輸的任何問題的共性。
Throwable: 有兩個重要的子類:Exception(異常)和 Error(錯誤),二者都是 Java 異常處理的重要子類,各自都包含大量子類。
Error(錯誤):是程序無法處理的錯誤,表示運行應用程序中較嚴重問題。大多數錯誤與代碼編寫者執行的操作無關,而表示代碼運行時 JVM(Java 虛擬機)出現的問題。例如,Java虛擬機運行錯誤(Virtual MachineError),當 JVM 不再有繼續執行操作所需的內存資源時,將出現 OutOfMemoryError。這些異常發生時,Java虛擬機(JVM)一般會選擇線程終止。這些錯誤表示故障發生于虛擬機自身、或者發生在虛擬機試圖執行應用時,如Java虛擬機運行錯誤(Virtual MachineError)、類定義錯誤(NoClassDefFoundError)等。這些錯誤是不可查的,因為它們在應用程序的控制和處理能力之 外,而且絕大多數是程序運行時不允許出現的狀況。對于設計合理的應用程序來說,即使確實發生了錯誤,本質上也不應該試圖去處理它所引起的異常狀況。在 Java中,錯誤通過Error的子類描述。
Exception(異常):是程序本身可以處理的異常.
Exception 類有一個重要的子類 RuntimeException。RuntimeException 類及其子類表示“JVM 常用操作”引發的錯誤。例如,若試圖使用空值對象引用、除數為零或數組越界,則分別引發運行時異常(NullPointerException、ArithmeticException)和 ArrayIndexOutOfBoundException。
注意:異常和錯誤的區別:異常能被程序本身可以處理,錯誤是無法處理。
通常,Java的異常(包括Exception和Error)分為可查的異常(checked exceptions)和不可查的異常(unchecked exceptions).
可查異常(編譯器要求必須處置的異常):正確的程序在運行中,很容易出現的、情理可容的異常狀況。可查異常雖然是異常狀況,但在一定程度上它的發生是可以預計的,而且一旦發生這種異常狀況,就必須采取某種方式進行處理。除了RuntimeException及其子類以外,其他的Exception類及其子類都屬于可查異常。這種異常的特點是Java編譯器會檢查它,也就是說,當程序中可能出現這類異常,要么用try-catch語句捕獲它,要么用throws子句聲明拋出它,否則編譯不會通過。
不可查異常(編譯器不要求強制處置的異常):包括運行時異常(RuntimeException與其子類)和錯誤(Error)。
Exception 這種異常分兩大類運行時異常和非運行時異常(編譯異常)。程序中應當盡可能去處理這些異常。
運行時異常:都是RuntimeException類及其子類異常,如NullPointerException(空指針異常)、IndexOutOfBoundsException(下標越界異常)等,這些異常是不檢查異常,程序中可以選擇捕獲處理,也可以不處理。這些異常一般是由程序邏輯錯誤引起的,程序應該從邏輯角度盡可能避免這類異常的發生。運行時異常的特點是Java編譯器不會檢查它,也就是說,當程序中可能出現這類異常,即使沒有用try-catch語句捕獲它,也沒有用throws子句聲明拋出它,也會編譯通過。
非運行時異常 (編譯異常):是RuntimeException以外的異常,類型上都屬于Exception類及其子類。從程序語法角度講是必須進行處理的異常,如果不處理,程序就不能編譯通過。如IOException、SQLException等以及用戶自定義的Exception異常,一般情況下不自定義檢查異常。

13、wait和sleep的區別
avatar

avatar

sleep 讓線程從 【running】 -> 【阻塞態】 時間結束/interrupt -> 【runnable】
wait 讓線程從 【running】 -> 【等待隊列】notify -> 【鎖池】 -> 【runnable】

14、數組在內存中如何分配

幾乎所有的程序設計語言都支持數組。Java也不例外。當我們需要多個類型相同的變量的時候,就考慮定義一個數組。在Java中,數組變量是引用類型的變量,同時因為Java是典型的靜態語言,因此它的數組也是靜態的,所以想要使用就必須先初始化(為數組對象的元素分配空間)。

1.數組的初始化方式及其內存分配
對于Java數組的初始化,有以下兩種方式,這也是面試中經常考到的經典題目:

靜態初始化:初始化時由程序員顯式指定每個數組元素的初始值,由系統決定數組長度,如:

1 //只是指定初始值,并沒有指定數組的長度,但是系統為自動決定該數組的長度為4
2 String[] computers = {"Dell", "Lenovo", "Apple", "Acer"};  //①
3 //只是指定初始值,并沒有指定數組的長度,但是系統為自動決定該數組的長度為3
4 String[] names = new String[]{"多啦A夢", "大雄", "靜香"};  //②
動態初始化:初始化時由程序員顯示的指定數組的長度,由系統為數據每個元素分配初始值,如:
1 //只是指定了數組的長度,并沒有顯示的為數組指定初始值,但是系統會默認給數組數組元素分配初始值為null
2 String[] cars = new String[4];  //③

前面提到,因為Java數組變量是引用類型的變量,所以上述幾行初始化語句執行后,三個數組在內存中的分配情況如下圖所示:
Java數組及其內存分配
[圖片上傳失敗...(image-f1374c-1559907045613)]
由上圖可知,靜態初始化方式,程序員雖然沒有指定數組長度,但是系統已經自動幫我們給分配了,而動態初始化方式,程序員雖然沒有顯示的指定初始化值,但是因為Java數組是引用類型的變量,所以系統也為每個元素分配了初始化值null,當然不同類型的初始化值也是不一樣的,假設是基本類型int類型,那么為系統分配的初始化值也是對應的默認值0。

對于多維數組,假設有這么一段代碼:
1 int[][] nums = new int[2][2];2 nums[0][1] = 2;
那么他在內存中的分配情況如下:
[圖片上傳失敗...(image-434b2c-1559907045613)]

Java數組及其內存分配
由上圖可知,并沒有真正的多維數組,它的本質其實是一維數組

Java 并發

1、synchronized 的實現原理以及鎖優化?

http://www.lxweimin.com/p/c5058b6fe8e5

2、volatile 的實現原理?

3、Java 的信號燈?

4、synchronized 在靜態方法和普通方法的區別?

5、怎么實現所有線程在等待某個事件的發生才會去執行?

6、CAS?CAS 有什么缺陷,如何解決?

7、synchronized 和 lock 有什么區別?

8、Hashtable 是怎么加鎖的 ? TODO
9、HashMap 的并發問題?
10、ConcurrenHashMap 介紹?1.8 中為什么要用紅黑樹?
11、AQS
12、如何檢測死鎖?怎么預防死鎖?

13、Java 內存模型?
14、如何保證多線程下 i++ 結果正確?
15、線程池的種類,區別和使用場景?
16、分析線程池的實現原理和線程的調度過程?
17、線程池如何調優,最大數目如何確認?
18、ThreadLocal原理,用的時候需要注意什么?
19、CountDownLatch 和 CyclicBarrier 的用法,以及相互之間的差別?
20、LockSupport工具
21、Condition接口及其實現原理
22、Fork/Join框架的理解
23、分段鎖的原理,鎖力度減小的思考
24、八種阻塞隊列以及各個阻塞隊列的特性

Spring

1、BeanFactory 和 FactoryBean?
2、Spring IOC 的理解,其初始化過程?
3、BeanFactory 和 ApplicationContext? 國際化 時間廣播 url訪問
4、Spring Bean 的生命周期,如何被管理的?
5、Spring Bean 的加載過程是怎樣的?
6、如果要你實現Spring AOP,請問怎么實現?
7、如果要你實現Spring IOC,你會注意哪些問題?
8、Spring 是如何管理事務的,事務管理機制?
9、Spring 的不同事務傳播行為有哪些,干什么用的?
10、Spring 中用到了那些設計模式? 工廠 單例 適配器 模板 觀察者
11、Spring MVC 的工作原理?
12、Spring 循環注入的原理?
13、Spring AOP的理解,各個術語,他們是怎么相互工作的?
14、Spring 如何保證 Controller 并發的安全? 避免定義實例變量 threadLocal變量 scope=prototype

Netty

1、BIO、NIO和AIO done
2、Netty 的各大組件 done
3、Netty的線程模型 done
4、TCP 粘包/拆包的原因及解決方法 done
https://blog.csdn.net/scythe666/article/details/51996268
5、了解哪幾種序列化協議?包括使用場景和如何去選擇
6、Netty的零拷貝實現 done 直接堆外內存 compositebuf transfer.to wrap
7、Netty的高性能表現在哪些方面 done

1.io線程模型
使用reactor模式,同步非阻塞。這決定了可以用最少的資源做更多的事。
2.內存零拷貝
使用直接緩存
3.內存池設計
申請的內存可以重用,主要指直接內存。
內部實現是用一顆二叉查找樹管理內存分配情況。
4.串形化處理socket讀寫,避免鎖,即一個指定socket的消息是串形化處理的。這樣性能比多個線程同時 處理一個socket對應消息要好,因為多線程處理會有鎖。
5.提供對protobuf等高性能序列化協議支持

分布式相關

1、Dubbo的底層實現原理和機制
https://blog.csdn.net/u013322876/article/details/72846054

2、描述一個服務從發布到被消費的詳細過程

3、分布式系統怎么做服務治理
https://www.cnblogs.com/Zachary-Fan/p/service_manage_discovery.html

4、接口的冪等性的概念

5、消息中間件如何解決消息丟失問題
kafka生成端:基于ack機制
kafka消費端:
大家都知道 Kafka 會自動提交 offset,那么只要 關閉自動提交 offset,在處理完之后自己手動提交 offset,就可以保證數據不會丟。但是此時確實還是 可能會有重復消費 ,比如你剛處理完,還沒提交 offset,結果自己掛了,此時肯定會重復消費一次,自己保證冪等性就好了。
broker端:

  • 給 topic 設置 replication.factor 參數:這個值必須大于 1,要求每個 partition 必須有至少 2 個副本。
  • 在 Kafka 服務端設置 min.insync.replicas 參數:這個值必須大于 1,這個是要求一個 leader 至少感知到有至少一個 follower 還跟自己保持聯系,沒掉隊,這樣才能確保 leader 掛了還有一個 follower 吧。
  • 在 producer 端設置 acks=all :這個是要求每條數據,必須是 寫入所有 replica 之后,才能認為是寫成功了 。
  • 在 producer 端設置 retries=MAX (很大很大很大的一個值,無限次重試的意思):這個是 要求一旦寫入失敗,就無限重試 ,卡在這里了。

6、Dubbo的服務請求失敗怎么處理 超時重試
7、重連機制會不會造成錯誤 接口冪等
8、對分布式事務的理解
9、如何實現負載均衡,有哪些算法可以實現?
在集群負載均衡時,Dubbo提供了多種均衡策略,缺省為random隨機調用。

  • Random LoadBalance
    隨機,按權重設置隨機概率。
    在一個截面上碰撞的概率高,但調用量越大分布越均勻,而且按概率使用權重后也比較均勻,有利于動態調整提供者權重。

  • RoundRobin LoadBalance
    輪循,按公約后的權重設置輪循比率。
    存在慢的提供者累積請求問題,比如:第二臺機器很慢,但沒掛,當請求調到第二臺時就卡在那,久而久之,所有請求都卡在調到第二臺上。

  • LeastActive LoadBalance
    最少活躍調用數,相同活躍數的隨機,活躍數指調用前后計數差。
    使慢的提供者收到更少請求,因為越慢的提供者的調用前后計數差會越大。

  • ConsistentHash LoadBalance
    一致性Hash,相同參數的請求總是發到同一提供者。
    當某一臺提供者掛時,原本發往該提供者的請求,基于虛擬節點,平攤到其它提供者,不會引起劇烈變動。

10、Zookeeper的用途,選舉的原理是什么?
http://www.lxweimin.com/p/e35104ec6e5a

11、數據的垂直拆分水平拆分。

12、zookeeper原理和適用場景

13、zookeeper watch機制
ZooKeeper 允許客戶端向服務端注冊一個 Watcher 監聽,當服務端的一些指定事件觸發了這個 Watcher,那么就會向指定客戶端發送一個事件通知來實現分布式的通知功能。
ZooKeeper 的 Watcher 機制主要包括客戶端線程、客戶端 WatchManager 和 ZooKeeper 服務器三部分。在具體工作流程上,簡單地講,客戶端在向 ZooKeeper 服務器注冊 Watcher 的同時,會將 Watcher 對象存儲在客戶端的 WatchManager 中。當 ZooKeeper 服務器端觸發 Watcher 事件后,會向客戶端發送通知,客戶端線程從 WatchManager 中取出對應的 Watcher 對象來執行回調邏輯。

14、redis/zk節點宕機如何處理
zk:保證過半數節點可用
redis節點掛掉:
A、某個主節點和所有從節點全部掛掉,我們集群就進入faill狀態。
B、如果集群超過半數以上master掛掉,無論是否有slave,集群進入fail狀態.
C、如果集群任意master掛掉,且當前master沒有slave.集群進入fail狀態

集群就會把這個主節點的一個從節點設置為新的主節點

15、分布式集群下如何做到唯一序列號

  • 利用數據庫
  • 利用redis,如果是集群,則每臺機器分配一個值

16、如何做一個分布式鎖
http://www.lxweimin.com/p/350a5f891f11

17、用過哪些MQ,怎么用的,和其他mq比較有什么優缺點,MQ的連接是線程安全的嗎?

kafka生產端是單線程,線程安全
kafka的消費端是非線程安全,確保消費端的線程數和分區數能夠映射上。

18、MQ系統的數據如何保證不丟失

19、列舉出你能想到的數據庫分庫分表策略;分庫分表后,如何解決全表查詢的問題

20、zookeeper的選舉策略
https://www.cnblogs.com/ASPNET2008/p/6421571.html

21、全局ID

數據庫 done

1、mysql分頁有什么優化
https://www.cnblogs.com/geningchao/p/6649907.html

2、悲觀鎖、樂觀鎖

樂觀鎖:使用版本號或者時間戳
悲觀鎖:使用了排他鎖來實現(select **** for update)。文章開頭說到,innodb加行鎖的前提是:必須是通過索引條件來檢索數據,否則會切換為表鎖。

3、組合索引,最左原則

4、mysql 的表鎖、行鎖

表鎖:表級鎖和M鎖
行鎖:讀鎖和寫鎖

5、mysql 性能優化
https://www.cnblogs.com/zhouyusheng/p/8038224.html
6、mysql的索引分類:
ans:有序數組(靜態存儲引擎) + b+樹
B+,hash;什么情況用什么索引

7、事務的特性和隔離級別
事務特性:ACID
事務隔離級別:RR、RC、RUC、SERLALIZABLE 分別解決可重復讀、臟讀、什么也解決不了、解決幻讀
幻讀:ABA操作,結果變成ABC,前面A為事務,后面為行數

緩存

1、Redis用過哪些數據數據,以及Redis底層怎么實現
底層數據結構:簡單動態字符串、鏈表、字典、跳躍表、整數集合、壓縮列表
實際應用數據結構:字符串對象、列表對象(list)、哈希對象(hash)、集合對象(set)、有序集合(sort set)對象
字符串對象:int、raw或者embstr(后面兩個是字符串的變種)
列表對象:壓縮列表 —> 鏈表
哈希對象:壓縮列表 -> 字典
集合對象:整數集合、字典
有序集合:跳躍表、壓縮列表

2、Redis緩存穿透、緩存雪崩
緩存穿透:指查詢一個數據庫一定不存在的數據 布隆過濾器攔截
緩存雪崩:由于原有緩存失效(過期),新緩存未到期間。所有請求都去查詢數據庫,而對數據庫CPU和內存造成巨大壓力,嚴重的會造成數據庫宕機。從而形成一系列連鎖反應,造成整個系統崩潰。

設置差異化的過期時間,使用異步定時任務更新過期時間
3、如何使用Redis來實現分布式鎖

SETNX key val
當且僅當key不存在時,set一個key為val的字符串,返回1;若key存在,則什么都不做,返回0。
expire key timeout
為key設置一個超時時間,單位為second,超過這個時間鎖會自動釋放,避免死鎖。

4、Redis的并發競爭問題如何解決(服務器 + 客戶端)方式
ans:
4.1、使用zk分布式鎖
4.2、使用redis.setux鎖
4.3、使用watch multi來實現樂觀鎖機制
4.4、redis.incr自增機制
https://www.cnblogs.com/shamo89/p/8385390.html

5、Redis持久化的幾種方式,優缺點是什么,怎么實現的

ans:AOF 和 RDB
RDB:fork一個子進程,子進程將數據寫到磁盤上一個臨時RDB文件中。當子進程完成寫臨時文件后,將原來的 RDB 替換掉,這樣的好處就是可以copy-on-write。以一定的頻率保存數據,DB文件需要保存整個數據集的狀態。
優點:這種文件非常適合用于進行備份
缺點:如果你需要盡量避免在服務器故障時丟失數據,那么RDB不適合你。

AOF:需要在配置文件中開啟(默認是no),appendonly yes開啟AOF之后,Redis 每執行一個修改數據的命令,都會把它添加到 AOF文件中,當Redis重啟時,將會讀取AOF文件進行“重放”以恢復到 Redis 關閉前的最后時刻
優點:使用AOF持久化會讓Redis變得非常耐久
缺點:AOF 文件的體積通常要大于RDB文件的體積,數據恢復需要處理巨大的文件載入

6、Redis的緩存失效策略

定時刪除:使用定時器,定時刪除數據,占用CPU時間
惰性刪除:程序只有在取出鍵時,才會對鍵進行定期刪除,CPU友好,內存不友好
定期刪除:整合這兩種策略,每隔一段時間執行一次過期鍵刪除操作,每次限制執行的時長和頻率。

7、Redis集群,高可用原理 sential+Master/Slave
redis集群實現:
客戶端實現: 典型的是支持一致性哈希的客戶端
代理層實現: 典型代表twemproxy、codis
redis服務端實現: redis cluster
http://bbs.redis.cn/forum.php?mod=viewthread&tid=2266&highlight=redis%2B%E9%9B%86%E7%BE%A4

8、Redis緩存分片

集群采用分片的方式來實現數據共享,提供復制和故障轉移功能。16384個槽,分配到不同節點
當 Redis Cluster 的客戶端來連接集群時,它也會得到一份集群的槽位配置信息。這樣當客戶端要查找某個 key 時,可以直接定位到目標節點。
集群的槽位配置信息通過slots_to_keys跳躍表來保存槽和鍵之間的關系。

9、Redis的數據淘汰策略

volatile-lru:從設置了過期時間的數據集中,選擇最近最久未使用的數據釋放;
allkeys-lru:從數據集中(包括設置過期時間以及未設置過期時間的數據集中),選擇最近最久未使用的數據釋放;
volatile-random:從設置了過期時間的數據集中,隨機選擇一個數據進行釋放;
allkeys-random:從數據集中(包括了設置過期時間以及未設置過期時間)隨機選擇一個數據進行入釋放;
volatile-ttl:從設置了過期時間的數據集中,選擇馬上就要過期的數據進行釋放操作;
noeviction:不刪除任意數據(但redis還會根據引用計數器進行釋放),這時如果內存不夠時,會直接返回錯誤

JVM

1、詳細jvm內存模型
2、講講什么情況下回出現內存溢出,內存泄漏?
3、說說Java線程棧
4、JVM 年輕代到年老代的晉升過程的判斷條件是什么呢?
5、JVM 出現 fullGC 很頻繁,怎么去線上排查問題?
6、類加載為什么要使用雙親委派模式,有沒有什么場景是打破了這個模式?
7、類的實例化順序
8、JVM垃圾回收機制,何時觸發MinorGC等操作
9、JVM 中一次完整的 GC 流程(從 ygc 到 fgc)是怎樣的
10、各種回收器,各自優缺點,重點CMS、G1
11、各種回收算法
12、OOM錯誤,stackoverflow錯誤,permgen space錯誤

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容