題目答案總結並不是標準,僅供參考,若是有錯誤或者更好的看法,歡迎留言討論!java
一、什麼是事務?事務的特性(ACID)mysql
什麼是事務:事務是程序中一系列嚴密的操做,全部操做執行必須成功完成,不然在每一個操做所作的更改將會被撤銷,這也是事務的原子性(要麼成功,要麼失敗)。程序員
事務特性分爲四個:原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)、持續性(Durability)簡稱ACID。面試
一、原子性:事務是數據庫的邏輯工做單位,事務中包含的各操做要麼都作,要麼都不作。算法
二、一致性:事務執行的結果必須是使數據庫從一個一致性狀態變到另外一個一致性狀態。所以當數據庫只包含成功事務提交的結果時,就說數據庫處於一致性狀態。若是數據庫系統運行中發生故障,有些事務還沒有完成就被迫中斷,這些未完成事務對數據庫所作的修改有一部分已寫入物理數據庫,這時數據庫就處於一種不正確的狀態,或者說是不一致的狀態。sql
三、隔離性:一個事務的執行不能其它事務干擾。即一個事務內部的操做及使用的數據對其它併發事務是隔離的,併發執行的各個事務之間不能互相干擾。數據庫
四、持久性:也稱永久性,指一個事務一旦提交,它對數據庫中的數據的改變就應該是永久性的。接下來的其它操做或故障不該該對其執行結果有任何影響。json
二、事務的隔離級別有幾種,最經常使用的隔離級別是哪兩種?數組
併發過程當中會出現的問題:緩存
事務的隔離級別有4種:
一、未提交讀(Read uncommitted)
二、提交讀(Read committed)
三、可重複讀(Repeatable read)
四、串行化(Serializable)
三、分佈式緩存的典型應用場景?
四、MongoDB與Mysql的區別?
兩種數據庫的區別:
Mongodb的鮮明特徵:
Mongodb的優點:
Mongodb的缺陷:
Mongodb的應用場景:
不適用的場景:
關係型數據庫和非關係型數據庫的應用場景對比:
關係型數據庫適合存儲結構化數據,如用戶的賬號、地址:
NoSQL適合存儲非結構化數據,如文章、評論:
五、Mysql索引相關問題。
1)什麼是索引?
2)索引具體採用的哪一種數據結構呢?
3)InnoDb內存使用機制?
Innodb體系結構如圖所示:
Innodb關於查詢效率有影響的兩個比較重要的參數分別是innodb_buffer_pool_size,innodb_read_ahead_threshold:
能夠看出來,Mysql的緩衝池機制是能充分利用內存且有預加載機制,在某些條件下目標數據徹底在內存中,也可以具有很是好的查詢性能。
4)B+ Tree索引和Hash索引區別?
5)B+ Tree的葉子節點均可以存哪些東西嗎?
6)這二者有什麼區別嗎?
7)聚簇索引和非聚簇索引,在查詢數據的時候有區別嗎?
8)主鍵索引查詢只會查一次,而非主鍵索引須要回表查詢屢次(這個過程叫作回表)。是全部狀況都是這樣的嗎?非主鍵索引必定會查詢屢次嗎?
覆蓋索引(covering index)指一個查詢語句的執行只用從索引中就可以取得,沒必要從數據表中讀取。也能夠稱之爲實現了索引覆蓋。當一條查詢語句符合覆蓋索引條件時,MySQL只須要經過索引就能夠返回查詢所須要的數據,這樣避免了查到索引後再返回表操做,減小I/O提升效率。
如,表covering_index_sample中有一個普通索引 idx_key1_key2(key1,key2)。當咱們經過SQL語句:select key2 from covering_index_sample where key1 = 'keytest';的時候,就能夠經過覆蓋索引查詢,無需回表。
9)在建立索引的時候都會考慮哪些因素呢?
通常對於查詢機率比較高,常常做爲where條件的字段設置索引。
10)在建立聯合索引的時候,須要作聯合索引多個字段之間順序,這是如何選擇的呢?
在建立多列索引時,咱們根據業務需求,where子句中使用最頻繁的一列放在最左邊,由於MySQL索引查詢會遵循最左前綴匹配的原則,即最左優先,在檢索數據時從聯合索引的最左邊開始匹配。
因此當咱們建立一個聯合索引的時候,如(key1,key2,key3),至關於建立了(key1)、(key1,key2)和(key1,key2,key3)三個索引,這就是最左匹配原則。
11)你知道在MySQL 5.6中,對索引作了哪些優化嗎?
12)如何知道索引是否生效?
explain顯示了MySQL如何使用索引來處理select語句以及鏈接表。能夠幫助選擇更好的索引和寫出更優化的查詢語句。使用方法,在select語句前加上explain就能夠了。
13)那什麼狀況下會發生明明建立了索引,可是執行的時候並無經過索引呢?
在一條單表查詢語句真正執行以前,MySQL的查詢優化器會找出執行該語句全部可能使用的方案,對比以後找出成本最低的方案。這個成本最低的方案就是所謂的執行計劃。優化過程大體以下:
14)爲何索引結構默認使用B+Tree,而不是Hash,二叉樹,紅黑樹?
六、如何優化MySQL?
MySQL優化大體能夠分爲三部分:索引的優化、SQL語句優化和表的優化
索引優化能夠遵循如下幾個原則:
SQL語句優化,能夠經過explain查看SQL的執行計劃,優化語句原則能夠有:
數據庫表優化
七、爲何任何查詢都不要使用SELECT *?
Java實現多線程有幾種方式?
有三種方式:
線程的生命週期
一、新建狀態(New):新建立了一個線程對象。
二、就緒狀態(Runnable):線程對象建立後,其餘線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
三、運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
四、阻塞狀態(Blocked):阻塞狀態是線程由於某種緣由放棄CPU使用權,暫時中止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的狀況分三種:
五、死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
start()方法和run()方法的區別?
Runnable接口和Callable接口的區別?
volatile關鍵字
volatile基本介紹:volatile能夠當作是synchronized的一種輕量級的實現,但volatile並不能徹底代替synchronized,volatile有synchronized可見性的特性,但沒有synchronized原子性的特性。可見性即用volatile關鍵字修飾的成員變量代表該變量不存在工做線程的副本,線程每次直接都從主內存中讀取,每次讀取的都是最新的值,這也就保證了變量對其餘線程的可見性。另外,使用volatile還能確保變量不能被重排序,保證了有序性。
當一個變量定義爲volatile以後,它將具有兩種特性:
volatile boolean isOK = false;
//假設如下代碼在線程A執行
A.init();
isOK=true;
//假設如下代碼在線程B執行
while(!isOK){
sleep();
}
B.init();
複製代碼
A線程在初始化的時候,B線程處於睡眠狀態,等待A線程完成初始化的時候纔可以進行本身的初始化。這裏的前後關係依賴於isOK這個變量。若是沒有volatile修飾isOK這個變量,那麼isOK的賦值就可能出如今A.init()以前(指令重排序,Java虛擬機的一種優化措施),此時A沒有初始化,而B的初始化就破壞了它們以前造成的那種依賴關係,可能就會出錯。
volatile使用場景:
若是正確使用volatile的話,必須依賴下如下種條件:
在如下兩種狀況下都必須使用volatile:
什麼是線程安全?
若是你的代碼在多線程下執行和在單線程下執行永遠都能得到同樣的結果,那麼你的代碼就是線程安全的。
線程安全的級別:
sleep方法和wait方法有什麼區別?
寫一個會致使死鎖的程序。
public class MyThread{
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(()->{
synchronized (lock1){
System.out.println("thread1 get lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2){
System.out.println("thread1 get lock2");
}
System.out.println("thread1 end");
}
}).start();
new Thread(()->{
synchronized (lock2){
System.out.println("thread2 get lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1){
System.out.println("thread2 get lock1");
}
System.out.println("thread2 end");
}
}).start();
}
}
複製代碼
類加載過程
一、類加載過程:加載->連接(驗證+準備+解析)->初始化(使用前的準備)->使用->卸載
具體過程以下:
1)加載:首先經過一個類的全限定名來獲取此類的二進制字節流;其次將這個字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構;最後在java堆中生成一個表明這個類的Class對象,做爲方法區這些數據的訪問入口。總的來講就是查找並加載類的二進制數據。
2)連接:
驗證:確保被加載類的正確性。
準備:爲類的靜態變量分配內存,並將其初始化爲默認值。
解析:把類中的符號引用轉換爲直接引用。
直接引用能夠是:
爲何要使用符號引用?
符號引用要轉換成直接引用纔有效,這也說明直接引用的效率要比符號引用高。那爲何要用符號引用呢?這是由於類加載以前,javac會將源代碼編譯成.class文件,這個時候javac是不知道被編譯的類中所引用的類、方法或者變量他們的引用地址在哪裏,因此只能用符號引用來表示,固然,符號引用是要遵循java虛擬機規範的。
還有一種狀況須要用符號引用,就例如前文舉得變量的符號引用的例子,是爲了邏輯清晰和代碼的可讀性。
3)爲類的靜態變量賦予正確的初始值。
二、類的初始化
1)類何時才被初始化:
2)類的初始化順序
三、類的加載
類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,而後在堆區建立一個這個類的java.lang.Class對象,用來封裝類在方法區類的對象。如:
類的加載的最終產品是位於堆區中的Class對象。Class對象封裝了類在方法區內的數據結構,而且向Java程序員提供了訪問方法區內的數據結構的接口。加載類的方式有如下幾種:
四、加載器
JVM的類加載是經過ClassLoader及其子類來完成的,類的層次關係和加載順序能夠由下圖來描述:
加載器介紹:
1)BootstrapClassLoader(啓動類加載器):
負責加載JAVA_HOME中jre/lib/rt.jar裏全部的class,加載System.getProperty(「sun.boot.class.path」)所指定的路徑或jar。
2)ExtensionClassLoader(標準擴展類加載器):
負責加載java平臺中擴展功能的一些jar包,包括JAVAHOME中jre/lib/rt.jar裏全部的class,加載System.getProperty(「sun.boot.class.path」)所指定的路徑或jar。2)ExtensionClassLoader(標準擴展類加載器):負責加載java平臺中擴展功能的一些jar包,包括JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目錄下的jar包。載System.getProperty(「java.ext.dirs」)所指定的路徑或jar。
3)AppClassLoader(系統類加載器):
負責加載classpath中指定的jar包及目錄中class。
4)CustomClassLoader(自定義加載器):
屬於應用程序根據自身須要自定義的ClassLoader,如tomcat、jboss都會根據j2ee規範自行實現。
類加載器的順序
五、類加載器之雙親委派模型
垃圾回收機制
Java內存區域劃分
咱們先來看看Java的內存區域劃分狀況,以下圖所示:
私有內存區的區域名和相應的特性以下表所示:
虛擬機棧中的局部變量表裏面存放了三個信息:
這個returnAddress和程序計數器有什麼區別?前者是指示JVM的指令執行到了哪一行,後者是指你的代碼執行到哪一行。
共享內存區(接下來主要講jdk1.7)的區域名和相應的特性以下表所示:
哪些內存須要回收?
私有內存區伴隨着線程的產生而產生,一旦線程停止,私有內存區也會自動消除,所以咱們在本文中討論的內存回收主要是針對共享內存區。
Java堆
新生代GC(Minor GC):指發生在新生代的垃圾收集動做,由於Java對象大都具有朝生夕滅的特性,因此Minor GC很是頻繁,通常回收速度也比較快。
老年代GC(Major GC/Full GC):指發生在老年代的GC,出現了Major GC,常常會伴隨至少一次的Minor GC (但非絕對,在Parallel Scavenge收集器的收集策略裏就有直接進行Major GC的策略選擇過程)。Major GC的速度通常會比Minor GC慢10倍以上。
新生代:剛剛新建的對象在Eden中,經歷一次Minor GC, Eden中的存活對象就被移動到第一塊survivor space S0,Eden被清空;等Eden區再滿了,就再觸發一次Minor GC, Eden和S0中的存活對象會被複制送入第二塊survivor space S1。S0和Eden被清空,而後下一輪S0與S1交換角色,如此循環往復。若是對象的複製次數達到16次,該對象就被送到老年代中。
爲何新生代內存須要有兩個Sruvivor區:
先不去想爲何有兩個Survivor區,第一個問題是,設置Survivor區的意義在哪裏?
若是沒有Survivor,Eden區每進行一次Minor GC,存活的對象就會被送到老年代。老年代很快被填滿,觸發Major GC(由於Major GC通常伴隨着Minor GC,也能夠看作觸發了Full GC)。老年代的內存空間遠大於新生代,進行一次Full GC消耗的時間比Minor GC長得多。你也許會問,執行時間長有什麼壞處?頻發的Full GC消耗的時間是很是可觀的,這一點會影響大型程序的執行和響應速度,更不要說某些鏈接會由於超時發生鏈接錯誤了。那咱們來想一想在沒有Survivor的狀況下,有沒有什麼解決辦法,能夠避免上述狀況:
顯而易見,沒有Survivor的話,上述兩種解決方案都不能從根本上解決問題。咱們能夠獲得第一條結論:Survivor的存在乎義,就是減小被送到老年代的對象,進而減小Full GC的發生,Survivor的預篩選保證,只有經歷16次Minor GC還能在新生代中存活的對象,纔會被送到老年代。
設置兩個Survivor區最大的好處就是解決了碎片化,下面咱們來分析一下。爲何一個Survivor區不行?
第一部分中,咱們知道了必須設置Survivor區。假設如今只有一個survivor區,咱們來模擬一下流程:
剛剛新建的對象在Eden中,一旦Eden滿了,觸發一次Minor GC,Eden中的存活對象就會被移動到Survivor區。這樣繼續循環下去,下一次Eden滿了的時候,問題來了,此時進行Minor GC,Eden和Survivor各有一些存活對象,若是此時把Eden區的存活對象硬放到Survivor區,很明顯這兩部分對象所佔有的內存是不連續的,也就致使了內存碎片化。
那麼,瓜熟蒂落的,應該創建兩塊Survivor區,剛剛新建的對象在Eden中,經歷一次Minor GC,Eden中的存活對象就會被移動到第一塊survivor space S0,Eden被清空;等Eden區再滿了,就再觸發一次Minor GC,Eden和S0中的存活對象又會被複制送入第二塊survivor space S1(這個過程很是重要,由於這種複製算法保證了S1中來自S0和Eden兩部分的存活對象佔用連續的內存空間,避免了碎片化的發生)。S0和Eden被清空,而後下一輪S0與S1交換角色,如此循環往復。若是對象的複製次數達到16次,該對象就會被送到老年代中。
老年代:若是某個對象經歷了幾回垃圾回收以後還存活,就會被存放到老年代中。老年代的空間通常比新生代大。
這個流程以下圖所示:
何時回收?
Java並無給咱們提供明確的代碼來標註一塊內存並將其回收。或許你會說,咱們能夠將相關對象設爲null或者用System.gc()。然而,後者將會嚴重影響代碼的性能,由於每一次顯示調用system.gc()都會中止全部響應,去檢查內存中是否有可回收的對象,這會對程序的正常運行形成極大威脅。
另外,調用該方法並不能保障JVM當即進行垃圾回收,僅僅是通知JVM要進行垃圾回收了,具體回收與否徹底由JVM決定。
生存仍是死亡
可達性算法:這個算法的基本思路是經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。
二次標記:在可達性分析算法中被判斷是對象不可達時不必定會被垃圾回收機制回收,由於要真正宣告一個對象的死亡,必須經歷兩次標記的過程。
若是發現對象不可達時,將會進行第一次標記,此時若是該對象調用了finalize()方法,那麼這個對象會被放置在一個叫F-Queue的隊列之中,若是在此隊列中該對象沒有成功拯救本身(拯救本身的方法是該對象有沒有被從新引用),
那麼GC就會對F-Queue隊列中的對象進行小規模的第二次標記,一旦被第二次標記的對象,將會被移除隊列並等待被GC回收,因此finalize()方法是對象逃脫死亡命運的最後一次機會。
在Java語言中,可做爲GC Roots的對象包括下面幾種:
GC的算法
引用計數法(Reference Counting):
給對象添加一個引用計數器,每過一個引用計數器值就+1,少一個引用就-1。當它的引用變爲0時,該對象就不能再被使用。它的實現簡單,可是不能解決互相循環引用的問題。
優勢:
缺點:
標記-清除(Mark-Sweep)算法:
分爲兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象(後續的垃圾回收算法都是基於此算法進行改進的)。
缺點:效率問題,標記和清除兩個過程的效率都不高;空間問題,會產生不少碎片。
複製算法:
將可用內存按容量劃分爲大小相等的兩塊,每次只用其中一塊。當這一塊用完了,就將還存活的對象複製到另一塊上面,而後把原始空間所有回收。高效、簡單。
缺點:將內存縮小爲原來的一半。
標記-整理(Mark-Compat)算法
標記過程與標記-清除算法過程同樣,但後面不是簡單的清除,而是讓全部存活的對象都向一端移動,而後直接清除掉端邊界之外的內存。
分代收集(Generational Collection)算法
新生代中,每次垃圾收集時都有大批對象死去,只有少許存活,就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。
老年代中,其存活率較高、沒有額外空間對它進行分配擔保,就應該使用「標記-整理」或「標記-清除」算法進行回收。
增量回收GC和並行回收GC這裏就不作具體介紹了,有興趣的朋友能夠自行了解一下。
垃圾收集器
Serial收集器:單線程收集器,表示在它進行垃圾收集時,必須暫停其餘全部的工做線程,直到它收集結束。"Stop The World"。
ParNew收集器:實際就是Serial收集器的多線程版本。
Parallel Scavenge收集器:該收集器比較關注吞吐量(Throughout)(CPU用於用戶代碼的時間與CPU總消耗時間的比值),保證吞吐量在一個可控的範圍內。
CMS(Concurrent Mark Sweep)收集器:CMS收集器是一種以獲取最短回收停頓時間爲目標的垃圾收集器,是基於「標記——清除」算法實現的。
其回收過程主要分爲四個步驟:
因爲初始標記和從新標記速度比較快,其它工做線程停頓的時間幾乎能夠忽略不計,因此CMS的內存回收過程是與用戶線程一塊兒併發執行的。初始標記和從新標記兩個步驟須要Stop the world;併發標記和併發清除兩個步驟可與用戶線程併發執行。「Stop the world」意思是垃圾收集器在進行垃圾回收時,會暫停其它全部工做線程,直到垃圾收集結束爲止。
CMS的缺點:
G1(Garbage First)收集器:G1收集器是一款成熟的商用的垃圾收集器,是基於「標記——整理」算法實現的。
其回收過程主要分爲四個步驟:
G1收集器的特色:
CMS收集器與G1收集器的區別:
JDK 1.8 JVM的變化
一、爲何取消方法區
二、JDK 1.8裏Perm區中的全部內容中字符串常量移至堆內存,其餘內容如類元信息、字段、靜態屬性、方法、常量等都移動到元空間內。
三、元空間
元空間(MetaSpace)不在堆內存上,而是直接佔用的本地內存。所以元空間的大小僅受本地內存限制
也可經過參數來設定元空間的大小:
除了上面兩個指定大小的選項之外,還有兩個與 GC 相關的屬性:
-XX:MinMetaspaceFreeRatio,在GC以後,最小的Metaspace剩餘空間容量的百分比,減小爲分配空間所致使的垃圾收集。
-XX:MaxMetaspaceFreeRatio,在GC以後,最大的Metaspace剩餘空間容量的百分比,減小爲釋放空間所致使的垃圾收集。
元空間的特色:
性能優化:
java自動裝箱拆箱總結
當基本類型包裝類與基本類型值進行==運算時,包裝類會自動拆箱。即比較的是基本類型值。
具體實現上,是調用了Integer.intValue()方法實現拆箱。
int a = 1;
Integer b = 1;
Integer c = new Integer(1);
System.out.println(a == b); //true
System.out.println(a == c); //true
System.out.println(c == b); //false
Integer a = 1;
會調用這個 Integer a = Integer.valueOf(1);
Integer已經默認建立了數值【-128到127】的Integer常量池
Integer a = -128;
Integer b = -128;
System.out.println(a == b); //true
Integer a = 128;
Integer b = 128;
System.out.println(a == b); //false
Java的數學計算是在內存棧裏操做的
c1 + c2 會進行拆箱,比較仍是基本類型
int a = 0;
Integer b1 = 1000;
Integer c1 = new Integer(1000);
Integer b2 = 0;
Integer c2 = new Integer(0);
System.out.println(b1 == b1 + b2); //true
System.out.println(c1 == c1 + c2); //true
System.out.println(b1 == b1 + a); //true
System.out.println(c1 == c1 + a); //true
複製代碼