前言:java
分享 Java高級工程師面試阿里,阿里雲,天貓,菜鳥,涉及到的知識點,文章有點長,但比較全面,閱讀時間15分鐘左右,乾貨滿滿。面試
1.一、HashMap的實現原理redis
1.1.一、結構算法
HashMap其實是一個「鏈表散列」的數據結構,即數組和鏈表的結合體,HashMap底層就是一個數組結構,數組中的每一項又是一個鏈表。以下圖所示:spring
image.pngsql
當新建一個HashMap的時候,就會初始化一個數組。哈希表是由數組+鏈表組成的,一個長度爲16的數組中,每一個元素存儲的是一個鏈表的頭結點。這些元素通常狀況是經過hash(key)%len的規則存儲到數組中,也就是元素的key的哈希值對數組長度取模獲得。數據庫
1.1.二、核心變量編程
image.png數組
1.1.三、put存儲邏輯瀏覽器
當咱們往HashMap中put元素的時候,先根據key的hashCode從新計算hash值,根據hash值獲得這個元素在數組中的位置(即下標), 若是數組該位置上已經存放有其餘元素了,那麼在這個位置上的元素將以鏈表的形式存放,新加入的放在鏈頭,最早加入的放在鏈尾。若是數組該位置上沒有元素,就直接將該元素放到此數組中的該位置上。
這裏有一個特殊的地方。在JDK1.6中,HashMap採用位桶+鏈表實現,即便用鏈表處理衝突,同一hash值的鏈表都存儲在一個鏈表裏。可是當位於一個桶中的元素較多,即hash值相等的元素較多時,經過key值依次查找的效率較低。而JDK1.8中,HashMap採用位桶+鏈表+紅黑樹實現,當鏈表長度超過閾值(8)時,將鏈表轉換爲紅黑樹,這樣大大減小了查找時間。
紅黑樹
紅黑樹和平衡二叉樹(AVL樹)相似,都是在進行插入和刪除操做時經過特定操做保持二叉查找樹的平衡,從而得到較高的查找性能。
紅黑樹和AVL樹的區別在於它使用顏色來標識結點的高度,它所追求的是局部平衡而不是AVL樹中的很是嚴格的平衡。
1.1.四、get讀取邏輯
從HashMap中get元素時,首先計算key的hashCode,找到數組中對應位置的某一元素,而後經過key的equals方法在對應位置的鏈表中找到須要的元素。
若是第一個節點是TreeNode,說明採用的是數組+紅黑樹結構處理衝突,遍歷紅黑樹,獲得節點值。
1.1.五、概括
簡單地說,HashMap 在底層將 key-value 當成一個總體進行處理,這個總體就是一個 Node 對象。HashMap 底層採用一個 Node<K,V>[] 數組來保存全部的 key-value 對,當須要存儲一個 Node 對象時,會根據hash算法來決定其在數組中的存儲位置,在根據equals方法決定其在該數組位置上的鏈表中的存儲位置;當須要取出一個Entry時,也會根據hash算法找到其在數組中的存儲位置,再根據equals方法從該位置上的鏈表中取出該Node。
1.1.六、HashMap的resize(rehash)
當HashMap中的元素愈來愈多的時候,hash衝突的概率也就愈來愈高,由於數組的長度是固定的。因此爲了提升查詢的效率,就要對HashMap的數組進行擴容,在對HashMap數組進行擴容時,就會出現性能問題:原數組中的數據必須從新計算其在新數組中的位置,並放進去,這就是resize。
那麼HashMap何時進行擴容呢?當HashMap中的元素個數超過數組大小loadFactor時,就會進行數組擴容,loadFactor的默認值爲0.75,這是一個折中的取值。也就是說,默認狀況下,數組大小爲16,那麼當HashMap中元素個數超過160.75=12的時候,就把數組的大小擴展爲 216=32,即擴大一倍,而後從新計算每一個元素在數組中的位置,而這是一個很是消耗性能的操做,因此若是咱們已經預知HashMap中元素的個數,那麼預設元素的個數可以有效的提升HashMap的性能。
1.二、HashMap在併發場景下的問題和解決方案
1.2.一、多線程put後可能致使get死循環
問題緣由就是HashMap是非線程安全的,多個線程put的時候形成了某個key值Entry key List的死循環,問題就這麼產生了。
當另一個線程get 這個Entry List 死循環的key的時候,這個get也會一直執行。最後結果是愈來愈多的線程死循環,最後致使服務器dang掉。咱們通常認爲HashMap重複插入某個值的時候,會覆蓋以前的值,這個沒錯。可是對於多線程訪問的時候,因爲其內部實現機制(在多線程環境且未做同步的狀況下,對同一個HashMap作put操做可能致使兩個或以上線程同時作rehash動做,就可能致使循環鍵表出現,一旦出現線程將沒法終止,持續佔用CPU,致使CPU使用率居高不下),就可能出現安全問題了。
爲HashMap以鏈表組形式存在,初始數組16位(爲什麼16位,又是一堆移位算法,下一篇文章再寫吧),若是長度超過75%,長度增長一倍,多線程操做的時候,恰巧兩個線程插入的時候都須要擴容,造成了兩個鏈表,這時候讀取,size不同,報錯了。其實這時候報錯都是好事,至少不會陷入死循環讓cpu死了,有種狀況,假如兩個線程在讀,還有個線程在寫,恰巧擴容了,這時候你死都不知道咋死的,直接就是死循環,假如你是雙核cpu,cpu佔用率就是50%,兩個線程恰巧都進入死循環了,得!中獎了。
1.2.二、多線程put的時候可能致使元素丟失
主要問題出在addEntry方法的new Entry (hash, key, value, e),若是兩個線程都同時取得了e,則他們下一個元素都是e,而後賦值給table元素的時候有一個成功有一個丟失。
1.2.三、解決方案
ConcurrentHashMap替換HashMap
Collections.synchronizedMap將HashMap包裝起來
1.三、ConcurrentHashMap PK HashTable
ConcurrentHashMap具體是怎麼實現線程安全的呢,確定不多是每一個方法加synchronized,那樣就變成了HashTable。
從ConcurrentHashMap代碼中能夠看出,它引入了一個「分段鎖」的概念,具體能夠理解爲把一個大的Map拆分紅N個小的HashTable,根據key.hashCode()來決定把key放到哪一個HashTable中。
以空間換時間的結構,跟分佈式緩存結構有點像,建立的時候,內存直接分爲了16個segment,每一個segment實際上仍是存儲的哈希表(Segment其實就是一個HashMap ),寫入的時候,先找到對應的segment,而後鎖這個segment,寫完,解鎖,鎖segment的時候,其餘segment還能夠繼續工做。
ConcurrentHashMap如此的設計,優點主要在於: 每一個segment的讀寫是高度自治的,segment之間互不影響。這稱之爲「鎖分段技術」;
2.一、線程的各個狀態及切換
Java中的線程的生命週期大致可分爲5種狀態:新建、可運行、運行、阻塞、死亡。
一、新建(NEW):新建立了一個線程對象。
二、可運行(RUNNABLE):線程對象建立後,其餘線程(好比main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取cpu 的使用權 。
三、運行(RUNNING):可運行狀態(runnable)的線程得到了cpu 時間片(timeslice) ,執行程序代碼。
四、阻塞(BLOCKED):阻塞狀態是指線程由於某種緣由放棄了cpu 使用權,也即讓出了cpu timeslice,暫時中止運行。直到線程進入可運行(runnable)狀態,纔有機會再次得到cpu timeslice 轉到運行(running)狀態。
阻塞的狀況分三種:
1)等待阻塞:運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。
2)同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。
3)其餘阻塞:運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程從新轉入可運行(runnable)狀態。
五、死亡(DEAD):線程run()、main() 方法執行結束,或者因異常退出了run()方法,則該線程結束生命週期。死亡的線程不可再次復生。
幾個方法的比較:
1)Thread.sleep(long millis),必定是當前線程調用此方法,當前線程進入阻塞,但不釋放對象鎖,millis後線程自動甦醒進入可運行狀態。做用:給其它線程執行機會的最佳方式。
2)Thread.yield(),必定是當前線程調用此方法,當前線程放棄獲取的cpu時間片,由運行狀態變會可運行狀態,讓OS再次選擇線程。做用:讓相同優先級的線程輪流執行,但並不保證必定會輪流執行。實際中沒法保證yield()達到讓步目的,由於讓步的線程還有可能被線程調度程序再次選中。Thread.yield()不會致使阻塞。
3)t.join()/t.join(long millis),當前線程裏調用其它線程1的join方法,當前線程阻塞,但不釋放對象鎖,直到線程1執行完畢或者millis時間到,當前線程進入可運行狀態。
4)obj.wait(),當前線程調用對象的wait()方法,當前線程釋放對象鎖,進入等待隊列。依靠notify()/notifyAll()喚醒或者wait(long timeout)timeout時間到自動喚醒。
5)obj.notify(),喚醒在此對象監視器上等待的單個線程,選擇是任意性的。notifyAll()喚醒在此對象監視器上等待的全部線程。
2.二、多線程的實現方式Thread、Runnable、Callable
繼承Thread類,實現Runnable接口,實現Callable接口。
這三種方法的介紹和比較:
一、實現Runnable接口相比繼承Thread類有以下優點:
1)能夠避免因爲Java的單繼承特性而帶來的侷限
2)加強程序的健壯性,代碼可以被多個線程共享,代碼與數據是獨立的
3)適合多個相同程序代碼的線程去處理同一資源的狀況
二、實現Runnable接口和實現Callable接口的區別
1)Runnable是自從java1.1就有了,而Callable是1.5以後才加上去的
2)實現Callable接口的任務線程能返回執行結果,而實現Runnable接口的任務線程不能返回結果
3)Callable接口的call()方法容許拋出異常,而Runnable接口的run()方法的異常只能在內部消化,不能繼續上拋
4)加入線程池運行,Runnable使用ExecutorService的execute方法,Callable使用submit方法
注:Callable接口支持返回執行結果,此時須要調用FutureTask.get()方法實現,此方法會阻塞主線程直到獲取返回結果,當不調用此方法時,主線程不會阻塞
2.三、線程池原理和運行機制
java.uitl.concurrent.ThreadPoolExecutor類是線程池中最核心的一個類。
在ThreadPoolExecutor類中提供了四個構造方法,主要參數包括下面的參數:
一、int corePoolSize:核心池的大小。
線程池的基本大小,即在沒有任務須要執行的時候線程池的大小,而且只有在工做隊列滿了的狀況下才會建立超出這個數量的線程。這裏須要注意的是:在剛剛建立ThreadPoolExecutor的時候,線程並不會當即啓動,而是要等到有任務提交時纔會啓動,除非調用了prestartCoreThread/prestartAllCoreThreads事先啓動核心線程。再考慮到keepAliveTime和allowCoreThreadTimeOut超時參數的影響,因此沒有任務須要執行的時候,線程池的大小不必定是corePoolSize。
二、int maximumPoolSize:線程池最大線程數,它表示在線程池中最多能建立多少個線程,注意與corePoolSize區分。
線程池中容許的最大線程數,線程池中的當前線程數目不會超過該值。若是隊列中任務已滿,而且當前線程個數小於maximumPoolSize,那麼會建立新的線程來執行任務。
三、long keepAliveTime:表示線程沒有任務執行時最多保持多久時間會終止。
四、TimeUnit unit:參數keepAliveTime的時間單位,有7種取值,在TimeUnit類中有7種靜態屬性。
五、BlockingQueue<Runnable> workQueue:一個阻塞隊列,用來存儲等待執行的任務。
六、ThreadFactory threadFactory:線程工廠,主要用來建立線程。
七、RejectedExecutionHandler handler:表示當拒絕處理任務時的策略。
還有一個成員變量比較重要:poolSize
線程池中當前線程的數量,當該值爲0的時候,意味着沒有任何線程,線程池會終止。同一時刻,poolSize不會超過maximumPoolSize。
2.四、線程池對任務的處理
一、若是當前線程池中的線程數目小於corePoolSize,則每來一個任務,就會建立一個線程去執行這個任務;
二、若是當前線程池中的線程數目>=corePoolSize,則每來一個任務,會嘗試將其添加到任務緩存隊列當中,若添加成功,則該任務會等待空閒線程將其取出去執行;若添加失敗(通常來講是任務緩存隊列已滿),則會嘗試建立新的線程去執行這個任務(maximumPoolSize);
三、若是當前線程池中的線程數目達到maximumPoolSize(此時線程池的任務緩存隊列已滿),則會採起任務拒絕策略進行處理;
任務拒絕策略,一般有如下四種策略:
1)ThreadPoolExecutor.AbortPolicy:丟棄任務並拋出RejectedExecutionException異常。
2)ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,可是不拋出異常。
3)ThreadPoolExecutor.DiscardOldestPolicy:丟棄隊列最前面的任務,而後從新嘗試執行任務(重複此過程)
4)ThreadPoolExecutor.CallerRunsPolicy:由調用線程處理該任務
四、若是線程池中的線程數量大於 corePoolSize時,若是某線程空閒時間超過keepAliveTime,線程將被終止,直至線程池中的線程數目不大於corePoolSize;若是容許爲核心池中的線程設置存活時間,那麼核心池中的線程空閒時間超過keepAliveTime,線程也會被終止。
2.五、線程池的狀態
一、線程池的狀態說明:
RUNNING(運行):接受新任務並處理排隊任務。
SHUTDOWN(關閉):不接受新任務,會處理隊列任務。
STOP(中止):不接受新任務,不處理隊列任務,而且中斷進程中的任務。
TIDYING(整理):全部任務都已終止,工做計數爲零,線程將執行terminated()方法終止線程。
TERMINATED(已終止):terminated()方法已完成。
二、各個狀態之間的轉換
RUNNING -> SHUTDOWN:調用shutdown()方法。
RUNNING or SHUTDOWN-> STOP:調用shutdownNow()方法。
SHUTDOWN -> TIDYING:當隊列和池均都爲空時。
STOP -> TIDYING:當池爲空時。
TIDYING -> TERMINATED:當terminated()方法已完成。
3.一、JVM的結構
每一個JVM都包含:方法區、Java堆、Java棧、本地方法棧、程序計數器等。
image.png
一、方法區:共享
各個線程共享的區域,存放類信息、常量、靜態變量。
二、Java堆:共享
也是線程共享的區域,咱們的類的實例就放在這個區域,能夠想象你的一個系統會產生不少實例,所以Java堆的空間也是最大的。若是Java堆空間不足了,程序會拋出OutOfMemoryError異常。
三、Java棧:私有
每一個線程私有的區域,它的生命週期與線程相同,一個線程對應一個Java棧,每執行一個方法就會往棧中壓入一個元素,這個元素叫「棧幀」,而棧幀中包括了方法中的局部變量、用於存放中間狀態值的操做棧。若是Java棧空間不足了,程序會拋出StackOverflowError異常,想想什麼狀況下會容易產生這個錯誤,對,遞歸,遞歸若是深度很深,就會執行大量的方法,方法越多Java棧的佔用空間越大。
四、本地方法棧:私有
角色和Java棧相似只不過它是用來表示執行本地方法的,本地方法棧存放的方法調用本地方法接口,最終調用本地方法庫,實現與操做系統、硬件交互的目的。
五、程序計數器:私有
說到這裏咱們的類已經加載了,實例對象、方法、靜態變量都去了本身該去的地方,那麼問題來了,程序該怎麼執行,哪一個方法先執行,哪一個方法後執行,這些指令執行的順序就是程序計數器在管,它的做用就是控制程序指令的執行順序。
六、執行引擎固然就是根據PC寄存器調配的指令順序,依次執行程序指令。
3.二、Java堆的介紹及典型的垃圾回收算法介紹
3.2.一、Java堆的介紹
Java堆是虛擬機管理的最大的一塊內存,堆上的全部線程共享一塊內存區域,在啓動虛擬機時建立。此內存惟一目的就是存放對象實例,幾乎全部對象實例都在這裏分配,這一點Java虛擬機規範中的描述是:全部對象實例及數組都要在堆上分配。
Java堆是垃圾收集器管理的主要區域,也被稱爲「GC堆」,因爲如今收集器基本都採用分代收集算法,因此Java堆中還能夠分爲:新生代和老年代。
堆的內存模型大體爲:
image.png
默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值爲 1:2 ( 該值能夠經過參數 –XX:NewRatio 來指定 ),即:新生代 ( Young ) = 1/3 的堆空間大小,老年代 ( Old ) = 2/3 的堆空間大小。
其中,新生代 ( Young ) 被細分爲 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名爲 from 和 to以示區分。 默認的,Edem : from : to = 8 : 1 : 1 ( 能夠經過參數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來爲對象服務,因此不管何時,老是有一塊 Survivor 區域是空閒着的。 所以,新生代實際可用的內存空間爲 9/10 ( 即90% )的新生代空間。
新生代是 GC 收集垃圾的頻繁區域。
當對象在 Eden ( 包括一個 Survivor 區域,這裏假設是 from 區域 ) 出生後,在通過一次 Minor GC 後,若是對象還存活,而且可以被另一塊 Survivor 區域所容納 ( 上面已經假設爲 from 區域,這裏應爲 to 區域,即 to 區域有足夠的內存空間來存儲 Eden 和 from 區域中存活的對象 ),則使用複製算法將這些仍然還存活的對象複製到另一塊 Survivor 區域 ( 即 to 區域 ) 中,而後清理所使用過的 Eden 以及 Survivor 區域 ( 即 from 區域 ),而且將這些對象的年齡設置爲1,之後對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,能夠經過參數 -XX:MaxTenuringThreshold 來設定 ),這些對象就會成爲老年代。
但這也不是必定的,對於一些較大的對象 ( 即須要分配一塊較大的連續內存空間 ) 則是直接進入到老年代。
From Survivor區域與To Survivor區域是交替切換空間,在同一時間內二者中只有一個不爲空。
3.2.二、如何肯定某個對象是可回收的(垃圾)
一、引用計數法
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器就加1;當引用失效時,計數器值就減1;任什麼時候刻計數器都爲0的對象就是不可能再被使用的。
這種方式的問題是沒法解決循環引用的問題,當兩個對象循環引用時,就算把兩個對象都設置爲null,由於他們的引用計數都不爲0,這就會使他們永遠不會被清除。
二、根搜索算法(可達性分析)
爲了解決引用計數法的循環引用問題,Java使用了可達性分析的方法。經過一系列的「GC roots」對象做爲起點搜索。若是在「GC roots」和一個對象之間沒有可達路徑,則稱該對象是不可達的。要注意的是,不可達對象不等價於可回收對象,不可達對象變爲可回收對象至少要通過兩次標記過程。兩次標記後仍然是可回收對象,則將面臨回收。
比較常見的將對象視爲可回收對象的緣由:
顯式地將對象的惟一強引用指向新的對象。
顯式地將對象的惟一強引用賦值爲Null。
局部引用所指向的對象(如,方法內對象)。
只有弱引用與其關聯的對象。
1)強引用(StrongReference)
強引用是使用最廣泛的引用。若是一個對象具備強引用,那垃圾回收器毫不會回收它。當內存空間不足,Java虛擬機寧願拋出OutOfMemoryError錯誤,使程序異常終止,也不會靠隨意回收具備強引用的對象來解決內存不足的問題。 ps:強引用其實也就是咱們平時A a = new A()這個意思。
2)軟引用(SoftReference)
若是一個對象只具備軟引用,則內存空間足夠,垃圾回收器就不會回收它;若是內存空間不足了,就會回收這些對象的內存。只要垃圾回收器沒有回收它,該對象就能夠被程序使用。軟引用可用來實現內存敏感的高速緩存(下文給出示例)。
軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。
3)弱引用(WeakReference)
弱引用與軟引用的區別在於:只具備弱引用的對象擁有更短暫的生命週期。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。不過,因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。
弱引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。
4)虛引用(PhantomReference)
「虛引用」顧名思義,就是形同虛設,與其餘幾種引用都不一樣,虛引用並不會決定對象的生命週期。若是一個對象僅持有虛引用,那麼它就和沒有任何引用同樣,在任什麼時候候均可能被垃圾回收器回收。
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。虛引用與軟引用和弱引用的一個區別在於:虛引用必須和引用隊列 (ReferenceQueue)聯合使用。當垃圾回收器準備回收一個對象時,若是發現它還有虛引用,就會在回收對象的內存以前,把這個虛引用加入到與之 關聯的引用隊列中。
3.2.三、典型的垃圾回收算法介紹
一、標記-清除算法(Mark-Sweep)
最基礎的垃圾回收算法,分爲「標註」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收掉全部被標記的對象。
標記過程:爲了可以區分對象是live的,能夠爲每一個對象添加一個marked字段,該字段在對象建立的時候,默認值是false。
清除過程:去遍歷堆中全部對象,並找出未被mark的對象,進行回收。與此同時,那些被mark過的對象的marked字段的值會被從新設置爲false,以便下次的垃圾回收。
缺點:效率低,空間問題(產生大量不連續的內存碎片),後續可能發生大對象不能找到可利用空間的問題。
image.png
二、複製算法(Copying)——新生代的收集算法就是這種,可是比例不是1:1,而是(8+1):1
爲了解決Mark-Sweep算法內存碎片化的缺陷而被提出的算法。按內存容量將內存劃分爲大小相等的兩塊,每次只使用其中一塊。當這一塊內存滿後將尚存活的對象複製到另外一塊上去,把已使用的內存空間一次清理掉。這種算法雖然實現簡單,內存效率高,不易產生碎片,可是最大的問題是可用內存被壓縮到了本來的一半。且存活對象增多的話,Copying算法的效率會大大下降。
image.png
三、標記-整理算法(Mark-Compact)——老年代的收集算法
結合了以上兩個算法,標記階段和Mark-Sweep算法相同,標記後不是清理對象,而是將全部存活對象移向內存的一端,而後清除端邊界外的對象。如圖:
image.png
四、分代收集算法(Generational Collection)
分代收集法是目前大部分JVM所採用的方法,其核心思想是根據對象存活的不一樣生命週期將內存劃分爲不一樣的域,通常狀況下將GC堆劃分爲老生代(Tenured/Old Generation)和新生代(Young Generation)。
老生代的特色是每次垃圾回收時只有少許對象須要被回收,新生代的特色是每次垃圾回收時都有大量垃圾須要被回收,所以能夠根據不一樣區域選擇不一樣的算法。
目前大部分JVM的GC對於新生代都採起復制算法(Copying),由於新生代中每次垃圾回收都要回收大部分對象,即要複製的操做比較少,但一般並非按照1:1來劃分新生代。通常將新生代劃分爲一塊較大的Eden空間和兩個較小的Survivor空間(From Space, To Space),每次使用Eden空間和其中的一塊Survivor空間,當進行回收時,將該兩塊空間中還存活的對象複製到另外一塊Survivor空間中。
老年代中的對象存活率高,沒有額外空間對它進行分配,使用「標記-清理」或「標記-整理」算法來進行回收。
3.三、JVM處理FULLGC經驗
3.3.一、內存泄漏
一、產生緣由
1)JVM內存太小。
2)程序不嚴密,產生了過多的垃圾。
二、通常狀況下,在程序上的體現爲:
1)內存中加載的數據量過於龐大,如一次從數據庫取出過多數據。
2)集合類中有對對象的引用,使用完後未清空,使得JVM不能回收。
3)代碼中存在死循環或循環產生過多重複的對象實體。
4)使用的第三方軟件中的BUG。
5)啓動參數內存值設定的太小。
3.3.二、Java內存泄漏的排查案例
一、肯定頻繁Full GC現象,找出進程惟一ID
使用jps(jps -l)或ps(ps aux | grep tomat)找出這個進程在本地虛擬機的惟一ID(LVMID,Local Virtual Machine Identifier)
二、再使用「虛擬機統計信息監視工具:jstat」(jstat -gcutil 20954 1000)查看已使用空間站總空間的百分比,能夠看到FGC的頻次。
image.png
三、找出致使頻繁Full GC的緣由,找出出現問題的對象
分析方法一般有兩種:
1)把堆dump下來再用MAT等工具進行分析,但dump堆要花較長的時間,而且文件巨大,再從服務器上拖回本地導入工具,這個過程有些折騰,不到萬不得已最好別這麼幹。
2)更輕量級的在線分析,使用「Java內存影像工具:jmap」生成堆轉儲快照(通常稱爲headdump或dump文件)。
jmap命令格式:jmap -histo:live 20954
四、一個工具:BTrace,沒有使用過
4.一、找出慢SQL的方法
4.1.一、開啓慢查詢日誌
4.1.二、MySQL基準測試方法
4.二、索引的優缺點及實現原理
4.2.一、索引的優缺點
4.2.二、MySQL索引的實現原理
4.三、對於一個SQL的性能優化過程
4.四、MySQL數據庫的分庫分表方式以及帶來的問題處理
4.五、MySQL主從複製數據一致性問題處理
5.一、HTTP請求報文和響應報文
image.png
5.二、HTTPS爲何是安全的?HTTPS的加密方式有哪些?
5.2.一、HTTPS的工做原理說明HTTPS是安全的
image.png
客戶端在使用HTTPS方式與Web服務器通訊時有如下幾個步驟,如圖所示。
一、客戶使用https的URL訪問Web服務器,要求與Web服務器創建SSL鏈接。
二、Web服務器收到客戶端請求後,會將網站的證書信息(證書中包含公鑰)傳送一份給客戶端。
三、客戶端的瀏覽器與Web服務器開始協商SSL鏈接的安全等級,也就是信息加密的等級。
四、客戶端的瀏覽器根據雙方贊成的安全等級,創建會話密鑰,而後利用網站的公鑰將會話密鑰加密,並傳送給網站。
五、Web服務器利用本身的私鑰解密出會話密鑰。
六、Web服務器利用會話密鑰加密與客戶端之間的通訊。
image.png
5.2.二、HTTPS的加密方式有哪些?
一、對稱加密
對稱加密是指加密和解密使用相同密鑰的加密算法。它要求發送方和接收方在安全通訊以前,商定一個密鑰。對稱算法的安全性依賴於密鑰,泄漏密鑰就意味着任何人均可以對他們發送或接收的消息解密,因此密鑰的保密性對通訊相當重要。
二、更多的加密方式的瞭解
5.三、TCP三次握手協議,四次揮手
第一次握手:主機A發送位碼爲syn=1,隨機產生seq number=1234567的數據包到服務器,主機B由SYN=1知道,A要求創建聯機;
第二次握手:主機B收到請求後要確認聯機信息,向A發送ack number=(主機A的seq+1),syn=1,ack=1,隨機產生seq=7654321的包;
第三次握手:主機A收到後檢查ack number是否正確,即第一次發送的seq number+1,以及位碼ack是否爲1,若正確,主機A會再發送ack number=(主機B的seq+1),ack=1,主機B收到後確認seq值與ack=1則鏈接創建成功。
完成三次握手,主機A與主機B開始傳送數據。
5.四、OAuth協議介紹
image.png
5.五、防盜鏈Referer
Referer請求頭: 表明當前訪問時從哪一個網頁鏈接過來的。
當Referer未存在或者是從其餘站點訪問咱們資源的時候就直接重定向到咱們的主頁,這樣既能夠防止咱們的資源被竊取。
6.一、AOP的實現原理
spring框架對於這種編程思想的實現基於兩種動態代理模式,分別是JDK動態代理 及 CGLIB的動態代理,這兩種動態代理的區別是 JDK動態代理須要目標對象實現接口,而 CGLIB的動態代理則不須要。下面咱們經過一個實例來實現動態代理,進而幫助咱們理解面向切面編程。
JDK的動態代理要使用到一個類 Proxy 用於建立動態代理的對象,一個接口 InvocationHandler用於監聽代理對象的行爲,其實動態代理的本質就是對代理對象行爲的監聽。
6.二、Spring MVC工做原理
Spring的MVC框架主要由DispatcherServlet、處理器映射、處理器(控制器)、視圖解析器、視圖組成。
6.2.一、SpringMVC原理圖
image.png
6.2.二、SpringMVC運行原理
一、客戶端請求提交到DispatcherServlet
二、由DispatcherServlet控制器查詢一個或多個HandlerMapping,找處處理請求的Controller
三、DispatcherServlet將請求提交到Controller
四、Controller調用業務邏輯處理後,返回ModelAndView
五、DispatcherServlet查詢一個或多個ViewResoler視圖解析器,找到ModelAndView指定的視圖
六、視圖負責將結果顯示到客戶端
6.2.三、SpringMVC核心組件
一、DispatcherServlet:中央控制器,把請求給轉發到具體的控制類
二、Controller:具體處理請求的控制器
三、HandlerMapping:映射處理器,負責映射中央處理器轉發給controller時的映射策略
四、ModelAndView:服務層返回的數據和視圖層的封裝類
五、ViewResolver:視圖解析器,解析具體的視圖
六、Interceptors :攔截器,負責攔截咱們定義的請求而後作處理工做
6.2.四、Servlet 生命週期
Servlet 生命週期可被定義爲從建立直到毀滅的整個過程。如下是 Servlet 遵循的過程:
一、Servlet 經過調用 init () 方法進行初始化。
二、Servlet 調用 service() 方法來處理客戶端的請求。
三、Servlet 經過調用 destroy() 方法終止(結束)。
四、最後,Servlet 是由 JVM 的垃圾回收器進行垃圾回收的。
6.2.五、Spring容器初始化過程
Spring 啓動時讀取應用程序提供的Bean配置信息,並在Spring容器中生成一份相應的Bean配置註冊表,而後根據這張註冊表實例化Bean,裝配號Bean之間的依賴關係,爲上層應用提供準備就緒的運行環境。
image.png
7.一、分佈式下如何保證事務一致性
分佈式事務,常見的兩個處理辦法就是兩段式提交和補償。
7.1.一、兩段式提交
分佈式事務將提交分紅兩個階段:
prepare;
commit/rollback
在分佈式系統中,每一個節點雖然能夠知曉本身的操做是成功或者失敗,卻沒法知道其餘節點的操做的成功或失敗。當一個事務跨越多個節點時,爲了保持事務的ACID特性,須要引入一個做爲協調者的組件來統一掌控全部節點(參與者)的操做結果並最終指示這些節點是否須要把操做結果進行真正的提交。算法步驟以下:
第一階段:
一、協調者會問全部的參與者,是否能夠執行提交操做。
二、各個參與者開始事務執行的準備工做,如:爲資源上鎖,預留資源。
三、參與者響應協調者,若是事務的準備工做成功,則迴應「能夠提交」,不然迴應「拒絕提交」。
第二階段:
一、若是全部的參與者都回應「能夠提交」。那麼協調者向全部的參與者發送「正式提交」的命令。參與者完成正式提交併釋放全部資源,而後迴應「完成」,協調者收集各節點的「完成」迴應後結束這個Global Transaction
二、若是有一個參與者迴應「拒絕提交」,那麼協調者向全部的參與者發送「回滾操做」,並釋放全部資源,而後迴應「回滾完成」,協調者收集各節點的「回滾」迴應後,取消這個Global Transaction。
7.1.二、三段式提交
三段提交的核心理念是:在詢問的時候並不鎖定資源,除非全部人都贊成了,纔開始鎖資源。他把二段提交的第一個段break成了兩段:詢問,而後再鎖資源。最後真正提交。
7.1.二、事務補償,最終一致性
補償比較好理解,先處理業務,而後定時或者回調裏,檢查狀態是否是一致的,若是不一致採用某個策略,強制狀態到某個結束狀態(通常是失敗狀態)。
8.一、Linux日誌分析經常使用命令
8.1.一、查看文件內容
cat
-n 顯示行號
8.1.二、分頁顯示
more
Enter 顯示下一行
空格 顯示下一頁
F 顯示下一屏
B 顯示上一屏
less
/get 查詢"get"字符串並高亮顯示
8.1.三、顯示文件尾
tail
-f 不退出持續顯示
-n 顯示文件最後n行
8.1.四、顯示頭文件
head
-n 顯示文件開始n行
8.1.五、內容排序
sort
-n 按照數字排序
-r 按照逆序排序
-k 表示排序列
-t 指定分隔符
8.1.六、字符統計
wc
-l 統計文件中行數
-c 統計文件字節數
-L 查看最長行長度
-w 查看文件包含多少個單詞
8.1.七、查看重複出現的行
uniq
-c 查看該行內容出現的次數
-u 只顯示出現一次的行
-d 只顯示重複出現的行
8.1.八、字符串查找
grep
8.1.九、文件查找
find
which
whereis
8.1.十、表達式求值
expr
8.1.十一、歸檔文件
tar
zip
unzip
8.1.十二、URL訪問工具
curl
wget
8.1.1三、查看請求訪問量
頁面訪問排名前十的IP
cat access.log | cut -f1 -d " " | sort | uniq -c | sort -k 1 -r | head -10
頁面訪問排名前十的URL
cat access.log | cut -f4 -d " " | sort | uniq -c | sort -k 1 -r | head -10
查看最耗時的頁面
cat access.log | sort -k 2 -n -r | head 10
9.一、kafka消息隊列
一、避免數據丟失
producer:
加大重試次數
同步發送
對於單條數據過大,要設置可接收的單條數據的大小
對於異步發送,經過回調函數來感知丟消息
block.on.buffer.full = true
consumer:
enable.auto.commit=false 關閉自動提交位移
二、避免消息亂序
假設a,b兩條消息,a先發送後因爲發送失敗重試,這時順序就會在b的消息後面,能夠設置max.in.flight.requests.per.connection=1來避免。
max.in.flight.requests.per.connection:限制客戶端在單個鏈接上可以發送的未響應請求的個數,設置此值是1表示kafka broker在響應請求以前client不能再向同一個broker發送請求,但吞吐量會降低。
三、避免消息重複
使用第三方redis的set
9.二、ZooKeeper的原理
9.三、SOA相關,RPC兩種實現方式:基於HTTP和基於TCP
9.四、Netty
image.png
9.五、Dubbo
10.一、系統作了哪些安全防禦
一、XSS(跨站腳本攻擊)
全稱是跨站腳本攻擊(Cross Site Scripting),指攻擊者在網頁中嵌入惡意腳本程序。
XSS防範:
XSS之因此會發生,是由於用戶輸入的數據變成了代碼。所以,咱們須要對用戶輸入的數據進行HTML轉義處理,將其中的「尖括號」、「單引號」、「引號」之類的特殊字符進行轉義編碼。
二、CSRF(跨站請求僞造)
攻擊者盜用了你的身份,以你的名義向第三方網站發送惡意請求。
CSRF的防護:
1)儘可能使用POST,限制GET
2)將cookie設置爲HttpOnly
3)增長token
4)經過Referer識別
三、SQL注入
使用預編譯語句(PreparedStatement),這樣的話即便咱們使用sql語句僞形成參數,到了服務端的時候,這個僞造sql語句的參數也只是簡單的字符,並不能起到攻擊的做用。
作最壞的打算,即便被’拖庫‘('脫褲,數據庫泄露')。數據庫中密碼不該明文存儲的,能夠對密碼使用md5進行加密,爲了加大破解成本,因此能夠採用加鹽的(數據庫存儲用戶名,鹽(隨機字符長),md5後的密文)方式。
四、DDOS
最直接的方法增長帶寬。可是攻擊者用各地的電腦進行攻擊,他的帶寬不會耗費不少錢,但對於服務器來講,帶寬很是昂貴。
雲服務提供商有本身的一套完整DDoS解決方案,而且能提供豐富的帶寬資源
那如何學習才能快速入門並精通呢?
在此給你們推薦一個Java架構方面的交流學習羣:698581634,裏面會分享一些資深架構師錄製的視頻錄像:有Spring,MyBatis,Netty源碼分析,高併發、高性能、分佈式、微服務架構的原理,JVM性能優化這些成爲架構師必備的知識體系,主要針對Java開發人員提高本身,突破瓶頸,相信你來學習,會有提高和收穫。在這個羣裏會有你須要的內容 朋友們請抓緊時間加入進來吧。