一淹仑、Java 基礎(chǔ)知識
1匀借、Object 類相關(guān)方法
- getClass
獲取當(dāng)前運(yùn)行時對象的 Class 對象吓肋。 - hashCode
返回對象的 hash 碼。 - clone
拷貝當(dāng)前對象肤舞, 必須實(shí)現(xiàn) Cloneable 接口李剖。淺拷貝對基本類型進(jìn)行值拷貝囤耳,對引用類型拷貝引用;深拷貝對基本類型進(jìn)行值拷貝充择,對引用類型對象不但拷貝對象的引用還拷貝對象的相關(guān)屬性和方法。兩者不同在于深拷貝創(chuàng)建了一個新的對象。 - equals
通過內(nèi)存地址比較兩個對象是否相等铃剔,String 類重寫了這個方法使用值來比較是否相等键兜。 - toString
返回類名@哈希碼的 16 進(jìn)制穗泵。 - notify
喚醒當(dāng)前對象監(jiān)視器的任一個線程佃延。 - notifyAll
喚醒當(dāng)前對象監(jiān)視器上的所有線程履肃。 - wait
1尺棋、暫停線程的執(zhí)行;2成福、三個不同參數(shù)方法(等待多少毫秒奴艾;額外等待多少毫秒握侧;一直等待)3品擎、與Thread.sleep(long time)
相比萄传,sleep 使當(dāng)前線程休眠一段時間,并沒有釋放該對象的鎖振诬,wait 釋放了鎖赶么。 - finalize
對象被垃圾回收器回收時執(zhí)行的方法辫呻。
2放闺、基本數(shù)據(jù)類型
- 整型:byte(8)怖侦、short(16)谜叹、int(32)叉谜、long(64)
- 浮點(diǎn)型:float(32)停局、double(64)
- 布爾型:boolean(8)
- 字符型:char(16)
3、序列化
Java 對象實(shí)現(xiàn)序列化要實(shí)現(xiàn) Serializable 接口企孩。
- 反序列化并不會調(diào)用構(gòu)造方法勿璃。反序列的對象是由 JVM 自己生成的對象推汽,不通過構(gòu)造方法生成歹撒。
- 序列化對象的引用類型成員變量暖夭,也必須是可序列化的迈着,否則裕菠,會報錯。
- 如果想讓某個變量不被序列化枫振,使用 transient 修飾。
- 單例類序列化雀扶,需要重寫 readResolve() 方法愚墓。
4浪册、String村象、StringBuffer厚者、StringBuilder
- String
由 char[] 數(shù)組構(gòu)成,使用了 final 修飾账忘,是不可變對象鳖擒,可以理解為常量蒋荚,線程安全圆裕;對 String 進(jìn)行改變時每次都會新生成一個 String 對象吓妆,然后把指針指向新的引用對象行拢。 - StringBuffer 線程安全舟奠;StringBuiler 線程不安全沼瘫。
- 操作少量字符數(shù)據(jù)用 String耿戚;單線程操作大量數(shù)據(jù)用 StringBuilder膜蛔;多線程操作大量數(shù)據(jù)用 StringBuffer脖阵。
5命黔、重載與重寫
- 重載
發(fā)生在同一個類中,方法名相同战转,參數(shù)的類型槐秧、個數(shù)刁标、順序不同膀懈,方法的返回值和修飾符可以不同启搂。 - 重寫
發(fā)生在父子類中胳赌,方法名和參數(shù)相同疑苫,返回值范圍小于等于父類捍掺,拋出的異常范圍小于等于父類挺勿,訪問修飾符范圍大于等于父類满钟;如果父類方法訪問修飾符為 private 或者 final 則子類就不能重寫該方法。
6吭露、final
- 修飾基本類型變量讲竿,一經(jīng)出初始化后就不能夠?qū)ζ溥M(jìn)行修改题禀。
- 修飾引用類型變量,不能夠指向另一個引用削彬。
- 修飾類或方法,不能被繼承或重寫雁刷。
7沛励、反射
- 在運(yùn)行時動態(tài)的獲取類的完整信息
- 增加程序的靈活性
- JDK 動態(tài)代理使用了反射
8目派、JDK 動態(tài)代理
- 使用步驟
- 創(chuàng)建接口及實(shí)現(xiàn)類
- 實(shí)現(xiàn)代理處理器:實(shí)現(xiàn) InvokationHandler 址貌,實(shí)現(xiàn) invoke(Proxy proxy练对,Method method螟凭,Object[] args) 方法
- 通過 Proxy.newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h) 獲得代理類
- 通過代理類調(diào)用方法螺男。
9下隧、Java IO
- 普通 IO 淆院,面向流土辩,同步阻塞線程拷淘。
- NIO启涯,面向緩沖區(qū)逝嚎,同步非阻塞补君。
二挽铁、Java 集合框架
1叽掘、List(線性結(jié)構(gòu))
- ArrayList
Object[] 數(shù)組實(shí)現(xiàn)更扁,默認(rèn)大小為 10 浓镜,支持隨機(jī)訪問膛薛,連續(xù)內(nèi)存空間哄啄,插入末尾時間復(fù)雜度 o(1)咨跌,插入第 i 個位置時間復(fù)雜度 o(n - i)锌半。擴(kuò)容,大小變?yōu)?1.5 倍,Arrays.copyOf(底層 System.ArrayCopy)钦勘,復(fù)制到新數(shù)組,指針指向新數(shù)組肛响。 - Vector
類似 ArrayList特笋,線程安全猎物,擴(kuò)容默認(rèn)增長為原來的 2 倍蔫磨,還可以指定增長空間長度堤如。 - LinkedList
基于鏈表實(shí)現(xiàn)搀罢,1.7 為雙向鏈表魄揉,1.6 為雙向循環(huán)鏈表洛退,取消循環(huán)更能分清頭尾省有。
2、Map(K辆毡,V 對)
- HashMap
- 底層數(shù)據(jù)結(jié)構(gòu),JDK 1.8 是數(shù)組 + 鏈表 + 紅黑樹奠货,JDK 1.7 無紅黑樹递惋。鏈表長度大于 8 時萍虽,轉(zhuǎn)化為紅黑樹,優(yōu)化查詢效率王财。
- 初始容量為 16绒净,通過 tableSizeFor 保證容量為 2 的冪次方挂疆。尋址方式缤言,高位異或胆萧,(n-1)&h 取模跌穗,優(yōu)化速度。
- 擴(kuò)容機(jī)制羹唠,當(dāng)元素數(shù)量大于容量 x 負(fù)載因子 0.75 時佩微,容量擴(kuò)大為原來的 2 倍哺眯,新建一個數(shù)組,然后轉(zhuǎn)移到新數(shù)組寝杖。
- 基于 Map 實(shí)現(xiàn)。
- 線程不安全互纯。
- HashMap (1.7) 多線程循環(huán)鏈表問題
- 在多線程環(huán)境下瑟幕,進(jìn)行擴(kuò)容時,1.7 下的 HashMap 會形成循環(huán)鏈表留潦。
- 怎么形成循環(huán)鏈表:
假設(shè)有一 HashMap 容量為 2 只盹, 在數(shù)組下標(biāo) 1 位置以 A -> B 鏈表形式存儲兔院。有一線程對該 map 做 put 操作殖卑,由于觸發(fā)擴(kuò)容條件,需要進(jìn)行擴(kuò)容坊萝。這時另一個線程也 put 操作孵稽,同樣需要擴(kuò)容,并完成了擴(kuò)容操作十偶,由于復(fù)制到新數(shù)組是頭部插入菩鲜,所以 1 位置變?yōu)?B -> A 。這時第一個線程繼續(xù)做擴(kuò)容操作惦积,首先復(fù)制 A 接校,然后復(fù)制 B ,再判斷 B.next 是否為空時狮崩,由于第二個線程做了擴(kuò)容操作蛛勉,導(dǎo)致 B.next = A,所以在將 A 放到 B 前厉亏,A.next 又等于 B 董习,導(dǎo)致循環(huán)鏈表出現(xiàn)。
- HashTable
- 線程安全爱只,方法基本全用 Synchronized 修飾皿淋。
- 初始容量為 11 招刹,擴(kuò)容為 2n + 1 。
- 繼承 Dictionary 類窝趣。
- ConcurrentHashMap
- 線程安全的 HashMap疯暑。
- 1.7 采用分段鎖的形式加鎖;1.8 使用 Synchronized 和 CAS 實(shí)現(xiàn)同步哑舒,若數(shù)組的 Node 為空妇拯,則通過 CAS 的方式設(shè)置值,不為空則加在鏈表的第一個節(jié)點(diǎn)洗鸵。獲取第一個元素是否為空使用 Unsafe 類提供的 getObjectVolatile 保證可見性越锈。
- 對于讀操作,數(shù)組由 volatile 修飾膘滨,同時數(shù)組的元素為 Node甘凭,Node 的 K 使用 final 修飾,V 使用 volatile 修飾火邓,下一個節(jié)點(diǎn)也用 volatile 修飾丹弱,保證多線程的可見性。
- LinkedHashMap
LinkedHashMap 繼承自 HashMap铲咨,所以它的底層仍然是基于拉鏈?zhǔn)缴⒘薪Y(jié)構(gòu)即由數(shù)組和鏈表或紅黑樹組成躲胳。另外,LinkedHashMap 在上面結(jié)構(gòu)的基礎(chǔ)上纤勒,增加了一條雙向鏈表坯苹,使得上面的結(jié)構(gòu)可以保持鍵值對的插入順序。 - TreeMap
有序的 Map摇天,紅黑樹結(jié)構(gòu)北滥,可以自定義比較器來進(jìn)行排序。 - Collections.synchronizedMap 如何實(shí)現(xiàn) Map 線程安全闸翅?
基于 Synchronized 再芋,實(shí)際上就是鎖住了當(dāng)前傳入的 Map 對象。
3坚冀、Set(唯一值)
- HashSet
基于 HashMap 實(shí)現(xiàn)济赎,使用了 HashMap 的 K 作為元素存儲,V 為 new Object() 记某,在 add() 方法中如果兩個元素的 Hash 值相同司训,則通過 equals 方法比較是否相等。 - LinkedHashSet
LinkedHashSet 繼承于 HashSet液南,并且其內(nèi)部是通過 LinkedHashMap 來實(shí)現(xiàn)的壳猜。 - TreeSet
紅黑樹實(shí)現(xiàn)有序唯一。
三滑凉、Java 多線程
1统扳、synchronized
- 修飾代碼塊
底層實(shí)現(xiàn)喘帚,通過 monitorenter & monitorexit 標(biāo)志代碼塊為同步代碼塊。 - 修飾方法
底層實(shí)現(xiàn)咒钟,通過 ACC_SYNCHRONIZED 標(biāo)志方法是同步方法吹由。 - 修飾類 class 對象時,實(shí)際鎖在類的實(shí)例上面朱嘴。
- 單例模式
public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance(){
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 偏向鎖倾鲫,自旋鎖,輕量級鎖萍嬉,重量級鎖
- 通過 synchronized 加鎖乌昔,第一個線程獲取的鎖為偏向鎖,這時有其他線程參與鎖競爭壤追,升級為輕量級鎖玫荣,其他線程通過循環(huán)的方式嘗試獲得鎖,稱自旋鎖大诸。若果自旋的次數(shù)達(dá)到一定的閾值,則升級為重量級鎖贯卦。
- 需要注意的是资柔,在第二個線程獲取鎖時,會先判斷第一個線程是否仍然存活撵割,如果不存活贿堰,不會升級為輕量級鎖。
2啡彬、Lock
- ReentrantLock
- 基于 AQS (AbstractQueuedSynchronizer)實(shí)現(xiàn)羹与,主要有 state (資源) + FIFO (線程等待隊(duì)列) 組成。
- 公平鎖與非公平鎖:區(qū)別在于在獲取鎖時庶灿,公平鎖會判斷當(dāng)前隊(duì)列是否有正在等待的線程纵搁,如果有則進(jìn)行排隊(duì)。
- 使用 lock() 和 unLock() 方法來加鎖解鎖往踢。
- ReentrantReadWriteLock
- 同樣基于 AQS 實(shí)現(xiàn)腾誉,內(nèi)部采用內(nèi)部類的形式實(shí)現(xiàn)了讀鎖(共享鎖)和寫鎖 (排它鎖)。
- 非公平鎖吞吐量高
在獲取鎖的階段來分析峻呕,當(dāng)某一線程要獲取鎖時利职,非公平鎖可以直接嘗試獲取鎖,而不是判斷當(dāng)前隊(duì)列中是否有線程在等待瘦癌。一定情況下可以避免線程頻繁的上下文切換猪贪,這樣,活躍的線程有可能獲得鎖讯私,而在隊(duì)列中的鎖還要進(jìn)行喚醒才能繼續(xù)嘗試獲取鎖热押,而且線程的執(zhí)行順序一般來說不影響程序的運(yùn)行西傀。
3、volatile
- Java 內(nèi)存模型
- 在多線程環(huán)境下楞黄,保證變量的可見性池凄。使用了 volatile 修飾變量后,在變量修改后會立即同步到主存中鬼廓,每次用這個變量前會從主存刷新肿仑。
- 禁止 JVM 指令重排序。
- 單例模式雙重校驗(yàn)鎖變量為什么使用 volatile 修飾碎税?
禁止 JVM 指令重排序尤慰,new Object()分為三個步驟:申請內(nèi)存空間,將內(nèi)存空間引用賦值給變量雷蹂,變量初始化伟端。如果不禁止重排序,有可能得到一個未經(jīng)初始化的變量匪煌。
4责蝠、線程的五種狀態(tài)
1). New
一個新的線程被創(chuàng)建,還沒開始運(yùn)行萎庭。
2). Runnable
一個線程準(zhǔn)備就緒霜医,隨時可以運(yùn)行的時候就進(jìn)入了 Runnable 狀態(tài)。
Runnable 狀態(tài)可以是實(shí)際正在運(yùn)行的線程驳规,也可以是隨時可以運(yùn)行的線程肴敛。
多線程環(huán)境下,每個線程都會被分配一個固定長度的 CPU 計算時間吗购,每個線程運(yùn)行一會兒就會停止讓其他線程運(yùn)行医男,這樣才能讓每個線程公平的運(yùn)行。這些等待 CPU 和正在運(yùn)行的線程就處于 Runnable 狀態(tài)捻勉。
3). Blocked
例如一個線程在等待 I/O 資源镀梭,或者它要訪問的被保護(hù)代碼已經(jīng)被其他線程鎖住了,那么它就在阻塞 Blocked 狀態(tài)踱启,這個線程所需的資源到位后就轉(zhuǎn)入 Runnable 狀態(tài)丰辣。
4). Waiting(無限期等待)
如果一個線程在等待其他線程的喚醒,那么它就處于 Waiting 狀態(tài)禽捆。以下方法會讓線程進(jìn)入等待狀態(tài):
- Object.wait()
- Thread.join()
- LockSupport.park()
5). Timed Waiting(有期限等待)
無需等待被其他線程顯示喚醒笙什,在一定時間后有系統(tǒng)自動喚醒。
以下方法會讓線程進(jìn)入有限等待狀態(tài):
- Thread.sleep(sleeptime)
- Object.wait(timeout)
- Thread.join(timeout)
- LockSupport.parkNanos(timeout)
- LockSupport.parkUntil(timeout)
6). Terminated
一個線程正常執(zhí)行完畢胚想,或者意外失敗琐凭,那么就結(jié)束了。
5浊服、 wait() 與 sleep()
- 調(diào)用后線程進(jìn)入 waiting 狀態(tài)统屈。
- wait() 釋放鎖胚吁,sleep() 沒有釋放鎖。
- 調(diào)用 wait() 后需要調(diào)用 notify() 或 notifyAll() 方法喚醒線程愁憔。
- wait() 方法聲明在 Object 中腕扶,sleep() 方法聲明在 Thread 中。
6吨掌、 yield()
- 調(diào)用后線程進(jìn)入 runnable 狀態(tài)半抱。
- 讓出 CPU 時間片,之后有可能其他線程獲得執(zhí)行權(quán)膜宋,也有可能這個線程繼續(xù)執(zhí)行窿侈。
7、 join()
- 在線程 B 中調(diào)用了線程 A 的 Join()方法秋茫,直到線程 A 執(zhí)行完畢后史简,才會繼續(xù)執(zhí)行線程 B。
- 可以保證線程的順序執(zhí)行肛著。
- join() 方法必須在 線程啟動后調(diào)用才有意義圆兵。
- 使用 wait() 方法實(shí)現(xiàn)。
9枢贿、線程使用方式
- 繼承 Tread 類
- 實(shí)現(xiàn) Runnable 接口
- 實(shí)現(xiàn) Callable 接口:帶有返回值
10殉农、Runnable 和 Callable 比較
- 方法簽名不同,
void Runnable.run()
,V Callable.call() throws Exception
- 是否允許有返回值萨咕,
Callable
允許有返回值 - 是否允許拋出異常,
Callable
允許拋出異常火本。 - 提交任務(wù)方式危队,
Callable
使用Future<T> submit(Callable<T> task)
返回 Future 對象,調(diào)用其 get() 方法可以獲得返回值钙畔,Runnable
使用void execute(Runnable command)
茫陆。
11、hapens-before
如果一個操作 happens-before 另一個操作擎析,那么第一個操作的執(zhí)行結(jié)果將對第二個操作可見簿盅,而且第一個操作的執(zhí)行順序排在第二個操作之前。
12揍魂、ThreadLocal
- 場景
主要用途是為了保持線程自身對象和避免參數(shù)傳遞桨醋,主要適用場景是按線程多實(shí)例(每個線程對應(yīng)一個實(shí)例)的對象的訪問,并且這個對象很多地方都要用到现斋。 - 原理
為每個線程創(chuàng)建變量副本喜最,不同線程之間不可見,保證線程安全庄蹋。使用 ThreadLocalMap 存儲變量副本瞬内,以 ThreadLocal 為 K迷雪,這樣一個線程可以擁有多個 ThreadLocal 對象。 - 實(shí)際
使用多數(shù)據(jù)源時虫蝶,需要根據(jù)數(shù)據(jù)源的名字切換數(shù)據(jù)源章咧,假設(shè)一個線程設(shè)置了一個數(shù)據(jù)源,這個時候就有可能有另一個線程去修改數(shù)據(jù)源能真,可以使用 ThreadLocal 維護(hù)這個數(shù)據(jù)源名字赁严,使每個線程持有數(shù)據(jù)源名字的副本,避免線程安全問題舟陆。
8误澳、線程池
1)、分類
- FixThreadPool 固定數(shù)量的線程池秦躯,適用于對線程管理忆谓,高負(fù)載的系統(tǒng)
- SingleThreadPool 只有一個線程的線程池,適用于保證任務(wù)順序執(zhí)行
- CacheThreadPool 創(chuàng)建一個不限制線程數(shù)量的線程池踱承,適用于執(zhí)行短期異步任務(wù)的小程序倡缠,低負(fù)載系統(tǒng)
- ScheduledThreadPool 定時任務(wù)使用的線程池,適用于定時任務(wù)
2)茎活、線程池的幾個重要參數(shù)
- int corePoolSize, 核心線程數(shù)
- int maximumPoolSize, 最大線程數(shù)
- long keepAliveTime, TimeUnit unit, 超過 corePoolSize 的線程的存活時長昙沦,超過這個時間,多余的線程會被回收载荔。
- BlockingQueue<Runnable> workQueue, 任務(wù)的排隊(duì)隊(duì)列
- ThreadFactory threadFactory, 新線程的產(chǎn)生方式
- RejectedExecutionHandler handler) 拒絕策略
3)盾饮、線程池線程工作過程
corePoolSize -> 任務(wù)隊(duì)列 -> maximumPoolSize -> 拒絕策略
核心線程在線程池中一直存活,當(dāng)有任務(wù)需要執(zhí)行時懒熙,直接使用核心線程執(zhí)行任務(wù)丘损。當(dāng)任務(wù)數(shù)量大于核心線程數(shù)時,加入等待隊(duì)列工扎。當(dāng)任務(wù)隊(duì)列數(shù)量達(dá)到隊(duì)列最大長度時徘钥,繼續(xù)創(chuàng)建線程,最多達(dá)到最大線程數(shù)肢娘。當(dāng)設(shè)置回收時間時呈础,核心線程以外的空閑線程會被回收。如果達(dá)到了最大線程數(shù)還不能夠滿足任務(wù)執(zhí)行需求橱健,則根據(jù)拒絕策略做拒絕處理而钞。
4)、線程池拒絕策略(默認(rèn)拋出異常)
|:---|:---|
| AbortPolicy | 拋出 RejectedExecutionException |
| DiscardPolicy | 什么也不做拘荡,直接忽略 |
| DiscardOldestPolicy | 丟棄執(zhí)行隊(duì)列中最老的任務(wù)笨忌,嘗試為當(dāng)前提交的任務(wù)騰出位置 |
| CallerRunsPolicy | 直接由提交任務(wù)者執(zhí)行這個任務(wù) |
5)、如何根據(jù) CPU 核心數(shù)設(shè)計線程池線程數(shù)量
- IO 密集型 2nCPU
- 計算密集型 nCPU+1
- 其中 n 為 CPU 核心數(shù)量,可通過
Runtime.getRuntime().availableProcessors()
獲得核心數(shù):官疲。 - 為什么加 1:即使當(dāng)計算密集型的線程偶爾由于缺失故障或者其他原因而暫停時袱结,這個額外的線程也能確保 CPU 的時鐘周期不會被浪費(fèi)。
- 其中 n 為 CPU 核心數(shù)量,可通過
四途凫、Java 虛擬機(jī)
1垢夹、Java 內(nèi)存結(jié)構(gòu)
- 堆
由線程共享,存放 new 出來的對象维费,是垃圾回收器的主要工作區(qū)域果元。 - 棧
線程私有,分為 Java 虛擬機(jī)棧和本地方法棧犀盟,存放局部變量表而晒、操作棧、動態(tài)鏈接阅畴、方法出口等信息倡怎,方法的執(zhí)行對應(yīng)著入棧到出棧的過程。 - 方法區(qū)
線程共享贱枣,存放已被加載的類信息监署、常量、靜態(tài)變量纽哥、即時編譯器編譯后的代碼等信息钠乏,JDK 1.8 中方法區(qū)被元空間取代,使用直接內(nèi)存春塌。
2晓避、Java 類加載機(jī)制
- 加載
加載字節(jié)碼文件。 - 鏈接
- 驗(yàn)證
驗(yàn)證字節(jié)碼文件的正確性只壳。 - 準(zhǔn)備
為靜態(tài)變量分配內(nèi)存俏拱。 - 解析
將符號引用(如類的全限定名)解析為直接引用(類在實(shí)際內(nèi)存中的地址)。
- 驗(yàn)證
- 初始化
為靜態(tài)變量賦初值吕世。
雙親委派模式
當(dāng)一個類需要加載時彰触,判斷當(dāng)前類是否被加載過梯投。已經(jīng)被加載的類會直接返回命辖,否則才會嘗試加載。加載的時候分蓖,首先會把該請求委派該父類加載器的
loadClass()
處理尔艇,因此所有的請求最終都應(yīng)該傳送到頂層的啟動類加載器BootstrapClassLoader
中。當(dāng)父類加載器無法處理時么鹤,才由自己來處理终娃。當(dāng)父類加載器為 null 時,會使用啟動類加載器BootstrapClassLoader
作為父類加載器蒸甜。
3棠耕、垃圾回收算法
- Mark-Sweep(標(biāo)記-清除)算法
標(biāo)記需要回收的對象余佛,然后清除,會造成許多內(nèi)存碎片窍荧。 - Copying(復(fù)制)算法
將內(nèi)存分為兩塊辉巡,只使用一塊,進(jìn)行垃圾回收時蕊退,先將存活的對象復(fù)制到另一塊區(qū)域郊楣,然后清空之前的區(qū)域。 - Mark-Compact(標(biāo)記-整理)算法(壓縮法)
與標(biāo)記清除算法類似瓤荔,但是在標(biāo)記之后输硝,將存活對象向一端移動放椰,然后清除邊界外的垃圾對象。 - Generational Collection(分代收集)算法
分為年輕代和老年代如蚜,年輕代時比較活躍的對象,使用復(fù)制算法做垃圾回收。老年代每次回收只回收少量對象魂拦,使用標(biāo)記整理法芯勘。
4荷愕、典型垃圾回收器
-
CMS
- 簡介
以獲取最短回收停頓時間為目標(biāo)的收集器安疗,它是一種并發(fā)收集器蝶桶,采用的是 Mark-Sweep 算法。 - 場景
如果你的應(yīng)用需要更快的響應(yīng)恢共,不希望有長時間的停頓癣蟋,同時你的 CPU 資源也比較豐富疯搅,就適合適用 CMS 收集器幔欧。 - 垃圾回收步驟
- 初始標(biāo)記 (Stop the World 事件 CPU 停頓, 很短) 初始標(biāo)記僅標(biāo)記一下 GC Roots 能直接關(guān)聯(lián)到的對象晒骇,速度很快洪囤;
- 并發(fā)標(biāo)記 (收集垃圾跟用戶線程一起執(zhí)行) 并發(fā)標(biāo)記過程就是進(jìn)行 GC Roots 查找的過程;
- 重新標(biāo)記 (Stop the World 事件 CPU 停頓箍鼓,比初始標(biāo)記稍微長崭参,遠(yuǎn)比并發(fā)標(biāo)記短) 修正由于并發(fā)標(biāo)記時應(yīng)用運(yùn)行產(chǎn)生變化的標(biāo)記。
- 并發(fā)清理,標(biāo)記清除算法奄喂;
- 缺點(diǎn)
- 并發(fā)標(biāo)記時和應(yīng)用程序同時進(jìn)行跨新,占用一部分線程,所以吞吐量有所下降肖揣。
- 并發(fā)清除時和應(yīng)用程序同時進(jìn)行羊异,這段時間產(chǎn)生的垃圾就要等下一次 GC 再清除。
- 采用的標(biāo)記清除算法彤断,產(chǎn)生內(nèi)存碎片野舶,如果要新建大對象,會提前觸發(fā) Full GC 宰衙。
- 簡介
-
G1
- 簡介
是一款面向服務(wù)端應(yīng)用的收集器平道,它能充分利用多 CPU、多核環(huán)境供炼。因此它是一款并行與并發(fā)收集器巢掺,并且它能建立可預(yù)測的停頓時間模型,即可以設(shè)置 STW 的時間劲蜻。 - 垃圾回收步驟
1陆淀、初始標(biāo)記(stop the world 事件 CPU 停頓只處理垃圾);
2先嬉、并發(fā)標(biāo)記(與用戶線程并發(fā)執(zhí)行)轧苫;
3、最終標(biāo)記(stop the world 事件 ,CPU 停頓處理垃圾)疫蔓;
4含懊、篩選回收(stop the world 事件 根據(jù)用戶期望的 GC 停頓時間回收) - 特點(diǎn)
- 并發(fā)與并行
充分利用多核 CPU ,使用多核來縮短 STW 時間衅胀,部分需要停頓應(yīng)用線程的操作岔乔,仍然可以通過并發(fā)保證應(yīng)用程序的執(zhí)行。 - 分代回收
新生代滚躯,幸存帶雏门,老年代 - 空間整合
總體看是采用標(biāo)記整理算法回收嘿歌,每個 Region 大小相等,通過復(fù)制來回收茁影。 - 可預(yù)測的停頓時間
使用 -XX:MaxGCPauseMillis=200 設(shè)置最長目標(biāo)暫停值宙帝。
- 并發(fā)與并行
- 簡介
在 Java 語言中,可作為 GC Roots 的對象包括 4 種情況:
a) 虛擬機(jī)棧中引用的對象(棧幀中的本地變量表)募闲;
b) 方法區(qū)中類靜態(tài)屬性引用的對象步脓;
c) 方法區(qū)中常量引用的對象;
d) 本地方法棧中 Native 方法引用的對象浩螺。
五靴患、MySQL (Inno DB)
1、聚簇索引與非聚簇索引
- 都使用 B+ 樹作為數(shù)據(jù)結(jié)構(gòu)
- 聚簇索引中數(shù)據(jù)存在主鍵索引的葉子結(jié)點(diǎn)中要出,得到 key 即得到 data 蚁廓;非聚簇索引的數(shù)據(jù)存在單獨(dú)的空間。
- 聚簇索引中輔助索引的葉子結(jié)點(diǎn)存的是主鍵厨幻;非聚簇索引中葉子結(jié)點(diǎn)存的是數(shù)據(jù)的地址相嵌;
- 聚簇索引的優(yōu)勢是找到主鍵就找到數(shù)據(jù),只需一次磁盤 IO 况脆;當(dāng) B+ 樹的結(jié)點(diǎn)發(fā)生變化時饭宾,地址也會發(fā)生變化,這時非聚簇索引需要更新所有的地址格了,增加開銷看铆。
2、為何使用 B 樹做索引而不是紅黑樹盛末?
索引很大弹惦,通常作為文件存儲在磁盤上面,每次檢索索引都需要把索引文件加載進(jìn)內(nèi)存悄但,所以磁盤 IO 的次數(shù)是衡量索引數(shù)據(jù)結(jié)構(gòu)好壞的重要指標(biāo)棠隐。應(yīng)用程序在從磁盤讀取數(shù)據(jù)時,不只是讀取需要的數(shù)據(jù)檐嚣,還會連同其他數(shù)據(jù)以頁的形式做預(yù)讀來減少磁盤 IO 的次數(shù)助泽。數(shù)據(jù)庫的設(shè)計者將每個節(jié)點(diǎn)的大小設(shè)置為一頁的大小,同時每次新建節(jié)點(diǎn)時都重新申請一個頁嚎京,這樣檢索一個節(jié)點(diǎn)只需要一次 IO嗡贺,根據(jù)索引定位到數(shù)據(jù)只需要 h- 1(h 為 B 樹高度,根節(jié)點(diǎn)常駐內(nèi)存) 次 IO鞍帝,而 d (度诫睬,可以理解為寬度)與 h 稱反比,即 d 越大帕涌,高度就越小摄凡,所以樹越扁续徽,磁盤 IO 次數(shù)越少,即漸進(jìn)復(fù)雜度為 logdN 架谎,這也是為什么不選擇紅黑樹做索引的原因。前面可以得出結(jié)論辟躏,d 越大谷扣,索引的性能越好。節(jié)點(diǎn)由 key 和 data 組成捎琐,頁的大小一定会涎,key 和 data 越小,d 越大瑞凑。B + 樹去掉了節(jié)點(diǎn)內(nèi)的 data 域末秃,所以有更大的 d , 性能更好。
3籽御、最左前綴原則
在 MySQL 中练慕,可以指定多個列為索引,即聯(lián)合索引技掏。比如 index(name铃将,age) ,最左前綴原則是指查詢時精確匹配到從最左邊開始的一列或幾列(name哑梳;name&age)劲阎,就可以命中索引。如果所有列都用到了鸠真,順序不同悯仙,查詢引擎會自動優(yōu)化為匹配聯(lián)合索引的順序,這樣是能夠命中索引的吠卷。
4锡垄、什么情況下可以用到 B 樹索引
(1) 定義有主鍵的列一定要建立索引。因?yàn)橹麈I可以加速定位到表中的某行
(2) 定義有外鍵的列一定要建立索引祭隔。外鍵列通常用于表與表之間的連接偎捎,在其上創(chuàng)建索引可以加快表間的連接
(3) 對于經(jīng)常查詢的數(shù)據(jù)列最好建立索引。
① 對于需要在指定范圍內(nèi)快速或頻繁查詢的數(shù)據(jù)列序攘,因?yàn)樗饕呀?jīng)排序茴她,其指定的范圍是連續(xù)的,查詢可以利用索引的排序程奠,加快查詢的時間
② 經(jīng)常用在 where
子句中的數(shù)據(jù)列丈牢,將索引建立在 where
子句的集合過程中,對于需要加速或頻繁檢索的數(shù)據(jù)列瞄沙,可以讓這些經(jīng)常參與查詢的數(shù)據(jù)列按照索引的排序進(jìn)行查詢己沛,加快查詢的時間慌核。
5、事務(wù)隔離級別
-
Read uncommitted
讀未提交申尼,可能出現(xiàn)臟讀垮卓,不可重復(fù)讀,幻讀师幕。 -
Read committed
讀提交粟按,可能出現(xiàn)不可重復(fù)讀,幻讀霹粥。 -
Repeatable read
可重復(fù)讀灭将,可能出現(xiàn)臟讀。 -
Serializable
可串行化后控,同一數(shù)據(jù)讀寫都加鎖庙曙,避免臟讀,性能不忍直視浩淘。
Inno DB 默認(rèn)隔離級別為可重復(fù)讀級別捌朴,分為快照度和當(dāng)前讀,并且通過行鎖和間隙鎖解決了幻讀問題张抄。
6男旗、MVCC (多版本并發(fā)控制)
- 實(shí)現(xiàn)細(xì)節(jié)
- 每行數(shù)據(jù)都存在一個版本,每次數(shù)據(jù)更新時都更新該版本欣鳖。
- 修改時 Copy 出當(dāng)前版本隨意修改察皇,各個事務(wù)之間互不干擾。
- 保存時比較版本號泽台,如果成功(commit)什荣,則覆蓋原記錄;失敗則放棄 copy(rollback)。
- Inno DB 實(shí)現(xiàn)
在 InnoDB 中為每行增加兩個隱藏的字段,分別是該行數(shù)據(jù)創(chuàng)建時的版本號和刪除時的版本號仰冠,這里的版本號是系統(tǒng)版本號(可以簡單理解為事務(wù)的 ID),每開始一個新的事務(wù)桅锄,系統(tǒng)版本號就自動遞增,作為事務(wù)的 ID 样眠。通常這兩個版本號分別叫做創(chuàng)建時間和刪除時間友瘤。
詳細(xì)參考:《 臟讀、幻讀和不可重復(fù)讀》
六檐束、Spring 相關(guān)
1辫秧、Bean 的作用域
|:---|:---|
| 類別 | 說明 |
|singleton| 默認(rèn)在 Spring 容器中僅存在一個實(shí)例 |
|prototype| 每次調(diào)用 getBean() 都重新生成一個實(shí)例 |
|request| 為每個 HTTP 請求生成一個實(shí)例 |
|session| 同一個 HTTP session 使用一個實(shí)例,不同 session 使用不同實(shí)例 |
2被丧、Bean 生命周期
簡單來說四步:
- 實(shí)例化 Instantiation
- 屬性賦值 Populate
- 初始化 Initialization
- 銷毀 Destruction
在這四步的基礎(chǔ)上面盟戏,Spring 提供了一些拓展點(diǎn):
- Bean 自身的方法: 這個包括了 Bean 本身調(diào)用的方法和通過配置文件中 %3Cbean %3E 的 init-method 和 destroy-method 指定的方法
- Bean 級生命周期接口方法: 這個包括了 BeanNameAware绪妹、BeanFactoryAware、InitializingBean 和 DiposableBean 這些接口的方法
- 容器級生命周期接口方法:這個包括了 InstantiationAwareBeanPostProcessor 和 BeanPostProcessor 這兩個接口實(shí)現(xiàn)柿究,一般稱它們的實(shí)現(xiàn)類為“后處理器”邮旷。
- 工廠后處理器接口方法: 這個包括了 AspectJWeavingEnabler, ConfigurationClassPostProcessor, CustomAutowireConfigurer 等等非常有用的工廠后處理器接口的方法。工廠后處理器也是容器級的蝇摸。在應(yīng)用上下文裝配配置文件之后立即調(diào)用婶肩。
3、Spring AOP
實(shí)現(xiàn)方式兩種:
- JDK 動態(tài)代理:帶有接口的對象探入,在運(yùn)行期實(shí)現(xiàn)
- CGlib 靜態(tài)代理:在編譯期實(shí)現(xiàn)狡孔。
4懂诗、Spring 事務(wù)傳播行為
默認(rèn) PROPAGATION_REQUIRED 蜂嗽,如果存在一個事務(wù),則支持當(dāng)前事務(wù)殃恒。如果沒有事務(wù)則開啟一個新的事務(wù)植旧。
5、Spring IoC
6离唐、Spring MVC 工作流程
七病附、計算機(jī)網(wǎng)絡(luò)
1、TCP/IP 五層模型
2亥鬓、瀏覽器輸入地址后做了什么完沪?
3、三次握手與四次揮手
-
三次握手
image.png -
四次揮手
image.png
4嵌戈、TIME_WAIT 與 CLOSE_WAIT
5覆积、TCP 滑動窗口
TCP 流量控制,主要使用滑動窗口協(xié)議熟呛,滑動窗口是接受數(shù)據(jù)端使用的窗口大小宽档,用來告訴發(fā)送端接收端的緩存大小,以此可以控制發(fā)送端發(fā)送數(shù)據(jù)的大小庵朝,從而達(dá)到流量控制的目的吗冤。這個窗口大小就是我們一次傳輸幾個數(shù)據(jù)。對所有數(shù)據(jù)幀按順序賦予編號九府,發(fā)送方在發(fā)送過程中始終保持著一個發(fā)送窗口椎瘟,只有落在發(fā)送窗口內(nèi)的幀才允許被發(fā)送;同時接收方也維持著一個接收窗口侄旬,只有落在接收窗口內(nèi)的幀才允許接收降传。
6、TCP 粘包和拆包
-
現(xiàn)象
image.png - 產(chǎn)生原因
1勾怒、要發(fā)送的數(shù)據(jù)大于 TCP 發(fā)送緩沖區(qū)剩余空間大小婆排,將會發(fā)生拆包声旺。
2、待發(fā)送數(shù)據(jù)大于 MSS(最大報文長度)段只,TCP 在傳輸前將進(jìn)行拆包腮猖。
3、要發(fā)送的數(shù)據(jù)小于 TCP 發(fā)送緩沖區(qū)的大小赞枕,TCP 將多次寫入緩沖區(qū)的數(shù)據(jù)一次發(fā)送出去澈缺,將會發(fā)生粘包。
4炕婶、接收數(shù)據(jù)端的應(yīng)用層沒有及時讀取接收緩沖區(qū)中的數(shù)據(jù)姐赡,將發(fā)生粘包。 - 解決方式
1柠掂、發(fā)送端給每個數(shù)據(jù)包添加包首部项滑,首部中應(yīng)該至少包含數(shù)據(jù)包的長度,這樣接收端在接收到數(shù)據(jù)后涯贞,通過讀取包首部的長度字段枪狂,便知道每一個數(shù)據(jù)包的實(shí)際長度了。
2宋渔、發(fā)送端將每個數(shù)據(jù)包封裝為固定長度(不夠的可以通過補(bǔ) 0 填充)州疾,這樣接收端每次從接收緩沖區(qū)中讀取固定長度的數(shù)據(jù)就自然而然的把每個數(shù)據(jù)包拆分開來。
3皇拣、可以在數(shù)據(jù)包之間設(shè)置邊界严蓖,如添加特殊符號,這樣氧急,接收端通過這個邊界就可以將不同的數(shù)據(jù)包拆分開颗胡。
八、MQ 消息隊(duì)列
1态蒂、場景作用
削峰填谷杭措,異步解耦。
2钾恢、如何保證消息不被重復(fù)消費(fèi)呢手素?
這個問題可以換個思路,保證消息重復(fù)消費(fèi)瘩蚪,其實(shí)是保證程序的冪等性泉懦。無論消息如何重復(fù),程序運(yùn)行的結(jié)果是一致的疹瘦。比如消費(fèi)消息后做數(shù)據(jù)庫插入操作崩哩,為了防止消息重復(fù)消費(fèi),可以在插入前先查詢一下有沒有對應(yīng)的數(shù)據(jù)。
3邓嘹、怎么保證從消息隊(duì)列里拿到的數(shù)據(jù)按順序執(zhí)行酣栈?
消費(fèi)端在接收到消息后放入內(nèi)存隊(duì)列,然后對隊(duì)列中的消息進(jìn)行有序消費(fèi)汹押。
4矿筝、如何解決消息隊(duì)列的延時以及過期失效問題?消息隊(duì)列滿了以后該怎么處理棚贾?有幾百萬消息持續(xù)積壓幾小時窖维,說說怎么解決?
消息過期失效問題妙痹,如果消息一段時間不消費(fèi)铸史,導(dǎo)致過期失效了,消息就丟失了怯伊,只能重新查出丟失的消息琳轿,重新發(fā)送。
再來說消息積壓的問題:(思路是快速消費(fèi)掉積壓的消息)
- 首先排查消費(fèi)端問題震贵,恢復(fù)消費(fèi)端正常消費(fèi)速度利赋。
- 然后著手處理隊(duì)列中的積壓消息水评。
- 停掉現(xiàn)有的 consumer猩系。
- 新建一個 topic ,設(shè)置之前 10 倍的 partation中燥,之前 10 倍的隊(duì)列寇甸。
- 寫一個分發(fā)程序,將積壓的消息均勻的輪詢寫入這些隊(duì)列疗涉。
- 然后臨時用 10 倍的機(jī)器部署 consumer拿霉,每一批 consumer 消費(fèi) 1 個臨時的隊(duì)列。
- 消費(fèi)完畢后咱扣,恢復(fù)原有架構(gòu)绽淘。
消息隊(duì)列滿了:只能邊接收邊丟棄,然后重新補(bǔ)回丟失的消息闹伪,再做消費(fèi)沪铭。
4、如何保證消息的可靠性傳輸(如何處理消息丟失的問題)偏瓤?
kafka 為例:
- 消費(fèi)者丟了數(shù)據(jù):
每次消息消費(fèi)后杀怠,由自動提交 offset 改為手動提交 offset 。 - kafka 丟了消息:
比較常見的一個場景厅克,就是 kafka 某個 broker 宕機(jī)赔退,然后重新選舉 partition 的 leader 時。要是此時其他的 follower 剛好還有些數(shù)據(jù)沒有同步,結(jié)果此時 leader 掛了硕旗,然后大家選舉某個 follower 成為 leader 之后窗骑,不就少了一些數(shù)據(jù)。- 給 topic 設(shè)置replication.factor參數(shù):這個值必須大于 1漆枚,要求每個 partition 必須有至少兩個副本慧域。
- 在 kafka 服務(wù)端設(shè)置min.insync.replicas參數(shù):這個值必須大于 1,這個是要求一個 leader 至少感知到有至少一個 follower 還跟自己保持聯(lián)系浪读,沒掉隊(duì)昔榴,這樣才能確保 leader 掛了還有一個 follower。
- 在 producer 端設(shè)置acks=all:這個是要求每條數(shù)據(jù)碘橘,必須是寫入所有 replica 之后互订,才能認(rèn)為是寫成功了。
- 在 producer 端設(shè)置retries=MAX(很大很大很大的一個值痘拆,無限次重試的意思):這個是要求一旦寫入失敗仰禽,就無限重試,卡在這里纺蛆。
- 生產(chǎn)者丟了消息:
如果按照上述的思路設(shè)置了 ack=all吐葵,一定不會丟,要求是桥氏,你的 leader 接收到消息温峭,所有的 follower 都同步到了消息之后,才認(rèn)為本次寫成功了字支。如果沒滿足這個條件凤藏,生產(chǎn)者會自動不斷的重試,重試無限次堕伪。
九揖庄、Redis
1、數(shù)據(jù)類型
- String
常用命令: set,get,decr,incr,mget 等欠雌。
- Hash
常用命令: hget,hset,hgetall 等
- List
常用命令: lpush,rpush,lpop,rpop,lrange 等
可以通過 lrange 命令蹄梢,就是從某個元素開始讀取多少個元素,可以基于 list 實(shí)現(xiàn)分頁查詢富俄。
- Set
常用命令: sadd,spop,smembers,sunion 等
- Sort Set
常用命令: zadd,zrange,zrem,zcard 等
2禁炒、Redis 如何實(shí)現(xiàn) key 的過期刪除?
定期刪除和惰性刪除的形式蛙酪。
- 定期刪除
Redis 每隔一段時間從設(shè)置過期時間的 key 集合中齐苛,隨機(jī)抽取一些 key ,檢查是否過期桂塞,如果已經(jīng)過期做刪除處理凹蜂。 - 惰性刪除
Redis 在 key 被訪問的時候檢查 key 是否過期,如果過期則刪除。
3玛痊、Redis 的持久化機(jī)制
數(shù)據(jù)快照(RDB)+ 修改數(shù)據(jù)語句文件(AOF)
4汰瘫、如何解決 Redis 緩存雪崩和緩存穿透?
-
緩存雪崩
緩存同一時間大面積的失效擂煞,所以混弥,后面的請求都會落到數(shù)據(jù)庫上,造成數(shù)據(jù)庫短時間內(nèi)承受大量請求而崩掉对省。- 解決方式
- 事前:保證 Redis 集群的穩(wěn)定性蝗拿,發(fā)現(xiàn)機(jī)器宕機(jī)盡快補(bǔ)上,設(shè)置合適的內(nèi)存淘汰策略蒿涎。
- 事中:本地緩存 + 限流降級哀托,避免大量請求落在數(shù)據(jù)庫上。
- 事后:利用 Redis 持久化機(jī)制盡快恢復(fù)緩存劳秋。
- 解決方式
-
緩存穿透
一般是黑客故意去請求緩存中不存在的數(shù)據(jù)仓手,導(dǎo)致所有的請求都落到數(shù)據(jù)庫上,造成數(shù)據(jù)庫短時間內(nèi)承受大量請求而崩掉玻淑。- 解決方式
將不存在的數(shù)據(jù)列舉到一個足夠大的 map 上嗽冒,這樣遭到攻擊時,直接攔截 map 中的請求补履,請求到數(shù)據(jù)庫上面添坊。或是把不存在的也做緩存干像,值為 null 帅腌,設(shè)置過期時間驰弄。
- 解決方式
5麻汰、如何使用 Redis 實(shí)現(xiàn)消息隊(duì)列?
Redis 實(shí)現(xiàn)消息隊(duì)列依賴于 Redis 集群的穩(wěn)定性戚篙,通常不建議使用五鲫。
- Redis 自帶發(fā)布訂閱功能,基于 publish 和 subscribe 命令岔擂。
- 使用 List 存儲消息位喂,lpush,rpop 分別發(fā)送接收消息乱灵。
十塑崖、Nginx
Nginx 是一款輕量級的 Web 服務(wù)器/反向代理服務(wù)器及電子郵件(IMAP/POP3)代理服務(wù)器。 Nginx 主要提供反向代
理痛倚、負(fù)載均衡规婆、動靜分離(靜態(tài)資源服務(wù))等服務(wù)。
1、正向代理和反向代理
- 正向代理
代理客戶端訪問服務(wù)器抒蚜。典型:VPN - 反向代理
代替服務(wù)器接收客戶端請求掘鄙,然后轉(zhuǎn)發(fā)給服務(wù)器,服務(wù)器接收請求并將處理的結(jié)果通過代理服務(wù)器轉(zhuǎn)發(fā)給客戶端嗡髓。
2操漠、負(fù)載均衡
將請求分?jǐn)偟蕉嗯_機(jī)器上去,高并發(fā)饿这,增加吞吐量浊伙。
- 負(fù)載均衡算法
- 權(quán)重輪詢
- fair
- ip_hash
- url_hash
3、動靜分離
動靜分離是讓動態(tài)網(wǎng)站里的動態(tài)網(wǎng)頁根據(jù)一定規(guī)則把不變的資源和經(jīng)常變的資源區(qū)分開來长捧,動靜資源做好了拆分以后吧黄,我們就可以根據(jù)靜態(tài)資源的特點(diǎn)將其做緩存操作,這就是網(wǎng)站靜態(tài)化處理的核心思路唆姐。
4拗慨、Nginx 四個組成部分
- Nginx 二進(jìn)制可執(zhí)行文件:由各模塊源碼編譯出一個文件
- Nginx.conf 配置文件:控制 Nginx 行為
- acess.log 訪問日志: 記錄每一條 HTTP 請求信息
- error.log 錯誤日志:定位問題
歡迎訪問個人博客 獲取更多知識分享。