2018 Java面試題(2019.08.01 更新)

目錄

Java基礎:

面向對象的特徵:繼承、封裝和多態

重載和重寫的區別

int 和 Integer 有什麼區別;Integer的值緩存範圍

說說反射的用途及實現

Http 請求的 GET 和 POST 方式的區別

MVC設計思想

什麼是Java序列化和反序列化;如何實現Java序列化;或者請描述Serializable接口的作用

Colletcion類庫中常用類

 

進程和線程:

線程和進程的概念

並行和併發的概念

創建線程的方式及實現

進程間通信的方式

說說 CountDownLatch、CyclicBarrier 原理和區別

說說 Semaphore 原理

說說 Exchanger 原理

ThreadLocal 原理分析;ThreadLocal爲什麼會出現OOM,出現的深層次原理

講講線程池的實現原理

線程池的幾種實現方式 

線程的生命週期;狀態是如何轉移的

Java中用到的線程調度算法

單例模式的線程安全性

線程類的構造方法、靜態塊是被哪個線程調用的?

sleep() 和 wait() 有什麼區別?

多線程的上下文切換

 

鎖機制:

什麼是線程安全?如何保證線程安全?

重入鎖的概念;重入鎖爲什麼可以防止死鎖?

產生死鎖的四個條件

如何檢查死鎖

volatile 實現原理

synchronized 實現原理(對象監視器)

synchronized 與 lock 的區別

AQS 同步隊列

CAS 無鎖的概念;樂觀鎖和悲觀鎖

常見的原子操作類

什麼是 ABA 問題;出現 ABA 問題 JDK 是如何解決的

樂觀鎖的業務場景及實現方式

Java 8 併發包下常見的併發類

偏向鎖、輕量級鎖、重量級鎖、自旋鎖的概念

 

數據庫:

DDL、DML、DCL 分別指什麼

explain 命令

髒讀、幻讀、不可重複讀

事務的隔離級別

數據庫的幾大範式

說說分庫與分表設計

分庫與分錶帶來的分佈式困境與對應之策

說說 SQL 優化之道

存儲引擎的 InnoDB 與 MyISAM 區別、優缺點、使用場景

索引類別(B+樹索引、全文索引、哈希索引);索引的區別

什麼是自適應哈希索引(AHI)

爲什麼要用 B+tree 作爲 MySql 索引的數據結構

聚集索引與非聚集索引的區別

limit 20000 加載很慢怎麼解決

常見的幾種分佈式 ID 的設計方案

 

JVM

JVM 運行時內存區域劃分

常見的 GC 回收算法及其含義

常見的 JVM 性能監控和故障處理工具類

JVM 性能調優

類加載器、雙親委派模型

類加載的過程

強引用、軟引用、弱引用、虛引用

Java 內存模型 JMM


Java基礎:

面向對象的特徵:繼承、封裝和多態

              

  • 封裝:

            封裝,就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏;

  • 繼承:

          它可以使用現有類的所有功能,並在無需重新編寫原來的類的情況下對這些功能進行擴展;

          通過繼承創建的新類成爲"子類"或者"派生類";

          被繼承的類成爲"基類"、"父類"或"超類";

          繼承的過程,就是從一般到特殊的過程;

          一般情況下,一個子類只能有一個基類,要實現多重繼承,可以通過多級繼承來實現。

          繼承概念的實現方式有三類:

  1. 實現繼承:是指使用基類的屬性和方法而無需額外編碼的能力;
  2. 接口繼承:是指僅使用屬性和方法的名稱、但是子類必須提供實現的能力;
  3. 可視繼承:是指子窗體(類)使用基窗體(類)的外觀和實現代碼的能力。
  • 多態:

          是允許你將父對象設置成爲一個或者更多的它的子對象相等的技術,賦值之後,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說,就是一句話:允許將子類類型的指針賦值給父類類型的指針。

          多態概念的實現方式有兩種:

  1. 覆蓋:是指子類重新定義父類的虛函數的做法;
  2. 重載:是指允許存在多個同名函數,而這些函數的參數表不同(或許參數個數不用,或許參數類型不用、或許兩者都不同)。

 

重載和重寫的區別

  • override(重寫)
  1. 方法名、參數、返回值相同;
  2. 子類方法不能縮小父類方法的訪問權限;
  3. 子類方法不能拋出比父類方法更多的異常(但子類方法可以不拋出異常);
  4. 存在於父類和子類之間;
  5. 方法被定義爲final的不能被重寫。
  • overload(重載)
  1. 參數類型、個數、順序至少有一個不相同;
  2. 不能重載只有返回值不同的方法名;
  3. 存在於父類和子類、同類型中。
區別點 重載 重寫(覆寫)
英文 Overloading Overiding
定義 方法名稱相同,參數的類型或個數不同 方法名稱、參數類型、返回值類型全部相同
權限 對權限沒要求 被重寫的方法不能擁有更嚴格的權限
範圍 發生在一個類中 發生在繼承類中

 

 

int 和 Integer 有什麼區別;Integer的值緩存範圍

          兩者之間的區別:

  1. Integer是int的包裝類;int是基本數據類型;
  2. Integer變量必須實例化後才能使用;int不需要;
  3. Integer實際是對象的引用,指向此new的Integer對象;int是直接存儲數據值;
  4. Integer的默認值是null;int的默認值是0。

          Integer的值緩存範圍:-128 ~ 127 

 

說說反射的用途及實現

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

          反射機制的原理:

         

  1. 當 Student 類 new 一個對象的時候,會通知 JVM 去本地加載 Student.class 這個二進制文件;
  2. JVM 在本地磁盤尋找這個文件,找到之後就把它加載到 JVM 內存中,在加載的同時會生成一個對象來映射這個 class 文件,該對象中存儲着 Student.class 的信息,包括字段、方法等,將該對象放在 JVM 的一塊內存空間中;
  3. 在 JVM 內存中爲 Student 開闢一塊空間,來存儲 student1。

          反射的本質就是當獲取到表示 Student.class 的對象後,反向獲取 Student 類的信息。

          Java反射框架提供以下功能:

  • 在運行時判斷任意一個對象所屬的類;
  • 在運行時構造任意一個類的對象;
  • 在運行時判斷任意一個類所具有的成員變量和方法(通過反射設置可以調用private);
  • 在運行時調用任意一個對象的方法。

 

Http 請求的 GET 和 POST 方式的區別

          GET產生一個TCP數據包:

                    對於GET方式的請求,瀏覽器會把http header和data一併發送出去,服務器響應200(返回數據);

          POST產生兩個TCP數據包:

                 對於POST方式的請求,瀏覽器先發送header,服務器響應100 continue,瀏覽器再發送data,服務器響應200 ok(返回數據)。

 

MVC設計思想

 首先,MVC不是一種設計模式,而是一種設計思想,接下來看下兩個概念的區別:

  • 設計模式:是一種固定的方法,不靈活,有特定的使用場景;
  • 設計思想:是一種思想,比較靈活,由多種設計模式組合實現。

接下來說說MVC的設計思想:

  • M(Model):主要功能提供數據(主要用來提供數據,並不關心數據讓誰顯示(Controller 負責給M要數據,然後控制數據讓哪一個View來顯示));
  • V(View):主要功能是展示數據(主要有數據即可,不關心數據來源);
  • C(Controller):主要功能協調V層與M層,作爲V層與M層溝通的橋樑。

 

什麼是Java序列化和反序列化;如何實現Java序列化;或者請描述Serializable接口的作用

  • 序列化:把對象轉換爲字節序列的過程稱爲對象的序列化;
  • 反序列化:把字節序列恢復爲對象的過程稱爲對象的反序列化。

如何實現Java序列化:實現serializable接口

  • ObjectOutputStream(對象輸出流):

它的 writeObject(Object obj) 方法可對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中;

  • ObjectInputStream(對象輸入流):

它的 readObject() 方法從一個源輸入流中讀取字節序列,再把他們反序列化爲一個對象,並將其返回。

 

Colletcion類庫中常用類

Collection是List、set父接口,不是Map父接口。

  • JavaList
    • List集合:ArrayList   LinkList   Vector 等;
    • Vector:是List接口下線程安全的結婚;
    • List是有序的;
    • ArrayList  和 LinkList 的區別:
      • ArrayList:適合於查詢較多的場合,實現了基於動態數組的數據結構;
      • LinkList:適合於插入較多的場合,實現了基於鏈表的數據結構(雙向鏈表)。
  • JavaMap
    • Map集合:HashMap HashTable ConcurrentHashMap  LinkedHashMap 等;
    • HashMap 不是線程安全的;
    • HashTable ControllerHashMap  SynchronizedMap 是線程安全的;
    • HashMap 的鍵值都可以爲NULL,HashTable不行;
    • 按添加順序使用LinkedHashMap,按自然順序使用TreeMap,自定義排序用TreeMap;
    • HashSet 和 HashTree 的區別:
      • HashSet:哈希表實現,數據是無序的,可以放入一個NULL值;
      • TreeSet:二叉樹實現,數據是自動排好序的,不允許放入NULL值。

 

進程和線程:

線程和進程的概念

  • 線程:單個進程中執行的每個任務就是一個線程。線程是進程中執行運算的最小單位;
  • 進程:是執行中的一段程序,即一旦程序被載入到內存中並準備執行,它就是一個進程;進程表示資源分配的基本概念,又是調度原型的基本單位,是系統中的併發執行的單位。

並行和併發的概念

  • 並行:指應用能夠同時執行不同的任務;
  • 併發:指應用能夠交替執行不同的任務。

創建線程的方式及實現

  • 繼承 Thread 類創建線程;

              A)定義 Thread 類的子類,並重寫該類的 run() 方法,該 run() 方法的方法體就代表了線程要完成的任務,因此把 run() 方法稱爲執行體;

              B)創建 Thread 子類的實例,即創建了線程對象;

              C)調用線程對象的 start() 方法來啓動該線程。

  • 實現 Runnable 接口創建線程;

              A)定義 runnable 接口的實現類,並重寫該接口的 run() 方法,該 run() 方法的方法體同樣是該線程的線程執行體;

              B)創建 Runnable 接口實現類的實例,並依此實例作爲 Thread 的 target 來創建 Thread 對象,該 Thread 對象纔是真正的線程對象;

              C)調用線程對象的 start() 方法來啓動該線程。

  • 使用 Callable 和 Future 創建線程。

              A)創建 Callable 接口的實現類,並實現 call() 方法,該 call() 方法將作爲線程執行體,並且有返回值;

              B)創建 Callable 實現類的實例,使用 Future Task 類來包裝 Callable 對象,該 Future Task 對象封裝了該 Callable 對象的 call() 方法的返回值;

              C)使用 Future Task 對象作爲 Thread 對象的 target 創建並啓動新線程;

              D)調用 Future Task 對象的 get() 方法來獲得子線程執行結束後的返回值。

三種方式的比較:

1> 通過 Runnable 和 Callable 創建多線程:

     優勢:

           A)線程類只是實現了 Runnable 接口或 Callable 接口,還可以繼承其他類;

          B)在這種方式下,多個線程可以共享同一個 target 對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將 CPU、代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。

    劣勢:

         C)編程稍微複雜,如果要訪問當前線程,必須使用 Thread.currentThread() 方法。

         D)  Runnable 和 Callable 的區別:

Runnable Callable
重寫的方法是 run() 重寫的方法是call()
Runnable的任務是不能返回值的 Callable的任務執行後可返回值
run()方法不可以拋出異常 call() 方法可以拋出異常
 

運行 Callable 任務可以看到一個 Future 對象,表示異步計算的結果。

它提供了檢索計算是否完成的方法,以等待計算的完成,

並檢索計算的結果。通過 Future 對象可以瞭解任務執行情況,

可取消任務的執行,還可以獲取執行結果。 

2> 使用繼承 Thread 類的方式創建多線程:

     優勢:

          如果要訪問當前線程,則無需使用 Thread.currentThread() 方法,使用 this即可;

     劣勢:

         已經繼承了 Thread 類,不能再繼承其他父類。

進程間通信的方式

          進程間通信(IPC,InterProcess  Communication)是指在不同進程之間傳播或交換信息。

  • 無名管道通信

          管道是一種半雙工(即數據只能在一個方向上流動)的通信方式,數據只能單向流動,而且只能在具有親緣關係的進程間使用。進程的親緣關係通常指父子進程關係。

  • 高級管道通信

          將另一個程序當做一個新的進程在當前程序進程中啓動,則它算是當前程序的子進程,這種方式我們稱爲高級管道方式。

  • 有名管道通信

          有名管道也是半雙工的通信方式,但是它允許無親緣關係進程間的通信。

  • 消息隊列通信

          是由消息的鏈表,存放在內核中並由消息隊列標識符標識。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。

  • 信號量通信

          信號量(Semaphore)是一個計數器,可以用來控制多個進程對共享資源的訪問。它常作爲一種鎖機制,防止某進程正在訪問共享資源時,其他進程也訪問該資源。因此,主要作爲進程間以及統一進程內不用線程之間的同步手段。

  • 信號

          信號是一種比較複雜的通信方式,用於通知接收進程某個事件已經發生。

  • 共享內存通信

          共享內存就是映射一段能被其他進程所訪問的內存,這段共享內存由一個進程創建,但多個進程都可以訪問。共享內存是對快的IPC方式,它是針對其他進程間通信方式運行效率低而專門設計的。它往往與其他通信機制,如信號量配合使用,來實現進程間的同步和通信。

  • 套接字通信

          套接字(socket):套接口也是一種進程間通信機制,與其他通信機制不同的是,它可用於不同機器間的進程通信。

 

說說 CountDownLatch、CyclicBarrier 原理和區別

CountDownLatch:

          是同步輔助類,它可以指定一個計數值,在併發環境下由線程進行減1操作,當計數值變爲0後,被 await() 方法阻塞的線程將會喚醒,實現線程間的同步。

          一個或N個線程等待其它線程的關係。

CyclicBarrier:

          是同步輔助類,它允許一組線程互相等待,直到所有線程都達到某個特公共屏障點(也可以叫同步點),即相互等待的線程都完成調用 await() 方法,所有被屏障攔截的線程纔會繼續運行 await() 方法後面的程序。

          各個線程內部相互等待的關係。

兩者的區別:

CountDownLatch CyclicBarrier
減計數方式 加計數方式
計算爲0時釋放所有等待的線程 計數達到指定值時釋放所有等待線程
計數爲0時,無法重置 計數達到指定值時,計數置爲0重新開始
調用countDown()方法計數減一,調用await()方法只進行阻塞,對計數沒任何影響 調用await()方法計數加1,若加1後的值不等於構造方法的值,則線程阻塞
不可重複利用 可重複利用

兩者的使用場景,下面的文章說的很清楚:

CountDownLatch 和 CyclicBarrier 的使用場景

 

說說 Semaphore 原理

Semaphore(信號量) 是用來控制同時訪問特定資源的線程數量,它通過協調各個線程,保證合理的使用公共資源。

線程可以通過 acquire() 方法來獲取信號量的許可。當信號量中沒有可用的許可的時候,線程阻塞,直到有可用的許可爲止。線程可以通過 release() 方法釋放它持有的信號量的許可。

Semaphore 內部基礎AQS的共享模式,所以實現都委託給了Sync類。

Semaphore 有兩種模式:

  • 公平模式:

調用 acquire() 的順序就是獲取許可證的順序,遵循FIFO;

  • 非公平模式:

爲搶佔式的,可能一個新的獲取線程恰好在一個許可證釋放時得到這個許可證,而前面還有等待的線程。

 

說說 Exchanger 原理

主要用於兩個工作線程之間交換數據。

Java Exchanger 原理

 

ThreadLocal 原理分析;ThreadLocal爲什麼會出現OOM,出現的深層次原理

          ThreadLocal 是線程的局部變量,是每一個線程所單獨持有的,其他線程不能對其進行訪問。通常是類中的 private  static 字段,是對該字段初始值的一個拷貝,它們希望將狀態與某一個線程(例如用戶ID或者事務ID)相關聯。

          ThreadLocal 爲什麼會出現OOM:

         

          ThreadLocal 的實現是這樣的:每個 Thread 維護一個 ThreadLocalMap 映射表,這個映射表的 key 是 ThreadLocal 實例本身,value 是真正需要存儲的 Object;

          ThreadLocal 裏面使用了一個存在弱引用的 map ,map 的類型是 ThreadLocal.ThreadLocalMap。Map 中的 key 爲一個 ThreadLocal 實例本身,而這個 key 使用弱引用指向 ThreadLocal 。當 ThreadLocal 實例置爲 null 後,沒有任何強引用指向 ThreadLocal 實例,所以ThreadLocal 將會被 GC 回收。但是我們的 value 卻不能回收,而這塊 value 永遠不會被訪問到。所以存在着內存泄漏。因爲存在一條從 Current Thread 鏈接過來的強引用,只有當 Thread 結束以後, Current Thread 就不會存在棧中,強引用斷開,Current Thread、Map Value 將全部GC回收。

          如何避免內存泄漏:

          每次使用完 ThreadLocal,都調用它的 remove() 方法,清除數據。

 

講講線程池的實現原理

   提交一個任務到線程池中,線程池的處理流程如下:

  1. 判斷線程池裏的核心線程是否都在執行任務,如果不是(核心線程空閒或者還有核心線程沒有被創建),則創建一個新的工作線程來執行任務。如果核心線程都在執行任務,則進入下個流程;
  2. 線程池判斷工作隊列是否已滿,如果工作隊列沒有滿,則將新提交的任務存儲到這個工作隊列裏;如果工作隊列滿了,則進入下個流程;
  3. 判斷線程池的線程是否都處於工作狀態,如果沒有,則創建一個新的工作線程來執行任務。如果已經滿了,則交給飽和策略來處理這個任務。

         

線程池的幾種實現方式 

  • Executors.newCachedThreadPool

          創建一個可緩存的線程池,如果線程池的長度超過處理的需要,可以靈活回收空閒線程,若無可回收,則新建線程。

        ExecutorService executorService = Executors.newCachedThreadPool();
 
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}",index);
                }
            });
        }
 
        executorService.shutdown();
  • Executors.newFixedThreadPool

          創建一個定長線程池,可以控制線程最大併發數,超出的線程會在隊列中等待。

        ExecutorService executorService = Executors.newFixedThreadPool(3);
 
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}",index);
                }
            });
        }
 
        executorService.shutdown();
  • Executors.newScheduledThreadPool

          創建一個定長線程池,支持定時、週期性的任務執行。

        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
 
        // 延遲一秒之後,每隔三秒執行一次
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                log.info("scheduled run");
            }
        },1,3,TimeUnit.SECONDS);
 
        // 也可以使用timer的schedule方法來實現定時功能
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                log.info("timer run");
            }
        },new Date(),5*1000);
  • Executors.newSingleThreadExecutor

          創建一個單線程化的線程池,只會用唯一一個工作線程執行任務。

        ExecutorService executorService = Executors.newSingleThreadExecutor();
 
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    log.info("task:{}",index);
                }
            });
        }
 
        executorService.shutdown();

          線程池的相關信息可以看下這個鏈接:線程池相關概念

 

線程的生命週期;狀態是如何轉移的

線程的生命週期:

  • 新建(New Thread)

          當創建一個 Thread 類的一個實例(對象)時,此線程進入新建狀態(未被啓動);

          例如:Thread t1 = new Thread();

  • 就緒(Runnable)

          線程已經被啓動,正在等待被分配給 CPU 時間片,也就是說此時線程正在就緒隊列中排隊等候得到 CPU 資源;

          例如:ti.start();

  • 運行(Running)

          線程獲得 CPU 資源正在執行任務(run() 方法),此時除非此線程自動放棄 CPU 資源或者有優先級更高的線程進入,線程將一直運行到結束。

  • 阻塞(Blocked)

          由於某種原因導致正在運行的線程讓出 CPU 並暫停自己的執行,即進入阻塞狀態。

          正在睡眠:調用 sleep(long t) 方法 可使線程進入睡眠方式。一個睡眠着的線程在指定的時間過去可進入就緒狀態。

          正在等待:調用 wait() 方法(調用 motify() 方法回到就緒狀態)。

          被另一個線程所阻塞:調用 suspend() 方法(調用 resume() 方法恢復)。

  • 死亡(Dead)

          當線程執行完畢或被其它線程殺死,線程就進入死亡狀態,這是線程不可能再進入就緒狀態等待執行。

          自然終止:正常運行 run() 方法後終止;

          異常終止:調用 stop() 方法讓一個線程終止運行。

 

Java中用到的線程調度算法

        搶佔式。一個線程用完CPU之後,操作系統會根據線程優先級、線程飢餓情況等數據算出一個總的優先級並分配下一個時間片給某個線程執行。

       操作系統中可能會出現某條線程常常獲取到CPU控制權的情況,爲了讓某些優先級比較低的線程也能獲取到CPU控制權,可以使用Thread.sleep(0)手動觸發一次操作系統分配時間片的操作,這也是平衡CPU控制權的一種操作。

  • 線程調度器(Thread Schedulder)
    • 負責爲 Runnable 狀態的線程分配CPU時間,一旦創建一個線程並啓動它,它的執行變依賴於線程調度器的實現;
  • 時間分片(Time Slicing)
    • 將可用的CPU時間分配給可用的 Runnable 線程的過程。

 

單例模式的線程安全性

某個類的實例在多線程環境下置灰被創建一次出來。

寫法:

  • 餓漢式單例模式:線程安全
  • 懶漢式單例模式:非線程安全
  • 雙檢鎖單例模式:線程安全

 

線程類的構造方法、靜態塊是被哪個線程調用的?

線程類的構造方法、靜態塊是被 new 這個線程類所在的線程調用的,而 run() 方法裏面的代碼纔是被線程自身所調用的。

 

sleep() 和 wait() 有什麼區別?

相同點:

    兩者都可以用來放棄CPU一定的時間;

不同點:

   如果線程持有某個對象的監視器:

  • sleep() 不會放棄這個對象的監視器;
  • wait() 會放棄這個對象的監視器。

 

多線程的上下文切換

指CPU控制權由一個已經正在運行的線程切換到另一個就緒並等待獲取CPU執行權的線程的過程。

 

 

鎖機制:

什麼是線程安全?如何保證線程安全?

線程安全:是指要控制多個線程對某個資源的有序訪問或修改,而這些線程之間沒有產生衝突。

線程安全問題都是由 全部變量靜態變量 引起的。

造成線程安全問題的主要誘因有兩點:

  • 存在共享數據(也稱臨界資源);
  • 存在多條線程共同操作共享數據。

保證線程安全的方法

  • 競爭與原子操作
  • 同步與鎖
  • 可重入
  • 過度優化

 

重入鎖的概念;重入鎖爲什麼可以防止死鎖?

重入鎖:指的是以線程爲單位,當一個線程獲取對象鎖之後,這個線程可以再次獲取對象上的鎖。而其他的線程是不可以的。

死鎖:如果一個進程集合裏面的每個進程都在等待這個集合中的其他一個進程(包括自身)才能繼續往下執行,若無外力他們將無法推進。這個情況就是死鎖。處於死鎖狀態的進程成爲死鎖進程。

 

產生死鎖的四個條件

  • 互斥條件

進程對所分配到的資源不允許其他進程進行訪問,若其他進程訪問該資源,只能等待,直至佔有該資源的進程使用完成後釋放該資源;

  • 請求和保持條件

進程獲得一定的資源之後,又對其他資源發出請求,但是該資源可能被其他進程佔有,此時請求阻塞,但是又對自己獲得的的資源保持不放;

  • 不可剝奪條件

是指進程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用完成後自己釋放;

  • 環路等待條件

是指進程發生死鎖後,必然存在一個進程 -- 資源之間的環形鏈。

 

如何檢查死鎖

通過 jConsole(JDK 自帶的圖形化界面工具) 檢查死鎖

 

volatile 實現原理

volatile 定義:

Java 編程語言允許線程訪問共享變量,爲了確保共享變量能被準備和一致的更新,線程應該確保通過排它鎖單獨獲得這個變量

  • 禁止指令重排
  • 刷新內存

synchronized 實現原理(對象監視器)

依賴 JVM 實現。

每個對象都有一個監視器鎖(monitor)。當 montior 被佔用時,就會處於鎖定狀態,線程執行 monitorenter 指令時嘗試獲取 monitor 的所有權,過程如下:

  1. 如果 monitor 的進入數爲0,則該線程進入 monitor ,然後將進入數設置爲1,該線程即爲 monitor 所有者;
  2. 如果線程已經佔有該 monitor ,只是重新進入,則進入 monitor 的進入數加1;
  3. 如果其他線程已經佔用了 monitor,則該線程進入阻塞狀態,直到 monitor 的進入數爲0,再重新嘗試獲取 monitor 的所有權。

 

synchronized 與 lock 的區別

類別 synchronized Lock
存在層次 Java的關鍵字,在jvm層面上 是一個類
鎖的釋放 1、以獲取鎖的線程執行完同步代碼,釋放鎖 2、線程執行發生異常,jvm會讓線程釋放鎖 在finally中必須釋放鎖,不然容易造成線程死鎖
鎖的獲取 假設A線程獲得鎖,B線程等待。如果A線程阻塞,B線程會一直等待 分情況而定,Lock有多個鎖獲取的方式,具體下面會說道,大致就是可以嘗試獲得鎖,線程可以不用一直等待
鎖狀態 無法判斷 可以判斷
鎖類型 可重入 不可中斷 非公平 可重入 可判斷 可公平(兩者皆可)
性能 少量同步 大量同步

 

AQS 同步隊列

用來構建鎖或者其他同步組件的基礎框架,它使用了一個 int 成員變量表示同步狀態,通過內置FIFO隊列來完成資源獲取線程的排隊工作。

 

CAS 無鎖的概念;樂觀鎖和悲觀鎖

 

  • 樂觀鎖:

總是認爲不會產生併發問題,每次取數據的時候總認爲不會有其他線程對數據進行修改,因此不會上鎖。但是在更新時會判斷其他線程在這之前有沒有對數據進行修改,一般會使用版本號機制或者 CAS  操作實現;

  • 悲觀鎖:

總是假設最壞的情況,每次取數據時都認爲其他線程會修改,所以都會加鎖(讀鎖、寫鎖、行鎖等),當其他線程想要訪問數據時,都需要阻塞掛起。 synchronized 的思想就屬於悲觀鎖。

 

常見的原子操作類

Java 中的原子操作類

 

什麼是 ABA 問題;出現 ABA 問題 JDK 是如何解決的

ABA 問題:

如果另一個線程修改 V 值,假設值原來是 A,先修改成 B,再修改回成 A,當前線程的 CAS 操作無法分辨當前 V 值是否發生過變化。

如何解決 ABA 問題:

用 AtomicStampedReference 解決 ABA 問題。

 

樂觀鎖的業務場景及實現方式

樂觀鎖(Optimistic Lock): 
每次獲取數據的時候,都不會擔心數據被修改,所以每次獲取數據的時候都不會進行加鎖,但是在更新數據的時候需要判斷該數據是否被別人修改過。如果數據被其他線程修改,則不進行數據更新,如果數據沒有被其他線程修改,則進行數據更新。由於數據沒有進行加鎖,期間該數據可以被其他線程進行讀寫操作。

樂觀鎖:比較適合讀取操作比較頻繁的場景,如果出現大量的寫入操作,數據發生衝突的可能性就會增大,爲了保證數據的一致性,應用層需要不斷的重新獲取數據,這樣會增加大量的查詢操作,降低了系統的吞吐量。

 

Java 8 併發包下常見的併發類

Java 併發包常用類小結

 

偏向鎖、輕量級鎖、重量級鎖、自旋鎖的概念

  • 偏向鎖:

爲了在無多線程競爭的情況下儘量減少不必要的輕量級鎖執行路徑;

  • 輕量級鎖:

爲了在無多線程競爭的前提下,減少傳統的重量級鎖使用操作系統互斥量產生的性能消耗;

  • 重量級鎖:

通過對象內部的監視器(montior)實現,其中 montior 的本質是依賴於底層操作系統的 Mutex Lock 實現,操作系統實現線程之間的切換需要從用戶態到內核態的切換,切換成本非常高;

  • 自旋鎖:

 就是讓該線程等待一段時間,不會被立即掛起,看持有鎖的線程是否很快釋放鎖。

鎖優化相關知識點

 

數據庫:

DDL、DML、DCL 分別指什麼

  • DML(data manipulation language): 數據庫操作語言。就是我們經常用到的 SELECT 、UPDATE 、INSERT 、DELETE 等;
  • DDL (data definition language): 數據庫定義語言。就是我們經常用到的 CREATE 、ALTER 、DROP 等。DDL 主要是用在定義或改變表的結構、數據類型、表之間的鏈接和約束等初始化工作上;
  • DCL (data control language):數據庫控制語言。是用來設置或改變數據庫用戶或角色權限的語句,包括( GRANT 、DENY 、REVOKE 等)語句。

 

explain 命令

explain 命令顯示了 Mysql 如何使用索引來處理 SELECT 語句以及連接表。可以幫助選擇更好的索引和寫出更優化的查詢語句。

使用方法:在 SELECT 語句前加上 explain 就可以了。

explain 列說明:

id select 識別符。這是 select 的查詢序列號

  select_type  

 

select 類型,可以爲一下任何一種:

  • SIMPLE:簡單 slelect(不使用UNION或子查詢)
  • PRIMARY:最外面的 select
  • UNION:UNION 中的第二個或後面的 select 語句
  • DEPENDENT UNION:UNION 中的第二個或後面的 select 語句,取決於外面的查詢
  • UNION RESULT:UNION 的結果
  • SUBQUERY:子查詢中的第一個 select
  • DEPENSENT SUBQUERY:子查詢中的第一個 select,取決於外面的查詢
  • DERIVED:導出表的 select (from 子句的子查詢)
table 輸出的行所引用的表
type

聯接類型。下面給出各個聯接類型,按照從最佳類型到最壞類型進行排序:

  • system:表僅有一行(=系統表)。這是const聯接類型的一個特例
  • const:表最多有一個匹配行,它將在查詢開始時被讀取。因爲僅有一行,在這行的列值可被優化器剩餘部分認爲是常數。const 表很快,因爲它們只讀取一次
  • eq_ref:對於每個來自於前面的表的行組合,從該表中讀取一行。這可能是最好的聯接類型,除了 const 類型
  • ref:對於每個來自於前面的表的行組合,所有有匹配索引值的行將從這張表中讀取
  • ref_or_null:該聯接類型如同 ref ,但是添加了 MySQL 可以專門搜索包含 NULL 值的行
  • index_merge:該聯接類型表示使用了索引合併優化方法
  • unique_subquery:該類型替換了下面形式的 IN 子查詢的ref:value IN (SELECT primary_key FROM single_table WHERE some_expr) unique_subquery 是一個索引查找函數,可以完全替換子查詢
  • index_subquery:該聯接類型類似於 unqiue_subquery 。可以替換 ID 子查詢,但只適合下列形式的子查詢的非唯一索引:value IN(SELECT key_column FROM single_table WHERE some_expr)
  • range:只檢索給定範圍的行,使用一個索引來選擇行
  • index:該聯接類型與 ALL 相同,除了只有索引樹被掃描。這通常比 ALL 快,因爲索引文件通常比數據文件小
  • ALL:對於每個來自於先前的表的行組合,進行完整的表掃描

possible_keys

 

指出 MySQL 能使用哪個索引在該表中找到行

key

 

顯示 MySQL 實際覺得使用的鍵(索引)。如果沒有選擇索引,鍵是 NULL

key_len

 

顯示 MySQL 決定使用的鍵長度。如果鍵是 NULL,則長度爲 NULL
ref 顯示使用哪個列或常數與 key 一起從表中選擇行

rows

 

 顯示 MySQL 認爲它執行查詢時必須檢查的行數。多行之間的數據相乘可以估算要處理的行數
filtered 顯示了通過條件過濾出的行數的百分比估計值
Extra

該列包含 MySQL 解決查詢的詳細信息

  • Disinct:MySQL發現第1個匹配行後,停止爲當前的行組合搜索更多的行
  • Not exists:MySQL能夠對查詢進行LEFT JOIN優化,發現1個匹配LEFT JOIN標準的行後,不再爲前面的的行組合在該表內檢查更多的行
  • range checked for each record (index map: #):MySQL沒有發現好的可以使用的索引,但發現如果來自前面的表的列值已知,可能部分索引可以使用
  • Using filesort:MySQL需要額外的一次傳遞,以找出如何按排序順序檢索行
  • Using index:從只使用索引樹中的信息而不需要進一步搜索讀取實際的行來檢索表中的列信息
  • Using temporary:爲了解決查詢,MySQL需要創建一個臨時表來容納結果
  • Using where:WHERE 子句用於限制哪一個行匹配下一個表或發送到客戶
  • Using sort_union(...), Using union(...), Using intersect(...):這些函數說明如何爲index_merge聯接類型合併索引掃描
  • Using index for group-by:類似於訪問表的Using index方式,Using index for group-by表示MySQL發現了一個索引,可以用來查 詢GROUP BY或DISTINCT查詢的所有列,而不要額外搜索硬盤訪問實際的表

 

數據庫事務 ACID

  • Atomicity(原子性):原子性要求每個事務中的所有操作要麼全部完成,要麼就像全部沒有發生一樣;如果事務中的部分操作失敗了,則整個事務失敗,結果就是數據庫中的狀態保持沒變;
  • Consistency(一致性):一致性確保了任何事務都會使數據庫從一種合法的狀態變爲另一種合法的狀態;
  • Isolation(隔離性):隔離性保證了併發執行多個事務對系統狀態的影響和串行化執行多個事務對系統狀態的影響是一樣的;
  • Durability(持久性):持久性保證了一個事務一旦被提交以後,其狀態就保持不變,甚至是發生了主機斷電、崩潰、錯誤等。

 

髒讀、幻讀、不可重複讀

  • 髒讀:是指一個事務處理過程中讀取了另一個未提交的事務中的數據;

  • 幻讀:是事務非獨立執行時發生的一種現象;

  • 不可重複讀:是指在對於數據庫中的某個數據,一個事務範圍內多次查詢卻返回了不同的數據值,這是由於在查詢間隔,被另一個事務修改並提交了。

幻讀和不可重複讀都是讀取了另一條已經提交的事務(這點就髒讀不同),所不同的是不可重複讀查詢的都是同一個數據項,而幻讀針對的是一批數據整體(比如數據的個數)。

 

事務的隔離級別

  • Serializable(串行化):可避免髒讀、不可重複讀、幻讀的發生;
  • Repeatable read(可重複讀):可避免髒讀、不可重複讀的發生;
  • Read commited(讀已提交):可避免髒讀的發生;
  • Read uncommited(讀未提交):最低級別,任何情況都無法保證。

以上四種級別中,隔離級別最高的是 Serializable ,級別最低的是 Read uncommited 。級別越高,執行效率就越低。

在 Mysql 數據庫默認的隔離級別爲 Repeatable read 級別。

 

數據庫的幾大範式

  1. 第一範式:確保每列的原子性;
  2. 第二範式:確保表中的每列都和主鍵相關,要求每個表值描述一件事情;
  3. 第三範式:確保每列都和主鍵列直接相關,而不是間接相關。

 

說說分庫與分表設計

  • 分表:對於訪問極爲頻繁且數據量巨大的單表來說,我們首先要做的就是減少單表的記錄條數,以便減少數據查詢所需要的時間,提高數據庫的吞吐;
  • 分庫:分表能夠解決單表數據量過大帶來的查詢效率下降的問題,但是卻無法給數據庫的併發處理能力帶來質的提升。所以需要對數據庫進行拆分,從而提高數據庫的寫入能力。

 

分庫與分錶帶來的分佈式困境與對應之策

分庫與分錶帶來的分佈式困境與應對之策

 

說說 SQL 優化之道

  • 減少查詢字段數;
  • 表關聯儘量用主鍵;
  • 查詢條件儘量避免模糊查詢;
  • 避免使用排序字段,排序字段儘量使用主鍵;
  • 儘量使用限制查詢條件;
  • 查詢條件使用有效索引。

 

存儲引擎的 InnoDB 與 MyISAM 區別、優缺點、使用場景

兩者的區別:

  1. InnoDb 支持事務,MyISAM 不支持。這一點是非常之重要。事務是一種高級的處理方式,如在一些列增刪改中只要哪個出錯還可以回滾還原,而 MyISAM 就不可以了;
  2. MyISAM 適合查詢以及插入爲主的應用,InnoDB 適合頻繁修改以及設計到安全性較高的應用;
  3. InnoDB 支持外鍵, MyISAM 不支持;
  4. 從 MySql 5.5 以後,InnoDB  是默認引擎;
  5. InnoDb 不支持 FULLTEXT 類型的索引;
  6. InnoDb 中不保存表的行數,如 select count(*) from table 時,InnoDB 需要掃描一遍整個表來計算有多少行,但是 MyISAM 只要簡單的讀出保存好的行數即可。主要的是,當 count(*) 語句包含 where 條件時 MyISAM 特需要掃描整個表;
  7. 對於自增長的字段,InnoDB 中必須包含只有該字段的索引,但是在 MyISAM 表中可以和其它字段一起建立聯合索引;
  8. 清空整個表時,InnoDb 是一行一行的刪除,效率非常慢。MyISAM 則會重新建表;
  9. InnoDb 支持行鎖(某些情況下還是鎖整表,如 update table set a = 1 where user like '%lee%')。

InnoDB 與 MyISAM 的優缺點:

  • InnoDb :這種類型是事務安全的。
    • 優點:支持事務,支持外鍵,併發量較大,適合大量 update;
    • 缺點:查詢數據相對較慢,不適合大量的 select。
  • MyISAM:
    • 優點:查詢數據相對較快,適合大量的 select ,可以全文索引;
    • 缺點:不支持事務,不支持外鍵,併發量較小,不適合大量 update。

使用場景:

  • 如果你的應用程序一定要使用事務,毫無疑問你要選擇 InnoDb 引擎;
  • 如果你的應用程序對查詢性能要求較高,就要使用 MyISAM 引擎。

 

索引類別(B+樹索引、全文索引、哈希索引);索引的區別

  • FULLTEXT:即爲全文索引。目前只有MyISAM 引擎支持;
  • HASH:即爲哈希索引。HASH 索引可以一次定位,不需要像屬性索引那樣逐層查找,因此具有極高的效率;
  • BTREE:即爲B+樹索引。BTREE 索引就是一種將索引值按一定的算法,存入一個屬性的數據結構中(二叉樹),每次查詢都是從樹的入口 root 開始,依次遍歷 node,獲取 leaf 。這是 MySQL 裏默認和最常用的索引類型。

 

什麼是自適應哈希索引(AHI)

InnoDB 存儲引擎會監控對錶上各索引頁的查詢。如果觀察到建立哈希索引可以帶來速度提升,則建立哈希索引,稱之爲自適應哈希索引(Adaptive Hash Index,AHI)。AHI 是通過緩衝池的 B+ 樹頁構造而來,因此建立的速度很快,而且不需要對整張表構建哈希索引。InnoDB 存儲引擎會自動根據訪問的頻率和模式來自動地爲某些熱點頁建立哈希索引。

 

爲什麼要用 B+tree 作爲 MySql 索引的數據結構

鑑於 BTREE 具有良好的定位特性,其常被用於對檢索時間要求苛刻的場合,例如:

  • BTREE 索引是數據庫中存取和查找文件(稱爲記錄或鍵值)的一種方法;
  • 硬盤中的節點也是 BTREE 結構的。與內存相比,硬盤必須花城北的時間來存取一個數據元素,這是因爲硬盤的機械部件讀取數據的速度遠遠趕不上純電子媒體的內存。與一個節點兩個分支的二元樹相比, BTREE 利用多個分支(稱爲子樹)的節點,減少獲取記錄時所經歷的節點數,從而達到節省存取時間的目的。

 

聚集索引與非聚集索引的區別

快速理解聚集索引和非聚集索引

 

limit 20000 加載很慢怎麼解決

limit n 等價於 limit 0,n

讓 limit 走索引去查詢,例如:order by 索引字段,或者 limit 前面跟 where 條件走索引字段等。

 

常見的幾種分佈式 ID 的設計方案

  • UUID(Universally Unique Identifier):16字節128位,通常以36長度的字符串表示。 
    • 優點:本地生成 ID,不需要進行遠程調用,時延低,性能高;
    • 缺點:
      • UUID 過長,很多場景不適用,比如用 UUID 做數據庫索引字段;
      • 沒有排序,無法保證趨勢遞增。
  • 數據庫自增長序列或字段:最常見的方式,利用數據庫,全數據庫唯一。
    • 優點:
      • 簡單,代碼方便,性能可以接受;
      • 數字 ID 天然排序,對分頁或者需要排序的結果很有幫助。
    • 缺點:
      • 不同數據庫語法和實現不同,數據庫遷移的時候或多數據庫版本支持的時候需要處理;
      • 在單個數據庫或讀寫分離或一主多從的情況下,只有一個主庫可以生成。有單點故障的風險;
      • 在性能達不到要求的情況下,比較難於擴展;
      • 如果遇見多個系統需要合併或者涉及到數據遷移會相當痛苦;
      • 分表分庫的時候會有麻煩。
  • Filcker 方法:主要思路採用了 MySql 自增長 ID 的機制(auto_increment + replace into)。
    • 優點:充分藉助數據庫的自增 ID 機制,可靠性高,生成有序的ID;
    • 缺點:
      • ID 生成性能一來單臺數據庫讀寫性能;
      • 依賴數據庫,當數據庫異常時整個系統不可用。
  • Redis 生成 ID:主要依賴於 Redis 是單線程的,所以也可以用生成全局唯一的 ID 。可以用 Redis 的原子操作 INCR 和 INCRBY 來實現。
    • 優點:
      • 不依賴與數據庫,靈活方便,且性能優於數據庫;
      • 數字 ID 天然排序,對分頁或者需要排序的結果很有幫助。
    • 缺點:
      • 如果系統中沒有 Redis ,還需要引入新的組件,增加系統複雜度;
      • 需要編碼和配置的工作量比較大。
  • snowflake 算法:snowflake 是 Twitter 開源的分佈式 ID 生成算法,結果是一個 long 型的 ID。其核心思想是:使用 41bit 作爲毫秒數,10bit 作爲機器的 ID(5個 bit 是數據中心,5個 bit 是機器 ID),12bit 作爲毫秒內的流水號 (意味着每個節點在每毫秒可以產生4096個 ID),最後還有個符號位,永遠是0。
    • 優點:
      • 不依賴與數據庫,靈活方便,且性能優於數據庫;
      • ID 按照時間在單機上是遞增的。
    • 缺點:在單機上是遞增的,但是由於涉及到分佈式環境,每臺機器上的始終不可能完全同步,也許有時候也會出現不適全局遞增的情況。

JVM

JVM 運行時內存區域劃分

 首先了解下什麼是 JVM 內存:

Java 源代碼文件(.java後綴)會被 Java 編譯器編譯爲字節碼文件(.class後綴),然後由 JVM 中的類加載器加載各個類的字節碼文件,加載完畢之後,交由 JVM 執行引擎執行。在整個程序執行過程中,JVM 會用一段空間來存儲程序執行期間需要用到的數據和相關信息,這段空間一段被稱作爲 Runtime Data Area(運行時數據區),也就是我們常說的 JVM 內存。因此,在 Java 中我們常常說到的內存管理就是針對這塊空間進行管理(如何分配和回收空間)。

根據《Java 虛擬機規範》的規定,運行時數據區通常包括這幾個部分:

  • 程序計數器(Program Counter Register):
    • 指向當前線程所執行的字節碼指令的地址,通常也叫作行號指示器;
  • 虛擬機棧(VM Stack):
    • 描述的是 Java 方法執行的內存模型,方法執行的同時會創建一個棧禎,用於存儲方法中的局部變量表、操作數棧、動態鏈接、方法的出口等信息,每個方法從調用直到執行完成的過程,就對應着一個棧禎在虛擬機棧中入棧到出棧的過程;
  • 本地方法棧(Native Method Stack):
    • 跟虛擬機棧類似。區別在於本地方法棧是爲 Native 方法服務而虛擬機棧是爲 Java 方法服務;
  • 方法區(Method Area):
    • 與堆(Heap)一樣所有線程所共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、及時編譯器編譯後的代碼等數據。
  • 堆(Heap):
    • Java 堆是 Java 虛擬機所管理的內存中最大的一塊。Java 堆是被所有線程所共享的一塊內存區域,虛擬機啓動時創建,幾乎所有對象的實例都存儲在堆中,所有的對象和數組都要在堆上分配內存。

 

常見的 GC 回收算法及其含義

什麼是 GC:

GC 是將 Java 的無用的堆對象進行清理,釋放內存,以免發生內存泄漏。

常見的回收算法:

  • 標記-清除 算法:
    • 標記階段:先通過根節點,標記所有從根節點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象;
    • 清除階段:清除所有未被標記的對象;
  • 複製算法(新生代的 GC):
    • 將原有的內存空間分爲兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的內存中的存活對象複製到未使用的內存塊中,然後清楚正在使用的內存塊中的所有對象;
  • 標記-整理 算法(老年代的 GC):
    • 標記階段:先通過根節點,標記所有從根節點開始的可達對象。因此,未被標記的對象就是未被引用的垃圾對象;
    • 整理階段:將所有的存活對象壓縮到內存的一端;之後,清理邊界外所有的空間;
  • 分帶收集算法:
    • 存活率低:少量對象存活,適合用複製算法:在新生代中,每次 GC 時都發現有大批對象死去,只有少量存活(新生代中98%的對象都是 "朝生夕死"),那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成 GC;
    • 存活率高:大量對象存活,適合用標記-清除/標記-整理:在老年代中,因爲對象存活率高,沒有額外空間對他進行分配擔保,就必須使用 標記-清除/標記-整理 算法進行 GC。

常見的 JVM 性能監控和故障處理工具類

  • jmap:觀察運行中的 JVM 物理內存的佔用情況;
  • jps:列出所有的 JVM 實例;
  • jvmtop:一個輕量級的控制檯程序用來監控機器上運行的所有 java 虛擬機;
  • jstack:觀察 jvm 中當前所有線程的運行情況和線程當前狀態;
  • jstat:JVM 統計監測工具;
  • jconsole:內置 Java 性能分析器。

 

JVM 性能調優

調優的目的:爲了令應用程序使用最小的硬件消耗來承載更大的吞吐。

JVM 調優主要是針對垃圾收集器的收集性能優化,令運行在虛擬機上的應用能夠使用更少的內存以及延遲獲取更大的吞吐量。

JVM 性能調優

類加載器、雙親委派模型

  • 類加載器:
    • 啓動類加載器(BootStrap ClassLoader):負責加載 JAVA_HOME\lib 目錄中的,或通過 -Xbootclasspath 參數指定路徑中的,且被虛擬機認可(按文件名識別,如 rt、jar )的類;
    • 擴展類加載器(Extension ClassLoader):負責加載 JAVA_HOME\lib\ext 目錄中的,或通過 java.ext.dirs 系統變量指定路徑中的類庫;
    • 應用程序類加載器(Application ClassLoader):負責加載用戶路徑(classpath) 上的類庫。
  • 雙親委派模型:
    • 工作過程:如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委託給父類加載器去完成,每一個層次的類加載器都是如此,因此所有的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父類加載器反饋自己無法完成這個加載請求(它的搜索範圍中沒有找到所需要加載的類)時,子加載器纔會嘗試自己去加載。
    • 好處:能夠有效確保一個類的全局唯一性,當程序中出現多個限定名相同的類時,類加載器在執行加載時,始終只會加載其中的某一個類;

 

類加載的過程

  • 加載:
    • 加載是類加載過程中的一個階段,這個階段會在內存中生成一個代表這個類的 java.lang.class 對象,作爲方法區這個類的各種數據的入口。
  • 鏈接:
    • 驗證:這一階段的主要目的是爲了確保 class 文件的字節流中包含的信息是否符合當前虛擬機的要求,並且不會危害虛擬機自身的安全;
    • 準備:準備階段是正式爲類變量分配內存並設置類變量的初始值階段,即在方法區中分配這些變量所使用的內存空間。
    • 解析:解析階段是指虛擬機將常量池中的符號引用替換爲直接引用的過程。
      • 符號引用:與虛擬機實現的佈局無關,引用的目標並不一定要已經加載到內存中。各種虛擬機實現的內存佈局可以各不相同,但是它們能接受的符號引用必須是一直的,因爲符號引用的字面量形式明確定義在 Java 虛擬機規範的 Class 文件格式中;
      • 直接引用:是指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。如果有了直接引用,那引用的目標必定已經在內存中存在。
  • 初始化:
    • 初始化階段是類加載的最後一個階段。前面的類加載階段之後,除了在加載階段可以自定義類加載器以外,其他操作都是由 JVM 主導。到了初始階段,纔開始真正執行類中定義的 Java 程序代碼。

強引用、軟引用、弱引用、虛引用

  • 強引用:只要引用存在,垃圾回收器永遠不會回收;
  • 軟引用:非必須引用,內存溢出之前進行回收;
  • 弱引用:第二次垃圾回收時回收;
  • 虛引用:垃圾回收時回收,無法通過引用取到對象值。

Java 內存模型 JMM

JMM 解決了 可見性和有序性的問題,而鎖解決了原子性的問題。

特性:

  • 原子性:指的是一個操作是不可中斷的,即使是在多線程環境下,一個操作一旦開始就不會被其他線程影響;
  • 可見性:指的是當一個線程修改了某個共享變量的值,其他線程是否能夠馬上得知這個修改的值;
  • 有序性:指的是數據不相關的變量在併發的情況下,實際執行的結果和單線程的執行結果是一樣的,不會因爲重排序的問題導致結果不可預知。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章