文章目錄
基礎部分
1.集合繼承體系
2.List與Set區別
相同點
都是可以用來存放任意類型數據的集合
不同點
List集合可以存放:有序可重複數據(存入順序與取出順序一致)
Set集合可以存放:無序不可重複數據 (存入順序與取出順序不一致),可以通過hash方法和equals方法來進行去重。
3.ArrayList與LinkedList區別
相同點
都是List接口的實現類,可以存放任意類型的數據
不同點
ArrayList是基於數組實現的,數據具有索引值可以直接定位到數組中的位置所以查找快,增加需要查看是否達到了設定的容量,如果超過了就會進行數組的擴容所以增加慢。
LinkedList是基於雙向鏈表實現的,雙向鏈表可以直接增加節點,所以增加快;但查詢需要遍歷鏈表才能得到值,所以查詢慢。
4.HashSet與TreeSet區別
相同點:
都是Set接口的實現類,可以存放任意類型的數據
不同點:
HashSet的去重標準是通過:hashcode和equals方法共同決定
TreeSet的去重標準:自然排序或定製排序
自然排序:實體類實現Comparable接口
定製排序:set容器中傳入一個Comparator接口
的實現類(匿名內部類)
5.HashMap、HashTable、TreeMap、ConcurrentHashMap區別
相同點:
1.都是map實現類,都是鍵值對
2.鍵都不能重複,可以存儲任意類型任意多個數據
3.遍歷要通過keySet和entrySet方法來進行遍歷
不同點
HashMap線程不安全 可以以null作爲key或value 效率高,適用於在Map中插入、刪除和定位元素。 (最常用)
TreeMap 線程不安全 key的值不能爲null,保存的記錄是默認使用自然排序—升序(Comprable),也可以用比較器(Comparator)進行指定排序,所以是有序的。底層基於紅黑樹(一種自平衡二叉查找樹)。
Hashtable線程安全 不能以null作爲key或value 效率較低 同步的方法
ConcurrentHashMap 線程安全 不能以null作爲key或value 效率較高 同步的代碼快,提高了併發效率。
6.HashMap put get過程
這篇博客說的很詳細,多看多理解。
https://segmentfault.com/a/1190000015726870
put方式
1.我們在new一個新的HashMap時會創建一個空的HashMap容器,其中會有一些準備好的常量字段:數組默認長度:16,默認加載因子:0.75,鏈表轉化紅黑樹閥值:8,紅黑樹轉化爲鏈表的閥值:6
2.在put方法的時候根據key的hash
值去找到具體的桶,先判斷桶中是否有內容(長度是否爲0,是否爲null);如果沒有就創建一個默認長度爲16
的數組;
3.根據key值得hashcode定位到一個桶中,如果沒有值就創建一個新桶,直接加進去。
4.如果桶中有內容,就循環遍歷,依次比較兩個key值的hashcode
與equals
是否相等;如果相等就進行覆蓋並在該方法末尾返回一個原value值
5.如果兩個key值的hashcode
與equals
不相等,那麼就判斷當前是以什麼方式進行存儲的。如果是鏈表就以鏈表的形式存入,如果爲紅黑樹就以紅黑樹的形式存入。
6.如果以鏈表的方式存入,就判斷存入後鏈表的長度是否小於8
,如果不小於8
就轉化爲紅黑樹
7.存入判斷當前map是否需要擴容(直接當前數組長度的兩倍)
get方式
1.根據key來計算hash值,查看hash中是否有數據。如果爲空直接返回null
2.取桶中的第一個數據是否爲想要的key值,如果成功直接返回
3.不成功判斷該桶中的內容是爲鏈表還是紅黑樹
4.如果爲鏈表就以鏈表的形式返回,如果爲紅黑樹就以紅黑樹的方式返回
7.線程的創建方式
1.繼承Thread
類
2.實現Runable接口
3.實現Callable接口
,具有返回值
推薦使用實現,JAVA支持類的單繼承多重繼承,實現的拓展性更強。
8.線程的狀態有哪些 線程中的方法有哪些
新建狀態—>就緒狀態—>(有可能出現阻塞狀態)—>運行—>死亡
new—>start—>(sleep/wait)—>獲取到cup—>完成或者其它原因終止
9.線程安全問題 如何解決
1.同步代碼—同步監聽對象
2.同步方法—靜態方法(監聽當前字節碼對象) 非靜態方法(監聽this對象)
3.Lock上鎖
10.ThreadLocal有什麼作用 原理
作用
通過爲每個線程提供一個獨立的變量副本解決了變量併發訪問的衝突問題,線程間的數據隔離
原理
初始時,在Thread裏面,threadLocals爲空,當通過ThreadLocal變量調用get()方法或者set()方法,就會對Thread類中的threadLocals進行初始化,並且以當前ThreadLocal變量爲鍵值,以ThreadLocal要保存的副本變量爲value,存到threadLocals。
然後在當前線程裏面,如果要使用副本變量,就可以通過get方法在threadLocals裏面查找。
11.wait與sleep區別
相同點
運行狀態中的線程執行,都可以使線程進入到等待阻塞狀態。
不同點
wait() :1.通過 wait() 使線程掛起,直到線程得到 notify() 或 notifyAll() 消息,不然會一直等待。2.是屬於Object中方法 會放棄鎖資源
sleep(): 1.當過了等待時間,或者 I/O 處理完畢,線程重新轉入就緒狀態。 2.調用 Thread.sleep() 方法進入休眠狀態,不會放棄鎖資源
12.synchronize與lock的區別
相同點
不同點
synchronize同步代碼塊:
監聽對象:特點 唯一性
1.this
2.字節碼
3.其他對象(匿名對象不能 普通對象要static修飾 String有常量池的)
同步方法:
默認監聽對象是this
Lock加鎖 :
加鎖的功能更強大
但需要進行手動釋放,不然會造成死鎖
總結:
同步是由jvm來維護 加鎖是代碼級別 需要釋放鎖 效率要更高一些
13.Spring的理解
IO與NIO的區別
線程池:
1.線程池的作用?
1.提高線程複用性,減少線程創建與銷燬時的內存消耗
2.控制併發數量,通過閒時時間來控制線程的最大數量與最小數量
3.管理線程的生命週期
2.創建方式有哪些?有什麼區別?
創建方式 | 創建內容 | 作用 | 默認值 |
---|---|---|---|
CachedThreadPool |
創建一個可緩存的線程池 | 執行很多短期異步的小程序或者負載較輕的服務器 | 如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。線程可以重複利用執行任務。默認爲60s未使用就被終止和移除 |
FixedThreadPool |
創建一個定長的線程池 | 執行長期的任務 | 如果當前線程數小於核心線程數,並且也有閒置線程的時候提交了任務,這時也不會去複用之前的閒置線程,會創建新的線程去執行任務。如果當前執行任務數大於了核心線程數,大於的部分就會進入隊列等待。等着有閒置的線程來執行這個任務 |
SingleThreadPool |
創建一個單線程的線程池 | 一個任務一個任務執行的場景 | 有且僅有一個工作線程執行任務,所有任務按照指定順序執行,即遵循隊列的入隊出隊規則 |
ScheduledThreadPool |
創建一個預定時間的線程池,支持定時及週期性任務執行 | 週期性執行任務的場景(定期的同步數據) | |
ThreadPoolExecutor |
創建一個自定義的線程池 | 控制性高 |
3.創建線程池的核心參數有哪些?每一代表什麼意思?
參數 | 作用 |
---|---|
corePoolSize | 核心線程數(最小存活的工作線程數量) |
maxPoolSize | 最大線程數 |
keepAliveTime | 線程存活時間(在corePoreSize<maxPoolSize情況下有用,線程的空閒時間超過了keepAliveTime就會銷燬) |
timeUnit | 存活時間的時間單位 |
workQueue | 阻塞隊列,用來保存等待被執行的任務(①synchronousQueue:這個隊列比較特殊,它不會保存提交的任務,而是將直接新建一個線程來執行新來的任務;②LinkedBlockingQueue:基於鏈表的先進先出隊列,如果創建時沒有指定此隊列大小,則默認爲Integer.MAX_VALUE;③ArrayBlockingQueue:基於數組的先進先出隊列,此隊列創建時必須指定大小) |
threadFactory | 線程工廠,主要用來創建線程 |
handler | 表示當拒絕處理任務時的策略(①丟棄任務並拋出RejectedExecutionException異常;②丟棄任務,但是不拋出異常;③丟棄隊列最前面的任務,然後重新嘗試執行任務;④由調用線程處理該任務) |
4.線程池的拒絕策略有哪些?每一種有什麼特點?
前提
當線程池中的數量到達了maximumPoolSize(最大線程數量)就會採用拒絕策略。
四種方式
方式 | 作用 |
---|---|
AbortPolicy(默認) | 直接拋出RejectedExecutionException異常阻止系統正常運行。 |
CallerRunsPolicy | “調用者運行”一種調節機制,該策略既不會丟棄任務,也不會拋出異常,而是將某些任務回退給調用者,從而降低新任務的流量 |
DiscardOldestPolicy | 拋棄隊列中等待最久的任務,然後把當前任務加入隊列中嘗試再次提交當前任務。 |
DiscardPolicy | 直接丟棄任務,不予任何處理也不拋出異常。如果允許任務丟失,這是最好的一種方案。 |
JVM優化?
1.什麼是JVM?JVM有哪些組成?每一部分的作用?
定義
JVM是虛擬機(Java Virtual Machine)的英文簡稱
JAVA跨平臺原理:憑藉的就是JVM.而對於不同的平臺,Windows,Linux,Mac OS等,有具體不同的JVM版本.這些JVM屏蔽了平臺的不同,提供了統一的運行環境,讓Java代碼無需考慮平臺的差異,運行在相同的環境中
組成圖
組件
- 類加載器子系統
- 本地方法庫
- 執行引擎
- 運行數據區
作用
1.類加載器子系統
1.1 類加載的過程有哪些?每一個階段做什麼事情?
包含了加載、驗證、準備、解析和初始化5個步驟
1.加載:找到相應的字節碼文件,加載至內存中。分爲顯示加載與隱式加載。顯示加載:拿到完全限定名,通過反射獲得加載對象 隱式加載:通過new的方式來加載對象
2.驗證:判斷當前文件夾是不是一個java的class文件
3.準備:將static
修飾的內容進行初始化創建,要麼爲0或者爲null。如果還有finally
修飾就給它賦予後面的初始值
4.解析:將java代碼中的符號引用轉化爲直接引用,會直接找到該方法內存中的地址值。
5.初始化:爲剛纔準備好的參數進行初始化賦值
1.2雙親委派機制指的是什麼?
原理
在我們自定義類加載器
中需要加載一個類的時候,不會直接進行加載,而會委託上級的應用程序類加載器
進行加載,而一直會委託到啓動類加載器
。
啓動類加載器
會看自己是否具有這個類,如果沒有就再次返回給下級繼續尋找該類。如果找到了就進行加載,沒有找到就拋出異常。
2. 執行引擎
執行引擎包含即時編譯器(JIT)和垃圾回收器(GC)
3. JDK1.8以後運行時數據區有哪些組成?每一部分有什麼作用?
JDK1.7與1.8最大的區別就是,使用元數據區取代了永久代。元數據區不再存在虛擬機中,而是存於本地內存中
4.1 程序計數器
每個線程所私有的一塊很小的內存空間,當前線程執行字節碼的行號指示器,唯一一塊不會內存溢出的區域(OOM),如果當前線程執行的是native
方法的話,其值爲null
4.2 JAVA虛擬機棧
每個線程私有的,每一個方法執行的時候都會創建一個棧幀,其生命週期與線程同進同退,併入棧。一旦完成調用,則出棧。所有的的棧幀都出棧後,線程也就完成了使命。
4.3 本地方法棧
與JAVA虛擬機棧的類似,只對Native method
的方法起作用
4.4 堆(重點)
內存模型
堆是java虛擬機管理內存最大的一塊內存區域,所有線程共享,用來存放對象的區域,也是GC的主要區域。其中分爲老年代與新生代,其比例爲2:3.
新生代:一個Eden區,兩個Survivor區。
Eden區:
存放的是通過new 或者newInstance方法創建出來的對象,絕大多數都是很短命的.正常情況下經歷一次gc之後,存活的對象會轉入到其中一個Survivor區,然後再經歷默認15次的gc,就轉入到老年代.這是常規狀態下,在Survivor區已經滿了的情況下,JVM會依據擔保機制將一些對象直接放入老年代。
4.5 直接內存
jdk1.4引入了NIO,它可以使用Native函數庫直接分配堆外內存。
4.6 元數據區
存放虛擬機加載的類信息,靜態變量,常量等數據。
垃圾回收
1.什麼是垃圾即如何判斷對象已死?
什麼垃圾:
對象失去引用,不會再次用到的內容。
如何判斷
(1)引用計數算法
引用了就添加一個計數,但互相引用也會添加。會導致失敗
(2)可達性分析算法
當一個對象到GC Roots沒有任何引用鏈相連(GC Roots到這個對象不可達)時,就說明此對象是不可用的,是死對象
(3)方法區回收
方法區中主要回收的是廢棄的常量和無用的類,
判斷常量是否廢棄可以判斷是否有地方引用這個常量,如果沒有引用則爲廢棄的常量。
判斷類是否廢棄需要同時滿足如下條件:
- 該類所有的實例已經被回收(堆中不存在任何該類的實例)
- 加載該類的ClassLoader已經被回收、
- 該類對應的java.lang.Class對象在任何地方沒有被引用(無法通過反射訪問該類的方法)
2.垃圾回收算法有哪些?每種的特點是什麼樣的?
1.標記-清除算法
該算法先標記,後清除,將所有需要回收的算法進行標記,然後清除;這種算法的缺點是:效率比較低;標記清除後會出現大量不連續的內存碎片,這些碎片太多可能會使存儲大對象會觸發GC回收,造成內存浪費以及時間的消耗。
2.複製算法
複製算法將可用的內存分成兩份,每次使用其中一塊,當這塊回收之後把未回收的複製到另一塊內存中,然後把使用的清除。這種算法運行簡單,解決了標記-清除算法的碎片問題,但是這種算法代價過高,需要將可用內存縮小一半,對象存活率較高時,需要持續的複製工作,效率比較低。
3.標記整理算法
標記整理算法是針對複製算法在對象存活率較高時持續複製導致效率較低的缺點進行改進的,該算法是在標記-清除算法基礎上,不直接清理,而是使存活對象往一端遊走,然後清除一端邊界以外的內存,這樣既可以避免不連續空間出現,還可以避免對象存活率較高時的持續複製。這種算法可以避免100%對象存活的極端狀況,因此老年代不能直接使用該算法。
4.分代收集算法
分代收集算法就是目前虛擬機使用的回收算法,它解決了標記整理不適用於老年代的問題,將內存分爲各個年代,在不同年代使用不同的算法,從而使用最合適的算法,新生代存活率低,可以使用複製算法。而老年代對象存活率搞,沒有額外空間對它進行分配擔保,所以只能使用標記清除或者標記整理算法。
3.什麼時間回收垃圾?
當程序運行到安全點和安全區的時候纔會進行垃圾回收
4.垃圾回收器有哪些?G1垃圾回收器有什麼特點?
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:Serial Old、CMS、Parallel Old
堆內存垃圾收集器:G1
G1垃圾回收的特點
1.G1進行垃圾收集的範圍是整個堆內存,其它收集器收集的範圍都是新生代或者老年代。
2.採用”化整爲零“的思路,堆內存被劃分爲多個大小相等的內存塊,每個Region被標記了E、S、O和H,說明每個Region在運行時都充當了一種角色,其中H是以往算法中沒有的,它代表Humongous,這表示這些Region存儲的是巨型對象(humongous object,H-obj),當新建對象大小超過Region大小一半時,直接在新的一個或多個連續Region中分配,並標記爲H。
5.新生代和老年代的垃圾回收有什麼不同?
young gc
發生在年輕代的GC算法,一般對象(除了巨型對象)都是在eden region中分配內存,當所有eden region被耗盡無法申請內存時,就會觸發一次young gc,這種觸發機制和之前的young gc差不多,執行完一次young gc,活躍對象會被拷貝到survivor region或者晉升到old region中,空閒的region會被放入空閒列表中,等待下次被使用。
mixed gc
當越來越多的對象晉升到老年代old region時,爲了避免堆內存被耗盡,虛擬機會觸發一個混合的垃圾收集器,即mixed gc,該算法並不是一個old gc,除了回收整個young region,還會回收一部分的old region,這裏需要注意:是一部分老年代,而不是全部老年代,可以選擇哪些old region進行收集,從而可以對垃圾回收的耗時時間進行控制。
full gc
如果對象內存分配速度過快,mixed gc來不及回收,導致老年代被填滿,就會觸發一次full gc,G1的full gc算法就是單線程執行的serial old gc,會導致異常長時間的暫停時間,需要進行不斷的調優,儘可能的避免full gc.
6.JVM優化用的工具有哪些?常用的參數?
前提
JVM調優目標:
使用較小的內存佔用來獲得較高的吞吐量或者較低的延遲
指標:
- 內存佔用:程序正常運行需要的內存大小。
- 延遲:由於垃圾收集而引起的程序停頓時間。
- 吞吐量:用戶程序運行時間佔用戶程序和垃圾收集佔用總時間的比值。
常用工具
- 用 jps(JVM process Status)可以查看虛擬機啓動的所有進程、執行主類的全名、JVM啓動參數
- 用jstat(JVM Statistics Monitoring Tool)監視虛擬機信息
jstat -gc pid 500 10 :每500毫秒打印一次Java堆狀況(各個區的容量、使用容量、gc時間等信息),打印10次
3.用jmap(Memory Map for Java)查看堆內存信息
4.利用jvisualvm分析內存信息(各個區如Eden、Survivor、Old等內存變化情況)
常用參數
參數 | 說明 | 實例 |
---|---|---|
-Xms | 初始堆大小,默認物理內存的1/64 | -Xms512M |
-Xmx | 最大堆大小,默認物理內存的1/4 | -Xms2G |
-Xmn | 新生代內存大小,官方推薦爲整個堆的3/8 | -Xmn512M |
-Xss | 線程堆棧大小,jdk1.5及之後默認1M,之前默認256k | -Xss512k |
-XX:NewRatio=n | 設置新生代和年老代的比值。如:爲3,表示年輕代與年老代比值爲1:3,年輕代佔整個年輕代年老代和的1/4 | -XX:NewRatio=3 |
-XX:SurvivorRatio=n | 年輕代中Eden區與兩個Survivor區的比值。注意Survivor區有兩個。如:8,表示Eden:Survivor=8:1:1,一個Survivor區佔整個年輕代的1/8 | -XX:SurvivorRatio=8 |
Redis
1.Redis爲什麼這麼快
1.數據是存在內存中
2.數據結構簡單,查詢和操作的時間複雜的都是O(1);
3.單線程架構,防止了多線程間的問題。如:不存在線程間的切換。
2.常用類型,以及使用場景
常用類型 | 使用場景 |
---|---|
String:字符串 | (最大不超過512M)緩存、共享用戶Session、計數、限流、點贊 |
Hash:鍵值對結構 | 儲存用戶信息、簡單直觀、減少內存使用空間 |
List:多個有序字符串 | 消息隊列:Redis的lpush+brpop命令組合即可實現阻塞隊列 |
Set:無序不可重複,可以求交集與並集 | 全局去重、給用戶增加標籤,抽獎 |
ZSet:有序不可重複 | 排行榜 |
3.緩存擊穿、緩存穿透、緩存雪崩
緩存穿透
定義
用戶一直訪問Redis和數據庫都沒有的數據(如id值爲-1),攻擊會造成數據庫壓力過大
解決
1.會在接口層增加校驗,比如用戶鑑權校驗,參數做校驗,不合法的參數直接代碼Return,比如:id 做基礎校驗,id <=0的直接攔截等。
2.從緩存取不到的數據,在數據庫中也沒有取到,這時也可以將對應Key的Value對寫爲null、位置錯誤、稍後重試這樣的值具體取啥問產品,或者看具體的場景,緩存有效時間可以設置短點,如30秒(設置太長會導致正常情況也沒法使用)。
3.絕活:避免緩存穿透的利器之BloomFilter(布隆過濾器)(站在布隆後面?)
BloomFilter(布隆過濾器):當一個元素被加入集合時,通過K個散列函數將這個元素映射成一個位數組中的K個點,把它們置爲1。檢索時,我們只要看看這些點是不是都是1就(大約)知道集合中有沒有它了:如果這些點有任何一個0,則被檢元素一定不在;如果都是1,則被檢元素很可能在。這就是布隆過濾器的基本思想。
作者:敖丙
鏈接:https://juejin.im/post/5db69365518825645656c0de
來源:掘金
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
緩存擊穿
定義
在程序中Redis的一個熱點數據過期,這個熱點數據一直承受着高併發請求,而這個數據過期時會造成大量請求直接訪問到數據庫中引起數據庫崩潰。就像水桶突然被鑿開了一個洞,大量水從洞中涌出
解決
熱點數據設置爲永不過期,或者增加一個互斥鎖
緩存雪崩
定義
在Redis中大量數據同時過期,而用戶在這個時候訪問這些過期數據會直接請求到數據庫中,造成數據庫崩潰的情況
解決
在設置過期時間的時候加上一個隨機值,或者設置爲永不過期
總結
Redis就是作爲用戶和數據庫的一箇中間件,用來緩存數據在中間起一個緩衝作用。而就是會出現總總原因導致了Redis失效,從而大量操作涌進數據庫中造成數據庫崩潰。而我們解決的就是在Redis失效的時候找到一個方案來重新建立一個新的緩存區或者在那一段時間現在用戶的訪問來保護數據庫。
4.AOF和RDB的區別
redis提供了兩種持久化的方式:AOF和RDB,可以通過修改配置文件redis.conf來進行更改
RDB模式:
持久化可以在指定的時間間隔內(五分鐘)生成數據集的時間點快照,默認開啓該模式.(保存內容)
優點:
適合做冷備(定時備份)
相同內容比AOF文件更小,數據恢復的時候速度更快
缺點
是快照文件,只會在指定時間間隔生成。會丟失一部分數據內容。
在生成數據快照的時候,如果文件內容很大,客戶端會暫停下來去進行備份
AOF模式:
持久化記錄服務器執行的所有寫操作命令,並在服務器啓動時,通過重新執行這些命令來還原數據集,默認關閉該模式(保存命令,一秒一次)
優點
在AOF進行備份的時候是以追加的形式進行,對磁盤開銷小,效率性能高
缺點
AOF開啓的時候,Redis支持寫的QPS會比RDB的更低,因爲會一秒進行備份一次
選擇
RDB進行快速恢復,然後AOF做細節的補全。