Java面試底層原理

面試發現常常有些重複的面試問題,本身也應該學會記錄下來,最好本身能作成筆記,在下一次面的時候說得有條不紊,深刻具體,面試官想必也很開心。如下是我我的總結,請參考:html

HashSet底層原理:(問了大概率跟HashMap一塊兒面)java

HashMap底層原理:(很是大概率問到)linux

Hashtable底層原理:(問的少,問了大概率問你跟HashMap的區別)程序員

synchronized底層如何實現?鎖優化,怎麼優化?面試

ReentrantLock 底層實現;算法

ConcurrentHashMap 的工做原理,底層原理(談到多線程高併發大概率會問它)spring

JVM調優(JVM層層漸進問時大概率問)編程

JVM內存管理,JVM的常見的垃圾收集器,GC調優,Minor GC ,Full GC 觸發條件(像是必考題)數組

java內存模型瀏覽器

線程池的工做原理(談到多線程高併發大概率會問它)

ThreadLocal的底層原理(有時問)

voliate底層原理

NIO底層原理

IOC底層實現原理(Spring IOC ,AOP會問的兩個原理,面試官常常會問看過源碼嗎?因此你有所準備吧)

AOP底層實現原理

MyisAM和innodb的有關索引的疑問(容易混淆,能夠問的會深刻)

HashSet底層原理:(面試過)
http://zhangshixi.iteye.com/blog/673143

http://www.javashuo.com/article/p-memupmkd-bc.html

HashSet實現Set接口,由哈希表(其實是一個HashMap實例)支持。它不保證set 的迭代順序;特別是它不保證該順序恆久不變。此類容許使用null元素。

2.    HashSet的實現:

   對於HashSet而言,它是基於HashMap實現的,HashSet底層使用HashMap來保存全部元素,所以HashSet 的實現比較簡單,相關HashSet的操做,基本上都是直接調用底層HashMap的相關方法來完成, (實際底層會初始化一個空的HashMap,並使用默認初始容量爲16和加載因子0.75。)

HashSet的源代碼

對於HashSet中保存的對象,請注意正確重寫其equals和hashCode方法,以保證放入的對象的惟一性。

插入
當有新值加入時,底層的HashMap會判斷Key值是否存在(HashMap細節請移步深刻理解HashMap),若是不存在,則插入新值,同時這個插入的細節會依照HashMap插入細節;若是存在就不插入

HashMap底層原理:

1.    HashMap概述:

   HashMap是基於哈希表的Map接口的非同步實現。此實現提供全部可選的映射操做,並容許使用null值和null鍵。此類不保證映射的順序,特別是它不保證該順序恆久不變。

2.    HashMap的數據結構:

HashMap其實是一個「數組+鏈表+紅黑樹」的數據結構

3.    HashMap的存取實現:

(1.8以前的)

當咱們往HashMap中put元素的時候,先根據key的hashCode從新計算hash值,根據hash值獲得這個元素在數組中的位置(即下標),若是數組該位置上已經存放有其餘元素了,那麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最早加入的放在鏈尾。若是數組該位置上沒有元素,就直接將該元素放到此數組中的該位置上。

1.8:

put():

  1. 根據key計算獲得key.hash = (h = k.hashCode()) ^ (h >>> 16);

  2. 根據key.hash計算獲得桶數組的索引index = key.hash & (table.length - 1),這樣就找到該key的存放位置了:

① 若是該位置沒有數據,用該數據新生成一個節點保存新數據,返回null;

② 若是該位置有數據是一個紅黑樹,那麼執行相應的插入 / 更新操做

③ 若是該位置有數據是一個鏈表,分兩種狀況一是該鏈表沒有這個節點,另外一個是該鏈表上有這個節點,注意這裏判斷的依據是key.hash是否同樣: 若是該鏈表沒有這個節點,那麼採用尾插法新增節點保存新數據,返回null; 若是該鏈表已經有這個節點了,那麼找到該節點並更新新數據,返回老數據。 注意: HashMap的put會返回key的上一次保存的數據。

get():

計算需獲取數據的hash值(計算過程跟put同樣),計算存放在數組table中的位置(計算過程跟put同樣),而後依次在數組,紅黑樹,鏈表中查找(經過equals()判斷),最後再判斷獲取的數據是否爲空,若爲空返回null不然返回該數據

 

樹化與還原

  • 哈希表的最小樹形化容量

  • 當哈希表中的容量大於這個值時(64),表中的桶才能進行樹形化

  • 不然桶內元素太多時會擴容,而不是樹形化

  • 爲了不進行擴容、樹形化選擇的衝突,這個值不能小於 4 * TREEIFY_THRESHOLD

  • 一個桶的樹化閾值

  • 當桶中元素個數超過這個值時(8),須要使用紅黑樹節點替換鏈表節點

  • 這個值必須爲 8,要否則頻繁轉換效率也不高

  • 一個樹的鏈表還原閾值

  • 當擴容時,桶中元素個數小於這個值(6),就會把樹形的桶元素 還原(切分)爲鏈表結構

  • 這個值應該比上面那個小,至少爲 6,避免頻繁轉換

條件1. 若是當前桶數組爲null或者桶數組的長度 < MIN_TREEIFY_CAPACITY(64),則進行擴容處理(見代碼片斷2:resize());

條件2. 當不知足條件1的時候則將桶中鏈表內的元素轉換成紅黑樹!!!稍後再詳細討論紅黑樹。

 

擴容機制的實現

  1. 擴容(resize)就是從新計算容量。當向HashMap對象裏不停的添加元素,而HashMap對象內部的桶數組沒法裝載更多的元素時,HashMap對象就須要擴大桶數組的長度,以便能裝入更多的元素。

  2. capacity 就是數組的長度/大小,loadFactor 是這個數組填滿程度的最大比比例。 

  3. size表示當前HashMap中已經儲存的Node<key,value>的數量,包括桶數組和鏈表 / 紅黑樹中的的Node<key,value>。

  4. threshold表示擴容的臨界值,若是size大於這個值,則必需調用resize()方法進行擴容。 

  5. 在jdk1.7及之前,threshold = capacity * loadFactor,其中 capacity 爲桶數組的長度。 這裏須要說明一點,默認負載因子0.75是是對空間和時間(縱向橫向)效率的一個平衡選擇,建議你們不要修改。 jdk1.8對threshold值進行了改進,經過一系列位移操做算法最後獲得一個power of two size的值

何時擴容

當向容器添加元素的時候,會判斷當前容器的元素個數,若是大於等於閾值---即當前數組的長度乘以加載因子的值的時候,就要自動擴容啦。

擴容必須知足兩個條件:

一、 存放新值的時候   當前已有元素的個數  (size) 必須大於等於閾值

二、 存放新值的時候當前存放數據發生hash碰撞(當前key計算的hash值換算出來的數組下標位置已經存在值)

//若是計算的哈希位置有值(及hash衝突),且key值同樣,則覆蓋原值value,並返回原值value

      if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

        V oldValue = e.value;

        e.value = value;

        e.recordAccess(this);

        return oldValue;

      }

resize()方法: 該函數有2種使用狀況1.初始化哈希表 2.當前數組容量太小,需擴容

過程:

插入鍵值對時發現容量不足,調用resize()方法方法,

1.首先進行異常狀況的判斷,如是否須要初始化,二是若當前容量》最大值則不擴容,

2.而後根據新容量(是就容量的2倍)新建數組,將舊數組上的數據(鍵值對)轉移到新的數組中,這裏包括:(遍歷舊數組的每一個元素,從新計算每一個數據在數組中的存放位置(原位置或者原位置+舊容量),將舊數組上的每一個數據逐個轉移到新數組中,這裏採用的是尾插法。)

3.新數組table引用到HashMap的table屬性上

4.最後從新設置擴容闕值,此時哈希表table=擴容後(2倍)&轉移了舊數據的新table

synchronized底層如何實現?鎖優化,怎麼優化?

synchronized 是 Java 內建的同步機制,因此也有人稱其爲 Intrinsic Locking,它提供了互斥的語義和可見性,當一個線程已經獲取當前鎖時,其餘試圖獲取的線程只能等待或者阻塞在那裏。

原理:

synchronized能夠保證方法或者代碼塊在運行時,同一時刻只有一個方法能夠進入到臨界區,同時它還能夠保證共享變量的內存可見性

 

底層實現:

同步代碼塊是使用monitorenter和monitorexit指令實現的, ,當且一個monitor被持有以後,他將處於鎖定狀態。線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor全部權,即嘗試獲取對象的鎖; 

 

同步方法(在這看不出來須要看JVM底層實現)依靠的是方法修飾符上的ACC_SYNCHRONIZED實現。  synchronized方法則會被翻譯成普通的方法調用和返回指令如:invokevirtual、areturn指令,在VM字節碼層面並無任何特別的指令來實現被synchronized修飾的方法,而是在Class文件的方法表中將該方法的access_flags字段中的synchronized標誌位置1,表示該方法是同步方法並使用調用該方法的對象或該方法所屬的Class在JVM的內部對象表示 Klass 作爲鎖對象。

 

Java對象頭和monitor是實現synchronized的基礎!

synchronized存放的位置:

synchronized用的鎖是存在Java對象頭裏的。

 

其中, Java對象頭包括: 

Mark Word(標記字段): 用於存儲對象自身的運行時數據, 如哈希碼(HashCode)、GC分代年齡、鎖狀態標誌、線程持有的鎖、偏向線程 ID、偏向時間戳等等。它是實現輕量級鎖和偏向鎖的關鍵

Klass Pointer(類型指針): 是對象指向它的類元數據的指針,虛擬機經過這個指針來肯定這個對象是哪一個類的實例

monitor:  能夠把它理解爲一個同步工具, 它一般被描述爲一個對象。 是線程私有的數據結構

鎖優化,怎麼優化?

jdk1.6對鎖的實現引入了大量的優化。 鎖主要存在四中狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨着競爭的激烈而逐漸升級。 注意鎖能夠升級不可降級,這種策略是爲了提升得到鎖和釋放鎖的效率。 重量級鎖降級發生於STW階段,降級對象爲僅僅能被VMThread訪問而沒有其餘JavaThread訪問的對象。( HotSpot JVM/JRockit JVM是支持鎖降級的)

偏斜鎖:

當沒有競爭出現時,默認會使用偏斜鎖。JVM 會利用 CAS 操做(compare and swap),在對象頭上的 Mark Word 部分設置線程 ID,以表示這個對象偏向於當前線程,因此並不涉及真正的互斥鎖。

自旋鎖:

自旋鎖 for(;;)結合cas確保線程獲取取鎖

就是讓該線程等待一段時間,不會被當即掛起,看持有鎖的線程是否會很快釋放鎖。怎麼等待呢?執行一段無心義的循環便可(自旋)。

輕量級鎖:

引入偏向鎖主要目的是:爲了在無多線程競爭的狀況下儘可能減小沒必要要的輕量級鎖執行路徑。 當關閉偏向鎖功能或者多個線程競爭偏向鎖致使偏向鎖升級爲輕量級鎖,則會嘗試獲取輕量級鎖

重量級鎖:

重量級鎖經過對象內部的監視器(monitor)實現,其中monitor的本質是依賴於底層操做系統的Mutex Lock實現,操做系統實現線程之間的切換須要從用戶態到內核態的切換,切換成本很是高。

ReentrantLock 底層實現

https://blog.csdn.net/u011202334/article/details/73188404

AQS原理:

AQS和Condition各自維護了不一樣的隊列,在使用lock和condition的時候,其實就是兩個隊列的互相移動。若是咱們想自定義一個同步器,能夠實現AQS。它提供了獲取共享鎖和互斥鎖的方式,都是基於對state操做而言的。

 

概念+實現:

ReentrantLock實現了Lock接口,是AQS( 一個用來構建鎖和同步工具的框架, AQS沒有 鎖之 類的概念)的一種。加鎖和解鎖都須要顯式寫出,注意必定要在適當時候unlock。ReentranLock這個是可重入的。其實要弄明白它爲啥可重入的呢,咋實現的呢。其實它內部自定義了同步器Sync,這個又實現了AQS,同時又實現了AOS,然後者就提供了一種互斥鎖持有的方式。其實就是每次獲取鎖的時候,看下當前維護的那個線程和當前請求的線程是否同樣,同樣就可重入了。

 

和synhronized相比:

synchronized相比,ReentrantLock用起來會複雜一些。在基本的加鎖和解鎖上,二者是同樣的,因此無特殊狀況下,推薦使用synchronized。ReentrantLock的優點在於它更靈活、更強大,增長了輪訓、超時、中斷等高級功能。

可重入鎖。可重入鎖是指同一個線程能夠屢次獲取同一把鎖。ReentrantLock和synchronized都是可重入鎖。

可中斷鎖。可中斷鎖是指線程嘗試獲取鎖的過程當中,是否能夠響應中斷。synchronized是不可中斷鎖,而ReentrantLock則z,dz提供了中斷功能。

公平鎖與非公平鎖。公平鎖是指多個線程同時嘗試獲取同一把鎖時,獲取鎖的順序按照線程達到的順序,而非公平鎖則容許線程「插隊」。synchronized是非公平鎖,而ReentrantLock的默認實現是非公平鎖,可是也能夠設置爲公平鎖。

 

lock()和unlock()是怎麼實現的呢?

由lock()和unlock的源碼能夠看到,它們只是分別調用了sync對象的lock()和release(1)方法。而  Sync是ReentrantLock的內部類, 其擴展了AbstractQueuedSynchronizer。

lock():

final void lock() {

if (compareAndSetState(0, 1))

setExclusiveOwnerThread(Thread.currentThread());

else

acquire(1);

}

首先用一個CAS操做,判斷state是不是0(表示當前鎖未被佔用),若是是0則把它置爲1,而且設置當前線程爲該鎖的獨佔線程,表示獲取鎖成功。當多個線程同時嘗試佔用同一個鎖時,CAS操做只能保證一個線程操做成功,剩下的只能乖乖的去排隊啦。( 「非公平」即體如今這裏)。

設置state失敗,走到了else裏面。咱們往下看acquire。

  1. 第一步。嘗試去獲取鎖。若是嘗試獲取鎖成功,方法直接返回。

2. 第二步,入隊。( 自旋+CAS組合來實現非阻塞的原子操做)

3. 第三步,掛起。 讓已經入隊的線程嘗試獲取鎖,若失敗則會被掛起

public final void acquire(int arg) {

if (!tryAcquire(arg) &&

    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

    selfInterrupt();

}

unlock():
流程大體爲先嚐試釋放鎖,若釋放成功,那麼查看頭結點的狀態是否爲SIGNAL,若是是則喚醒頭結點的下個節點關聯的線程,

若是釋放失敗那麼返回false表示解鎖失敗。這裏咱們也發現了,每次都只喚起頭結點的下一個節點關聯的線程。

public void unlock() {

sync.release(1);

}

public final boolean release(int arg) {

if (tryRelease(arg)) {

Node h = head;

if (h != null && h.waitStatus != 0)

unparkSuccessor(h);

return true;

}

return false;

}

ConcurrentHashMap 的工做原理

概念:

ConcurrentHashMap的目標是實現支持高併發、高吞吐量的線程安全的HashMap。

1.8以前:

數據結構:

ConcurrentHashMap是由Segment數組結構和 多個HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap裏扮演鎖的角色,HashEntry則用於存儲鍵值對數據。一個ConcurrentHashMap裏包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構, 一個Segment裏包含一個HashEntry數組,每一個HashEntry是一個鏈表結構的元素, 每一個Segment守護者一個HashEntry數組裏的元素,當對HashEntry數組的數據進行修改時,必須首先得到它對應的Segment鎖。

 

put和get的時候,都是現根據key.hashCode()算出放到哪一個Segment中: ConcurrentHashMap中默認是把segments初始化爲長度爲16的數組

http://www.javashuo.com/article/p-sfmdxgau-bp.html

1.8後:

變化:

ConcurrentHashMap的JDK8與JDK7版本的併發實現相比,最大的區別在於JDK8的鎖粒度更細,理想狀況下talbe數組元素的大小就是其支持併發的最大個數

實現:

改進一:取消segments字段,直接採用transient volatile HashEntry<K,V>[] table保存數據,採用table數組元素做爲鎖,從而實現了對每一行數據進行加鎖,進一步減小併發衝突的機率。

數據結構:

改進二:將原先table數組+單向鏈表的數據結構,變動爲table數組+單向鏈表+紅黑樹的結構。對於hash表來講,最核心的能力在於將key hash以後能均勻的分佈在數組中。

概念:

JDK1.8的實現已經摒棄了Segment的概念,而是直接用Node數組+鏈表+紅黑樹的數據結構來實現,併發控制使用Synchronized和CAS來操做,整個看起來就像是優化過且線程安全的HashMap,雖然在JDK1.8中還能看到Segment的數據結構,可是已經簡化了屬性,只是爲了兼容舊版本。

 

樹化和還原:

與HashMap同樣 。

 

一些成員:

Node是ConcurrentHashMap存儲結構的基本單元,繼承於HashMap中的Entry,用於存儲數據。 ,就是一個鏈表,可是隻容許對數據進行查找,不容許進行修改

經過TreeNode做爲存儲結構代替Node來轉換成黑紅樹。

TreeBin
TreeBin就是封裝TreeNode的容器,它提供轉換黑紅樹的一些條件和鎖的控制

 // 讀寫鎖狀態

    static final int WRITER = 1; // 獲取寫鎖的狀態

    static final int WAITER = 2; // 等待寫鎖的狀態

    static final int READER = 4; // 增長數據時讀鎖的狀態

 

構造器

public ConcurrentHashMap() {

} 初始化實際上是一個空實現, 初始化操做並非在構造函數實現的,而是在put操做中實現。 還提供了其餘的構造函數,有指定容量大小或者指定負載因子,跟HashMap同樣。

 

 

存取實現:

put(): 對當前的table進行無條件自循環直到put成功

  1. 若是沒有初始化就先調用initTable()方法來進行初始化過程

  2. 若是沒有hash衝突就直接CAS插入

  3. 若是還在進行擴容操做就先進行擴容

  4. 若是存在hash衝突,就加鎖來保證線程安全,這裏有兩種狀況,一種是鏈表形式就直接遍歷到尾端插入,一種是紅黑樹就按照紅黑樹結構插入,

  5. 最後一個若是該鏈表的數量大於閾值8,就要先轉換成黑紅樹的結構,break再一次進入循環

  6. 若是添加成功就調用addCount()方法統計size,而且檢查是否須要擴容。

get()

  1. 計算hash值,定位到該table索引位置,若是是首節點符合就返回

  2. 若是遇到擴容的時候,會調用標誌正在擴容節點ForwardingNode的find方法,查找該節點,匹配就返回

  3. 以上都不符合的話,就往下遍歷節點,匹配就返回,不然最後就返回null

歸納版:

(1)對於get讀操做,若是當前節點有數據,還沒遷移完成,此時不影響讀,可以正常進行。 

若是當前鏈表已經遷移完成,那麼頭節點會被設置成fwd節點,此時get線程會幫助擴容。 

(2)對於put/remove寫操做,若是當前鏈表已經遷移完成,那麼頭節點會被設置成fwd節點,此時寫線程會幫助擴容,若是擴容沒有完成,當前鏈表的頭節點會被鎖住,因此寫線程會被阻塞,直到擴容完成。 

 

擴容機制:https://www.e-learn.cn/content/java/1154828

引入了一個ForwardingNode類,在一個線程發起擴容的時候,就會改變sizeCtl這個值,

  1. sizeCtl :默認爲0,用來控制table的初始化和擴容操做,具體應用在後續會體現出來。  

  2. -1 表明table正在初始化  

  3. -N 表示有N-1個線程正在進行擴容操做  。

 

擴容時候會判斷這個值,

若是超過閾值就要擴容,首先根據運算獲得須要遍歷的次數i,而後利用tabAt方法得到i位置的元素f,初始化一個forwardNode實例fwd,若是f == null,則在table中的i位置放入fwd,

不然採用頭插法的方式把當前舊table數組的指定任務範圍的數據給遷移到新的數組中,

而後 

給舊table原位置賦值fwd。直到遍歷過全部的節點之後就完成了複製工做,把table指向nextTable,並更新sizeCtl爲新數組大小的0.75倍 ,擴容完成。在此期間若是其餘線程的有讀寫操做都會判斷head節點是否爲forwardNode節點,若是是就幫助擴容。 

 

 

Hashtable底層原理:

 

概念:

HashTable類繼承自Dictionary類, 實現了Map接口。 大部分的操做都是經過synchronized鎖保護的,是線程安全的, key、value都不能夠爲null, 每次put方法不容許null值,若是發現是null,則直接拋出異常。

官方文檔也說了:若是在非線程安全的狀況下使用,建議使用HashMap替換,若是在線程安全的狀況下使用,建議使用ConcurrentHashMap替換。

 

數據結構:

數組+鏈表。

 

存取實現:

put():

限制了value不能爲null。

因爲直接使用key.hashcode(),而沒有向hashmap同樣先判斷key是否爲null,因此key爲null時,調用key.hashcode()會出錯,因此hashtable中key也不能爲null。

Hashtable是在鏈表的頭部添加元素的。

 int index = (hash & 0x7FFFFFFF) %tab.length;獲取index的方式與HashMap不一樣

 

擴容機制:

Hashtable默認capacity是11,默認負載因子是0.75.。當前表中的Entry數量,若是超過了閾值,就會擴容,即調用rehash方法,從新計算每一個鍵值對的hashCode;

 判斷新的容量是否超過了上限,沒超過就新建一個新數組,大小爲原數組的2倍+1,將舊數的鍵值對從新hash添加到新數組中。

 

 

 

JVM調優

查看堆空間大小分配(年輕代、年老代、持久代分配)

垃圾回收監控(長時間監控回收狀況)

線程信息監控:系統線程數量

線程狀態監控:各個線程都處在什麼樣的狀態下

線程詳細信息:查看線程內部運行狀況,死鎖檢查

CPU熱點:檢查系統哪些方法佔用了大量CPU時間

內存熱點:檢查哪些對象在系統中數量最大

jvm問題排查和調優:

jps主要用來輸出JVM中運行的進程狀態信息。

jstat命令能夠用於持續觀察虛擬機內存中各個分區的使用率以及GC的統計數據

jmap能夠用來查看堆內存的使用詳情。

jstack能夠用來查看Java進程內的線程堆棧信息。 jstack是個很是好用的工具,結合應用日誌能夠迅速定位到問題線程。

 

Java性能分析工具
jdk會自帶JMC(JavaMissionControl)工具。能夠分析本地應用以及鏈接遠程ip使用。提供了實時分析線程、內存,CPU、GC等信息的可視化界面。

 

JVM內存調優

對JVM內存的系統級的調優主要的目的是減小GC的頻率和Full GC的次數。 過多的GC和Full GC是會佔用不少的系統資源(主要是CPU),影響系統的吞吐量。

使用JDK提供的內存查看工具,好比JConsole和Java VisualVM。

致使Full GC通常因爲如下幾種狀況:

舊生代空間不足

調優時儘可能讓對象在新生代GC時被回收、讓對象在新生代多存活一段時間和不要建立過大的對象及數組避免直接在舊生代建立對象

新生代設置太小

 一是新生代GC次數很是頻繁,增大系統消耗;二是致使大對象直接進入舊生代,佔據了舊生代剩餘空間,誘發Full GC

2). 新生代設置過大

一是新生代設置過大會致使舊生代太小(堆總量必定),從而誘發Full GC;二是新生代GC耗時大幅度增長

3). Survivor設置太小

致使對象從eden直接到達舊生代

4). Survivor設置過大

致使eden太小,增長了GC頻率

通常說來新生代佔整個堆1/3比較合適

 

GC策略的設置方式

1). 吞吐量優先 可由-XX:GCTimeRatio=n來設置

2). 暫停時間優先 可由-XX:MaxGCPauseRatio=n來設置

JVM內存管理:

1.先講內存5大模塊以及他們各類的做用。

2.將垃圾收集器,垃圾收集算法

3.適當講講GC優化,JVM優化

 

JVM的常見的垃圾收集器:

(注:此回答源於楊曉峯的Java核心技術36講之一)

 

GC調優:

  • GC日誌分析

  • 調優命令

  • 調優工具

 

調優命令

Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo

  • jps,JVM Process Status Tool,顯示指定系統內全部的HotSpot虛擬機進程。

  • jstat,JVM statistics Monitoring是用於監視虛擬機運行時狀態信息的命令,它能夠顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。

  • jmap,JVM Memory Map命令用於生成heap dump文件

  • jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果後,能夠在瀏覽器中查看

  • jstack,用於生成java虛擬機當前時刻的線程快照。

  • jinfo,JVM Configuration info 這個命令做用是實時查看和調整虛擬機運行參數。

 

調優工具

經常使用調優工具分爲兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。

  • jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中內存,線程和類等的監控

 

GC觸發的條件有兩種。(1)程序調用System.gc時能夠觸發;(2)系統自身來決定GC觸發的時機。

要徹底回收一個對象,至少須要通過兩次標記的過程。

第一次標記:對於一個沒有其餘引用的對象,篩選該對象是否有必要執行finalize()方法,若是沒有執行必要,則意味可直接回收。(篩選依據:是否複寫或執行過finalize()方法;由於finalize方法只能被執行一次)。

第二次標記:若是被篩選斷定位有必要執行,則會放入FQueue隊列,並自動建立一個低優先級的finalize線程來執行釋放操做。若是在一個對象釋放前被其餘對象引用,則該對象會被移除FQueue隊列。

Minor GC ,Full GC 觸發條件

Minor GC觸發條件:當Eden區滿時,觸發Minor GC。

Full GC觸發條件:

(1)調用System.gc時,系統建議執行Full GC,可是沒必要然執行

(2)老年代空間不足

(3)方法區空間不足

(4)經過Minor GC後進入老年代的平均大小大於老年代的可用內存

(5)由Eden區、From Space區向To Space區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小

java內存模型

與JVM 內存模型不一樣。

Java內存模型即Java Memory Model,簡稱JMM。JMM定義了Java 虛擬機(JVM)在計算機內存(RAM)中的工做方式。JVM是整個計算機虛擬模型,因此JMM是隸屬於JVM的。

Java內存模型定義了多線程之間共享變量的可見性以及如何在須要的時候對共享變量進行同步。

Java線程之間的通訊採用的是過共享內存模型,這裏提到的共享內存模型指的就是Java內存模型(簡稱JMM),JMM決定一個線程對共享變量的寫入什麼時候對另外一個線程可見。從抽象的角度來看,JMM定義了線程和主內存之間的抽象關係:線程之間的共享變量存儲在主內存(main memory)中,每一個線程都有一個私有的本地內存(local memory),本地內存中存儲了該線程以讀/寫共享變量的副本。

線程池的工做原理

1.先講下做用

減小資源的開銷    能夠減小每次建立銷燬線程的開銷

 提升響應速度    因爲線程已經建立成功

提升線程的可管理性   

2.講實現

線程池主要有兩部分組成,多個工做線程和一個阻塞隊列。

其中 工做線程是一組已經處在運行中的線程,它們不斷地向阻塞隊列中領取任務執行。而 阻塞隊列用於存儲工做線程來不及處理的任務。

3.細分講下線程的組成

建立一個線程池須要要的一些核心參數。

corePoolSize:基本線程數量 它表示你但願線程池達到的一個值。線程池會盡可能把實際線程數量保持在這個值上下。 

maximumPoolSize:最大線程數量 這是線程數量的上界。 若是實際線程數量達到這個值: 阻塞隊列未滿:任務存入阻塞隊列等待執行 阻塞隊列已滿:調用飽和策略 。

keepAliveTime:空閒線程的存活時間 當實際線程數量超過corePoolSize時,若線程空閒的時間超過該值,就會被中止。 PS:當任務不少,且任務執行時間很短的狀況下,能夠將該值調大,提升線程利用率。 

timeUnit:keepAliveTime的單位

runnableTaskQueue:任務隊列 

這是一個存聽任務的阻塞隊列,能夠有以下幾種選擇:

ArrayBlockingQueue 它是一個由數組實現的阻塞隊列,FIFO。 

LinkedBlockingQueue 它是一個由鏈表實現的阻塞隊列,FIFO。 吞吐量一般要高於ArrayBlockingQueue。fixedThreadPool使用的阻塞隊列就是它。 它是一個無界隊列。 

SynchronousQueue 它是一個沒有存儲空間的阻塞隊列,任務提交給它以後必需要交給一條工做線程處理;若是當前沒有空閒的工做線程,則當即建立一條新的工做線程。 cachedThreadPool用的阻塞隊列就是它。 它是一個無界隊列。 PriorityBlockingQueue 它是一個優先權阻塞隊列。

handler:飽和策略 當實際線程數達到maximumPoolSize,而且阻塞隊列已滿時,就會調用飽和策略。

AbortPolicy 默認。直接拋異常。 CallerRunsPolicy 只用調用者所在的線程執行任務。 DiscardOldestPolicy 丟棄任務隊列中最久的任務。 DiscardPolicy 丟棄當前任務。

4.運行機制

當有請求到來時: 

1.若當前實際線程數量 少於 corePoolSize,即便有空閒線程,也會建立一個新的工做線程;

2 若當前實際線程數量處於corePoolSize和maximumPoolSize之間,而且阻塞隊列沒滿,則任務將被放入阻塞隊列中等待執行; 

3.若當前實際線程數量 小於 maximumPoolSize,但阻塞隊列已滿,則直接建立新線程處理任務; 

4.若當前實際線程數量已經達到maximumPoolSize,而且阻塞隊列已滿,則使用飽和策略。

ThreadLocal的底層原理

歸納:

該類提供了線程局部 (thread-local) 變量。這些變量不一樣於它們的普通對應物,由於訪問某個變量(經過其 get 或 set 方法)的每一個線程都有本身的局部變量

使用:

set(obj):向當前線程中存儲數據 get():獲取當前線程中的數據 remove():刪除當前線程中的數據

 

實現原理:

ThreadLocal並不維護ThreadLocalMap(ThreadLocalMap是Thread的)並非一個存儲數據的容器,它只是至關於一個工具包,提供了操做該容器的方法,如get、set、remove等。而ThreadLocal內部類ThreadLocalMap纔是存儲數據的容器,而且該容器由Thread維護。 每個Thread對象均含有一個ThreadLocalMap類型的成員變量threadLocals,它存儲本線程中全部ThreadLocal對象及其對應的值( ThreadLocalMap 是個弱引用類,內部 一個Entry由ThreadLocal對象和Object構成,

爲何要用弱引用呢?

若是是直接new一個對象的話,使用完以後設置爲null後才能被垃圾收集器清理,若是爲弱引用,使用完後垃圾收集器自動清理key,程序員不用再關注指針。

 

操做細節

進行set,get等操做都是首先會獲取當前線程對象,而後獲取當前線程的ThreadLocalMap對象。再以當前ThreadLocal對象爲key ,再作相應的處理。

內存泄露問題

在ThreadLocalMap中,只有key是弱引用,value仍然是一個強引用。

每次操做set、get、remove操做時,ThreadLocal都會將key爲null的Entry刪除,從而避免內存泄漏。

固然,當 若是一個線程運行週期較長,並且將一個大對象放入LocalThreadMap後便再也不調用set、get、remove方法,此時該仍然可能會致使內存泄漏。 這個問題確實存在,沒辦法經過ThreadLocal解決,而是須要程序員在完成ThreadLocal的使用後要養成手動調用remove的習慣,從而避免內存泄漏。

使用場景;

Web系統Session的存儲 

當請求到來時,能夠將當前Session信息存儲在ThreadLocal中,在請求處理過程當中能夠隨時使用Session信息,每一個請求之間的Session信息互不影響。當請求處理完成後經過remove方法將當前Session信息清除便可。 

voliate 的實現原理

爲何volatile能保證共享變量的內存可見性?

volatile變量寫 

當被volatile修飾的變量進行寫操做時,這個變量將會被直接寫入共享內存,而非線程的專屬存儲空間。 

volatile變量讀 

當讀取一個被volatile修飾的變量時,會直接從共享內存中讀,而非線程專屬的存儲空間中讀。

禁止指令重排序

volatile讀 

若volatile讀操做的前一行爲volatile讀/寫,則這兩行不會發生重排序 volatile讀操做和它後一行代碼都不會發生重排序 

volatile寫 

volatile寫操做和它前一行代碼都不會發生重排序; 若volatile寫操做的後一行代碼爲volatile讀/寫,則這兩行不會發生重排序。

當volatile變量寫後,線程中本地內存中共享變量就會置爲失效的狀態,所以線程B再須要讀取從主內存中去讀取該變量的最新值。

 

NIO底層原理

1概念:

NIO 指新IO,核心是 同步非阻塞,解決傳統IO的阻塞問題。操做對象是Buffer。 其實NIO的核心是IO線程池,(必定要記住這個關鍵點)。 NIO中的IO多路複用調用系統級別的select和poll模型,由系統進行監控IO狀態,避免用戶線程經過反覆嘗試的方式查詢狀態。

  • Java NIO : 同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的鏈接請求都會註冊到多路複用器上,多路複用器輪詢到鏈接有I/O請求時才啓動一個線程進行處理。

2.工做原理:

  1. 由一個專門的線程來處理全部的 IO 事件,並負責分發。

  2. 事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。

  3. 線程通信:線程之間經過 wait,notify 等方式通信。保證每次上下文切換都是有意義的。減小無謂的線程切換。

 

3.通訊模型是怎麼實現的呢?

java NIO採用了雙向通道(channel)進行數據傳輸,而不是單向的流(stream),在通道上能夠註冊咱們感興趣的事件。

四種事件

服務端接收客戶端鏈接事件SelectionKey.OP_ACCEPT(16)

客戶端鏈接服務端事件SelectionKey.OP_CONNECT(8)

讀事件SelectionKey.OP_READ(1)

寫事件SelectionKey.OP_WRITE(4)

服務端和客戶端各自維護一個管理通道的對象,咱們稱之爲selector,該對象能檢測一個或多個通道 (channel) 上的事件。咱們以服務端爲例,若是服務端的selector上註冊了讀事件,某時刻客戶端給服務端發送了一些數據,阻塞I/O這時會調用read()方法阻塞地讀取數據,而NIO的服務端會在selector中添加一個讀事件。服務端的處理線程會輪詢地訪問selector,若是訪問selector時發現有感興趣的事件到達,則處理這些事件,若是沒有感興趣的事件到達,則處理線程會一直阻塞直到感興趣的事件到達爲止。

 

IOC底層實現原理

概念:

IOC 是面向對象編程中的一種設計原則,IOC理論提出的觀點大致是這樣的:藉助於「第三方」實現具備依賴關係的對象之間的解耦。 。所謂IoC,對於spring框架來講,就是由spring來負責控制對象的生命週期和對象間的關係。 是說建立對象的控制權進行轉移,之前建立對象的主動權和建立時機是由本身把控的,而如今這種權力轉移到第三方。

實現原理:

它是經過反射機制+工廠模式實現的,在實例化一個類時,它經過反射調用類中set方法將事先保存在HashMap中的類屬性注入到類中。

控制反轉就是:得到依賴對象的方式反轉了。

 

一、依賴注入發生的時間

(1).用戶第一次經過getBean方法向IoC容索要Bean時,IoC容器觸發依賴注入。

(2).當用戶在Bean定義資源中爲 元素配置了lazy-init屬性,即讓容器在解析註冊Bean定義時進行預實例化,觸發依賴注入。

2.依賴注入實如今如下兩個方法中:

(1).createBeanInstance:生成Bean所包含的java對象實例。

(2).populateBean :對Bean屬性的依賴注入進行處理。

 

 

AOP底層實現原理

概念

AOP(Aspect-OrientedProgramming,面向方面編程),能夠說是OOP(Object-Oriented Programing,面向對象編程)的補充和完善。OOP引入封裝、繼承和多態性等概念來創建一種對象層次結構,用以模擬公共行爲的一個集合。  而AOP技術則偏偏相反,它利用一種稱爲「橫切」的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其名爲「Aspect」,即方面。 簡單地說,就是將那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊間的耦合度,並有利於將來的可操做性和可維護性。 

AOP的核心思想就是「將應用程序中的商業邏輯同對其提供支持的通用服務進行分離。

 

AOP的實現

實現AOP的技術,主要分爲兩大類:一是採用動態代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行爲的執行;二是採用靜態織入的方式,引入特定的語法建立「方面」,從而使得編譯器能夠在編譯期間織入有關「方面」的代碼。

如何使用Spring AOP 

能夠經過配置文件或者編程的方式來使用Spring AOP。   配置能夠經過xml文件來進行,大概有四種方式: 

  1.        配置ProxyFactoryBean,顯式地設置advisors, advice, target等 

2.        配置AutoProxyCreator,這種方式下,仍是如之前同樣使用定義的bean,可是從容器中得到的其實已是代理對象 3.        經過 來配置 

4.        通 過來配置,使用AspectJ的註解來標識通知及切入點

Spring AOP的實現

如何生成代理類:

Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪一種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定。默認的策略是若是目標類是接口,則使用JDK動態代理技術,不然使用Cglib來生成代理 

切面是如何織入的?

InvocationHandler是JDK動態代理的核心,生成的代理對象的方法調用都會委託到InvocationHandler.invoke()方法。

 

MyisAM和innodb的有關索引的疑問

二者都是什麼索引?彙集仍是非彙集https://www.cnblogs.com/olinux/p/5217186.html

MyISAM( 非彙集)

使用B+Tree做爲索引結構,葉節點的data域存放的是數據記錄的地址。

MyISAM中索引檢索的算法爲首先按照B+Tree搜索算法搜索索引,若是指定的Key存在,則取出其data域的值,而後以data域的值爲地址,讀取相應數據記錄。

InnoDB( 彙集索引)

第一個重大區別是InnoDB的數據文件自己就是索引文件, 這棵樹的葉節點data域保存了完整的數據記錄。

可是輔助索引搜索須要檢索兩遍索引:首先檢索輔助索引得到主鍵,而後用主鍵到主索引中檢索得到記錄。

 

由於InnoDB的數據文件自己要按主鍵彙集,因此InnoDB要求表必須有主鍵(MyISAM能夠沒有),若是沒有顯式指定,則MySQL系統會自動選擇一個能夠惟一標識數據記錄的列做爲主鍵,若是不存在這種列,則MySQL自動爲InnoDB表生成一個隱含字段做爲主鍵,這個字段長度爲6個字節,類型爲長整形。

簡單說:

若是咱們定義了主鍵(PRIMARY KEY),那麼InnoDB會選擇其做爲彙集索引;若是沒有顯式定義主鍵,則InnoDB會選擇第一個不包含有NULL值的惟一索引做爲主鍵索引;

做者:睶先生
來源:CSDN
原文:http://www.javashuo.com/article/p-ocublqxy-n.html 版權聲明:本文爲博主原創文章,轉載請附上博文連接!

相關文章
相關標籤/搜索