本文分為17個模塊,分別是:Java基礎、容器、多線程、反射、對象拷貝、Java web、異常、網絡、設計模式、算法、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、MyBatis、MySQL、Redis、JVM。
1. Java基礎
1. JDK和JRE有什么區別?
答:
JDK:Java Development Kit的簡稱,Java開發工具包,提供了Java的開發環境和運行環境。
JRE:Java Runtime Environment的簡稱,Java運行環境,為Java的運行提供了所需環境。
2. == 和 equals的區別是什么?
答:
對於基本類型和引用類型 == 的作用效果是不同的,如下所示:
基本類型:比較的是值是否相同;
引用類型:比較的是引用是否相同;
==對於基本類型來說是值比較,對於引用類型來說是比較的是引用;而equals默認情況下是引用比較,只是很多類重寫了equals方法,比如String、Integer等把它變成了值比較,所以一般情況下equals比較的是值是否相等。
3. 普通類和抽象類有哪些區別?
答:
①普通類不能包含抽象方法,抽象類可以包含抽象方法。
②抽象類是不能被實例化的,就是不能用new調出構造方法創建對象,普通類可以直接實例化。
③如果一個類繼承於抽象類,則該子類必須實現父類的抽象方法。如果子類沒有實現父類的抽象方法,則必須將子類也定義為abstract類。
4. 抽象類能使用final修飾嗎?
答:
不能,定義抽象類就是讓其他類繼承的,如果定義為final該類就不能被繼承,這樣彼此就會產生矛盾,所以final不能修飾抽象類。
5. 接口和抽象類有什么區別?類可以繼承多個類么,接口可以繼承多個接口么,類可以實現多個接口么
答:
接口和抽象類的區別:
①實現:抽象類的子類使用extend來繼承;接口必須使用implements來實現接口。
②構造函數:抽象類可以有構造函數;接口不能有。
③實現數量:類可以實現很多個接口;但只能繼承一個抽象類。
④訪問修飾符:接口中的方法默認使用public修飾;抽象類中的抽象方法可以使用Public和Protected修飾,如果抽象方法修飾符為Private,則報錯:The abstract method 方法名 in type Test can only set a visibility modifier,one of public or protected。
類不可以繼承多個類,接口可以繼承多個接口,類可以實現多個接口。
6. 兩個Integer的引用對象傳給一個swap方法在方法內部交換引用,返回后,兩個引用的值是否會發現變化?
答:
線程對變量的所有操作(讀取、賦值)都必須在工作內存中進行,而不能直接讀寫主內存中的變量。在swap方法內部交換引用,只會交換線程的工作內存中持有的方法參數,而工作內存中的方法參數是主內存中變量的副本,因此執行這樣的swap方法不會改變主內存中變量的指向。
public class ExchangeInteger{ public static void main(String[] args){ int A = 2; int B = 3; swap(A,B); System.out.println("A:" + A +"\nB:" + B); } public static void swap(int A,int B){ int C = A; B = C; A = B; } } 結果為: A:2 B:3
6. String屬於基礎的數據類型嗎?Java中的幾種基本數據類型是什么,各自占用多少字節?
答:
String不屬於基礎類型,基礎類型有8種:byte、short、int、long、float、double、char、boolean,而String屬於對象。
int 32bit / short 16bit
long 64bit / byte 8bit
char 16bit / float 32bit
double 64bit / boolean 1bit
7. String,StringBuffer,StringBuilder的區別是什么?String為什么是不可變的?
答:
①String是字符串常量,StringBuffer和StringBuilder都是字符串變量。后兩者的字符內容可變,而前者創建后內容不可變。
②String不可變是因為在JDK中String類被聲明為一個final類。
③StringBuffer是線程安全的,而StringBuilder是非線程安全的。
④執行速度:StringBuilder > StringBuffer > String
注意:線程安全會帶來額外的系統開銷,所以StringBuilder的效率比StringBuffer高。所以單線程環境下推薦使用StringBuilder,多線程環境下推薦使用StringBuffer。
8. String str = "i" 與 String str = new String("i")一樣嗎?
答:
不一樣,因為內存的分配方式不一樣。String str = "i"的方式,Java虛擬機會將其分配到常量池中,如果常量池中有"i",就返回"i"的地址,如果沒有就創建"i",然后返回"i"的地址;而String str = new String("i")則會被分到堆內存中新開辟一塊空間。
9. sting s = new string("abc")分別在堆棧上新建了哪些對象?
答:
棧:string s。
堆:new String("abc")
字符串池(方法區):"abc"
JVM中存在着一個字符串池,使用引號 創建文本的方式的String對象都會放入字符串池,可以提高效率。
String a = "abc";String b = "abc";//這兩句在字符串池,只創建一個實例對象。
String a = "ab" + "cd";//這一句在字符串池創建三個實例對象。
new 方式新建String對象則不會放入字符串池,放入堆。
9. 如何將字符串反轉?
答:
使用StringBuilder或者stringBuffer的reverse()方法。
// StringBuffer reverse StringBuffer stringBuffer = new StringBuffer(); stringBuffer. append("abcdefg"); System. out. println(stringBuffer. reverse()); // gfedcba // StringBuilder reverse StringBuilder stringBuilder = new StringBuilder(); stringBuilder. append("abcdefg"); System. out. println(stringBuilder. reverse()); // gfedcba
10. String類的常用方法都有哪些?
答:
①indexOf():返回指定字符的索引;
②charAt():返回指定索引處的字符;
③replace():字符串替換;
④trim():去除字符串兩端空白;
⑤split():分割字符串,返回一個分割后的字符串數組;
⑥getBytes():返回字符串的byte類型數組;
⑦length():返回字符串長度;
⑧toLowerCase():將字符串轉成小寫字符;
⑨toUpperCase():將字符串轉成大寫字符;
⑩subString():截取字符串;
⑪equals():字符串比較;
12. 在自己的代碼中,如果創建一個java.lang.String對象,這個對象是否可以被類加載器加載?為什么?
答:
不可以,雙親委派模式會保證父類加載器先加載類,就是BootStrap(啟動類)加載器加載jdk里面的java.lang.String類,而自定義的java.lang.String類永遠不會被加載到。
11. final在Java中有什么作用?
答:
①final修飾的類叫最終類,該類不能被繼承。
②final修飾的方法不能被重寫。
③final修飾的變量叫常量,常量必須初始化,初始化之后值就不能被修改。
12. Java中IO流分為幾種?
答:
①按功能來分:輸入流(input)和輸出流(output)。
②按類型來分:字節流和字符流。
③字節流和字符流的區別是:字節流按8為傳輸以字節為單位輸入輸出數據,字符流按16位傳輸以字符為單位輸入輸出數據。
13. BIO、NIO、AIO有什么區別?談談reactor模型。
答:
①BIO:Block IO同步阻塞IO,就是我們平常使用的傳統IO,它的特點是模式簡單使用方便,並發處理能力低。服務器實現模式為一個連接一個線程,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。
②NIO:New IO同步非阻塞IO,就是傳統IO的升級,客戶端和服務器通過Channel(通道)通訊,實現了多路復用。服務器實現模式為一個請求一個線程,即客戶端發送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求時才啟動一個線程進行處理。
③AIO:Asynchronous IO是NIO的升級,也叫NIO2,實現了異步非堵塞IO,異步IO的操作基於事件和回調機制。
reactor模型:反應器模式(事件驅動模式):當一個主體發生改變時,所有的屬體都得到通知,類似於觀察者模式。
14. Files的常用方法都有哪些?
答:
①Files.exists():檢測文件路徑是否存在。
②Files.createFile():創建文件。
③Files.createDirectory():創建文件夾。
④Files.delete():刪除一個文件或目錄。
⑤Files.copy():復制文件。
⑥Files.move():移動文件。
⑦Files.size():查看文件個數。
⑧Files.read():讀取文件。
⑨Files.write():寫入文件。
15. 講講類的實例化順序,比如父類靜態數據,構造函數,字段,子類靜態數據,構造函數,字段,當new的時候,他們的執行順序。
答:
類的實例化順序:先靜態再父子
父類靜態數據 -> 子類靜態數據 -> 父類字段 -> 子類字段 -> 父類構造函數 -> 子類構造函數
16. 請結合OO設計理念,談談訪問修飾符public、private、protected、default在應用設計中的作用。
答:
OO設計理念:封裝、繼承、多態
封裝,也就是把客觀事物封裝成抽象的類,並且類可以把自己的數據和方法只讓可信的類或者對象操作,對不可信的進行信息隱藏。所以我們可以通過public、private、protected、default來進行訪問控制。
17. 在jdk1.5中,引入了泛型,泛型的存在是用來解決什么問題。
答:
泛型的好處是在編譯的時候檢查類型安全,並且所有的強制轉換都是自動和隱式的,提高代碼的重用率。
18. Java8的新特性。
答:
Java8有十大新特性,具體參考:https://blog.csdn.net/yitian_66/article/details/81010434
19. 簡單說說你了解的類加載器。
答:
類加載器主要分為:引導類加載器(Bootstrap ClassLoader)、擴展類加載器(Extension ClassLoader)、系統類加載器(AppClassLoader)和自定義加載器(Custom ClassLoader)
20. int的取值范圍。
答:
-2^31 ~ 2^31-1,即-2147483648 ~ 2147483647。
2. Java集合容器
1. Java容器都有哪些?
答:
Java容器分為Collection和Map兩大類,其下又有很多子類,如下所示:
Collection
List
ArrayList
LinkedList
Vector
Stack
Set
HashSet
LinkedHashSet
TreeSet
Map
HashMap
LinkedHashMap
TreeMap
ConcurrentHashMap
HashTable
2. Collection和Collections有什么區別?
答:
Collection是一個集合接口,它提供了對集合對象進行基本操作的通用接口方法,所有集合都是他的子類,比如List、Set等。
Collections是一個包裝類,包含了很多靜態方法,不能被實例化,就像一個工具類,比如提供的排序方法:Collections.sort(list)。
3. List、Set、Map之間的區別是什么?
答:
List、Set、Map的區別主要體現在兩個方面:元素是否有序、是否允許元素重復。
4. Array和ArrayList有何區別?
答:
①Array可以存儲基本數據類型和對象,ArrayList只能存儲對象。
②Array是指固定大小的,而ArrayList大小是自動擴展的。
③Array內置方法沒有ArrayList多,比如addAll,removeAll,iteration等方法只有ArrayList有。
4. Vector,ArrayList,LinkedList的區別是什么?
答:
①Vector、ArrayList都是以類似數組的形式存儲在內存中,LinkedList則以鏈表的形式進行存儲。
②List中的元素有序、允許有重復的元素,Set中的元素無序,不允許有重復元素。
③Vector線程安全,ArrayList、LinkedList線程不安全。
④LinkedList適合指定位置插入、刪除操作,不適合查找,因為LinkedList是線性的數據存儲方式,所以需要移動指針從前往后依次查找;ArrayList、Vector適合查找,在隨機訪問的時候效率要高,不適合指定位置的插入、刪除操作,因為ArrayList增刪操作要影響數組內的其他數據的下標。
⑤ArrayList在元素填滿容器使會自動擴充容器大小的50%,而Vector則是100%,因此ArrayList更節省空間。
5. 如何實現數組和List之間的轉換?
答:
數組轉List:使用Arrays.asList(array)進行轉換。
List轉數組:使用List自帶的toArray()方法。
6. HashTable,HashMap,TreeMap區別?
答:
①HashTable線程同步,HashMap非線程同步。
②HashTable和HashMap有幾個主要不同:線程安全以及速度;HashMap速度比HashTable快。
③HashTable不允許<鍵,值>有空值,HashMap允許<鍵,值>有空值。
④HashTable使用Enumeration,HashMap使用Iterator。
⑤HashTable中hash數組的默認大小是11,增加方式的n*2+1,HashMap中hash數組的默認大小是16,之后每次擴充,容量變為原來的2倍。
⑥TreeMap能夠把它保存的記錄根據鍵排序,默認是按升序排序。
7. 如何決定使用HashMap還是TreeMap?
答:
對於在Map中插入、刪除、定位一個元素這類操作,HashMap是最好的操作,因為相對而言HashMap的插入會更快,但如果你要對一個key集合進行有序的遍歷,那TreeMap是更好的選擇。
8. hashMap的底層實現原理?
答:
①hashMap基於hashing原理,我們通過put()和get()方法存儲和獲取對象。當我們將鍵值對傳遞給put()方法時,它調用鍵對象的hashCode()方法來計算hash值,然后根據hash值找到bucket位置來存儲value值對象。當獲取對象時,通過鍵對象的equals()方法找到正確的鍵值對,然后返回值對象。HashMap使用鏈表來解決碰撞問題,當發生碰撞了,對象將會存儲在鏈表的下一個節點中。HashMap在每個鏈表節點中存儲鍵值對對象。
當兩個不同的鍵對象的hashCode相同時,我們稱之為hash沖突,HashMap的做法是用鏈表和紅黑樹存儲相同hash值的value。當hash沖突的個數比較少時,使用鏈表否則使用紅黑樹。鍵對象的equals()方法用來找到鍵值對。
②HashMap的存儲結構:數組+鏈表+紅黑樹(jdk1.8)
HashMap是一種可以快速存儲以及快速查找的鍵值容器,那么jdk是如何實現HashMap的快速存儲和快速查找呢?
從數組和鏈表以及二叉查找樹這三種數據結構說起:
1)數組:數組結構是連續的內存地址,數組的部分元素被連續存放在cpu緩存中,利用二分查找法,數組的時間復雜度位低到O(1),可見數組的查詢效率是非常高的。但是由於數組的內存占用嚴重,空間復雜度很高,所以數組的增刪操作效率將非常低下。
2)鏈表:鏈表內存地址比較分散,空間復雜度較低,在插入和刪除上效率較高。但是內存地址過於分散,導致查詢效率大大降低。
3)二叉樹在查詢效率上和排序后數組的二分查找效率完全相同,從根節點開始,到下面分支節點左邊的永遠比父節點的要小,右邊的比父節點大。二叉樹的元素過於分散,導致空間復雜度過大,在插入和刪除上會非常低效。為了解決這個問題,jdk使用了紅黑樹這種數據結構,而紅黑樹在時間復雜度上可以做到O(log n)的高效率。
綜合以上三種數據結構的特點,HashMap有效的利用了各個數據結構的長處。
實現快速存儲
快速存儲是鏈表和紅黑樹以及無移動添加數組元素的優勢。
HashMap中數組的索引是通過hashCode的無符號右移16位后異或然后取余獲得,hash公式如下
index = [ (hashCode) ^ (HashCode >>> 16) ] / 數組的長度
通過這樣的計算可以保證數組索引的分散。但是分散並不代表不會出現相同的index,也就是索引沖突(hash沖突)。在遇到索引沖突的時候,HashMap會在該索引的位置生成一個單向鏈表,將元素放置到next。但是我們知道鏈表這種數據結構在存儲方面高效,但是在查詢上會非常低效。所以HashMap在鏈表元素大於8個的時候,會自動將鏈表轉成紅黑樹,以達到查詢高效,插入也高效的目的。當然,在紅黑樹中元素個數小於一定數量,也會變回原來的鏈表結構,jdk設置這個數量為6個。
這樣不管在外圍"數組"上還是在"鏈表"上以及變成"紅黑樹"這種數據結構,HashMap都能做到快速存儲。
HashMap對數組的擴容觸發條件是數組元素達到長度的0.75(75%),使用這樣的觸發條件jdk是從時間和空間角度上思考的,為了這個條件更加容易被觸發,也要考慮到暫用過多內存浪費資源,75%非常理想化的觸發條件。
實現快速查找
HashMap的外圍數組這點毋庸置疑,查找效率絕對不會存在問題。索引沖突變成鏈表,元素數量僅僅只有8個的鏈表,查詢效率不需要考慮。大於8個元素后變成的紅黑樹,二叉查找樹的查詢效率和數組相當,這點也不需要質疑。綜合考慮在查詢方面HashMap,也做到了快速查找的特性。
9. HashMap、HashTable和ConcurrentHashMap的區別,HashTable和ConcurrentHashMap是如何實現線程安全的?
答:
①因為多線程環境下,使用HashMap進行put操作會引起死循環,導致CPU利用率接近100%,所以在並發情況下不能使用HashMap。
②HashTable容器使用synchronized來保證線程安全,但在線程競爭激烈的情況下,HashTable的效率非常低下。因為當一個線程訪問HashTable的同步方法時,其他線程訪問HashTable的同步方法時,可能會進入阻塞或者輪詢狀態。如線程1使用put進行添加元素,線程2不但不能使用put方法添加元素,並且也不能使用get方法來獲取元素,所以競爭越激烈效率越低。
總結:線程安全,效率和容器的大小成正比。容器數據量越大,效率越慢。
③由於HashTable效率低下,JDK1.5提出了ConcurrentHashMap替代HashTable。其實ConcurrentHashMap實現線程安全也是通過synchronized關鍵字來控制代碼同步來實現的,不同於HashTable的是ConcurrentHashMap在線程同步上更加細分化,它不會像HashTable那樣一把包攬的將所有數據都鎖住。ConcurrentHashMap采用分段鎖。底層數據結構實現原理和HashMap沒什么兩樣,都是數組+鏈表+紅黑樹。
總結:線程安全,效率相對於不如HashMap,但是和HashTable相比,效率得到很大的提升。
綜合考慮,如果使用線程安全容器,推薦使用ConcurrentHashMap。
10. 用hashmap實現redis有什么問題
答:
①容量問題:HashMap是有最大容量的。
②時效問題:redis可以持久化,也可以定時時間;hashmap不可以持久化。
③線程並發問題:hashmap不是線程安全的,可能會出現死鎖,死循環。
④可用ConcurrentHashMap。
11. 遍歷HashMap的三種方式
答:
①通過HashMap.entrySet鍵值對集合,再通過迭代器Iterator遍歷鍵值對集合得到key值和value值;
②通過HashMap.keySet()獲得鍵的Set集合,遍歷鍵的Set集合iterator()獲取值;
③通過HashMap.values()得到“值”的集合iterator(),遍歷“值”的集合;
12. HashMap如果只有一個寫其他全讀會出什么問題
答:
會出現死鎖,死循環等問題,因為hashmap不是線程安全的,建議使用ConcurrentHashMap。
13. ConcurrenHashMap求size是如何加鎖的,如果剛求完一段后這段發生了變化該如何處理
答:
Put等操作都是在單個Segment中進行的,但是ConcurrentHashMap有一些操作是在多個Segment中進行的,比如size操作,ConcurrentHashMap的size操作也采用了一種比較巧的方式,來盡量避免對所有的Segment都加鎖。
Segment中的有一個modCount變量,代表的是對Segment中元素的數量造成影響的操作的次數,這個值只增不減。
size操作就是遍歷了兩次Segment,每次記錄Segment的modCount值,然后將兩次的modCount進行比較,如果相同,則表示期間沒有發生過寫入操作,就將原先遍歷的結果返回,如果不相同,則把這個過程再重復做一次,如果再不相同,則就需要將所有的Segment都鎖住,然后一個一個遍歷了。
14. ConcurrentHashmap的分段鎖是如何加的?是不是分段越多越好
答:
ConcurrentHashMap是由Segment數組結構和HashEntry數組結構組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用於存儲鍵值對數據。一個ConcurrentHashMap里包含一個Segment數組,Segment的結構和HashMap類似,是一種數組和鏈表結構,一個Segment里包含一個HashEntry數組,每個HashEntry是一個鏈表結構的元素,每個Segment守護着一個HashEntry數組里的元素,當對HashEntry數組的數據進行修改時,必須首先獲得它對應的Segment鎖。
Java7的segment數量推薦是hardware thread數。ConcurrentHashMap的segment分段數並不是越多越好,根據你並發的線程數量決定,太多會導致性能降低。
14. Java8的ConcurrentHashmap為什么放棄了分段鎖,有什么問題嗎,如果你來設計,你如何設計。
答:
jdk8放棄了分段鎖而是用了Node鎖,減低鎖的粒度,提高性能,並使用CAS操作來確保Node的一些操作的原子性,取代了鎖。但是ConcurrentHashMap的一些操作使用了synchronized鎖,而不是ReentrantLock,雖然說jdk8的synchronized的性能進行了優化,但是我覺得還是使用ReentrantLock鎖能有更多的提高性能。
15. 有沒順序的Map實現類,如果有,他們是怎么保證有序的。順序的Map實現類:LinkedHashMap,TreeMap。
答:
LinkedHashMap是基於元素進入集合的順序或者被訪問的先后順序排序,TreeMap則是基於元素的固有順序(由Comparator或者Comparable確定)。
15. 說一下HashSet的實現原理?
答:
HashSet是基於HashMap實現的,HashSet底層使用HashMap來保存所有元素,因此HashSet的實現比較簡單,相關HashSet的操作,基本上都是直接調用底層HashMap的相關方法來完成,HashSet不允許重復的值。
17. 在Queue中poll()和remove()有什么區別?
答:
相同點:都是返回第一個元素,並在隊列中刪除返回的對象。
不同點:如果沒有元素remove()會直接拋出NoSuchElementException異常,而poll()會返回null。
18. 迭代器Iterator是什么?
答:
Iterator接口提供遍歷任何Collection的接口。我們可以從一個Collection中使用迭代器方法來獲取迭代器實例。迭代器取代了Java集合框架中的Enumeration,迭代器允許調用者在迭代過程中移除元素。
Iterator的特點是更加安全,因為它可以確保,在當前遍歷的集合元素被更改的時候,就會拋出ConcurrentModificationException異常。
19. 怎么確保一個集合不能被修改?
答:
可以使用Collections.unmodifiableCollection(Collection c)方法來創建一個只讀集合,這樣改變集合的任何操作都會拋出Java.lang.UnsupportedOperationException異常。
20. 並發包里了解哪些?
答:
- ConcurrentHashMap 線程安全的HashMap的實現;
- CopyOnWriteArrayList 線程安全且在讀操作時無鎖的ArrayList;
- CopyOnWriteArraySet 基於CopyOnWriteArrayList,不添加重復元素;
- ArrayBlockingQueue 基於數組、先進先出、線程安全,可實現指定時間的阻塞讀寫,並且容量可以限制;
- LinkedBlockingQueue 基於鏈表實現,讀寫各用一把鎖,在高並發讀寫操作都多的情況下,性能優於ArrayBlockingQueue。組成一個鏈表+兩把鎖+兩個條件。
3. Java多線程
1. 並行和並發有什么區別?
答:
並行:多個處理器或多核處理器同時處理多個任務。
並發:多個任務在同一個CPU核上,按細分的時間片輪流(交替)執行,從邏輯上來看那些任務是同時執行的。
2. 線程和進程的區別?
答:
一個程序下至少有一個進程,一個進程下至少有一個線程,一個進程下也可以有多個線程來增加程序的執行速度。
3. 守護線程是什么?
答:
守護線程是運行在后台的一種特殊進程。它獨立於控制終端並且周期性地執行某種任務或等待處理某些發生的事件。在Java中垃圾回收線程就是特殊的守護線程。
4. 多線程有幾種實現方式?
答:
有4種,分別是:
①繼承Thread類;
②實現Runnable接口;
③實現Callable接口通過FutureTask包裝器來創建Thread線程;
④通過線程池創建線程,使用線程池接口ExecutorService結合Callable、Future實現有返回結果的多線程。
5. 說一下Runnable和Callable有什么區別?
答:
Runnable沒有返回值,Callable可以拿到有返回值,Callable可以看作是Runnable的補充。
6. 線程有哪些狀態?
答:
線程的6種狀態:
①初始(NEW):新創建了一個線程對象,但還沒有調用start()方法;
②運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱為"運行"。線程對象創建后,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片后變為運行中狀態(running)。
③阻塞(BLOCKED):表示線程阻塞於鎖。
④等待(WAITING):進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)。
⑤超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間后自行返回。
⑥終止(TERMINATED):表示該線程已經執行完畢。
7. sleep()和wait()有什么區別?
答:
類的不同:sleep()來自Thread,wait()來自Object。
釋放鎖:sleep()不釋放鎖;wait()釋放鎖。
用法不同:sleep()時間會自動恢復;wait可以使用notify()/notifyAll()直接喚醒。
8. notify()和notifyAll()有什么區別?
答:
notifyAll()會喚醒所有的線程,notify()之后喚醒一個線程。notifyAll()調用后,會將全部線程由等待池移到鎖池,然后參與鎖的競爭,競爭成功則繼續執行,如果不成功則留在鎖池被釋放后再次參與競爭。而notify()只會喚醒一個線程,具體喚醒哪一個線程由虛擬機控制。
9. 線程的run()和start()有什么區別?
答:
start()方法用於啟動線程,run()方法用於執行線程的運行時代碼。run()可以重復調用,而start()只能調用一次。
10. 創建線程池有哪幾種方式?
答:
①newSingleThreadExecutor():它的特點在於工作線程數目被限制為1,操作一個無界的工作隊列,所以它保證了所有任務的都是被順序執行,最多會有一個任務處於活動狀態,並且不允許使用者改動線程池實例,因此可以避免其改變線程數目;
②newCachedThreadPool():它是一種用來處理大量短時間工作任務的線程池,具有幾個鮮明特點:它會試圖緩存線程並重用,當無緩存線程可用時,就會創建新的工作線程;如果線程閑置的時間超過60秒,則被終止並移出緩存;長時間閑置時,這種線程池,不會消耗什么資源。其內部使用SynchronousQueue作為工作隊列;
③newFixedThreadPool(int nThreads):重用指定數目(nThreads)的線程,其背后使用的是無界的工作隊列,任何時候最多有nThreads個工作線程是活動的。這意味着,如果任務數據超過了活動隊列數目,將在工作隊列中等待空閑線程出現;如果有工作線程退出,將會有新的工作線程被創建,以補足指定的數目nThreads;
④newSingleThreadScheduledExecutor():創建單線程池,返回ScheduledExecutorService,可以進行定時或周期性的工作調度;
⑤newScheduledThreadPool(int corePoolSize):和newSingleThreadScheduledExecutor()類似,創建的是個ScheduledExecutorService,可以進行定時或周期性的工作調度,區別在於單一工作線程還是多個工作線程;
⑥newWorkStrealingPool(int parallelism):這是一個經常被人忽略的線程池,Java8才加入這個創建方法,其內部會構建ForkJoinPool,利用Work-Stealing算法,並行地處理任務,不保證處理順序;
11. 線程池都有哪些狀態?
答:
①RUNNING;
②SHUTDOWN;
③STOP;
④TIDYING;
⑤TERMINATED;
12. 線程池中submit()和execute()方法有什么區別?
答:
①execute():只能執行Runnable類型的任務。
②submit():可以執行Runnable和Callable類型的任務。
Callable類型的任務可以獲取執行的返回值,而Runnable執行無返回值。
13. 在Java程序中怎么保證多線程的運行安全?
答:
方法一:使用安全類,比如Java.util.concurrent下的類。
方法二:使用自動鎖synchronized。
方法三:使用手動鎖Lock。
14. 什么是死鎖以及如何避免死鎖?
答:
死鎖產生:當線程A持有獨占鎖a,並嘗試去獲取獨占鎖b的同時,線程B持有獨占鎖b,並嘗試獲取獨占鎖a的情況下,就會發生AB兩個線程由於互相持有對方需要的鎖,而發生的阻塞現象,我們稱為死鎖。
線程死鎖是指由於兩個或者多個線程互相持有對方所需要的資源,導致這些線程處於等待狀態,無法前往執行。當線程進入對象的synchronized代碼塊時,便占有了資源,直到它退出該代碼塊或者調用wait方法,才釋放資源,在此期間,其他線程將不能進入該代碼塊。當線程互相持有對方所需要的資源時,會互相等待對方釋放資源,如果線程都不主動釋放所占有的資源,將產生死鎖。
避免死鎖:
①盡量使用tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),設置超時時間,超時可以退出,防止死鎖。
②盡量使用Java.util.concurrent並發類代替自己手寫鎖。
③盡量降低鎖的使用粒度,盡量不要幾個功能用同一把鎖。
④盡量減少同步的代碼塊。
15. volatile關鍵字的作用?
答:
volatile字段值在所有的線程和CPU緩存中必須保持同步。簡單講,你讀取的volatile關鍵字修飾的變量看到的隨時是自己的最新值,而無關乎哪個線程寫入的,線程1中對變量v的最新修改,對線程2是可見的。volatile字段本身保證了可見性,所有線程都能看到共享內存的最新狀態。
16. 可重入的讀寫鎖,可重入是如何實現的?
答:
可重入鎖又叫做遞歸鎖。
reentrant鎖意味着什么呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。
這相當於是模仿了synchronized中又可以嵌套一個synchronized這樣的場景。
17. threadLocal使用時注意的問題
答:
(ThreadLocal和Synchronized都用於解決多線程並發訪問。但是ThreadLocal與synchronized有本質的區別。
synchronized是利用鎖的機制,使變量或代碼塊在某一時刻只能被一個線程訪問。
而ThreadLocal為每一個線程都提供了變量的副本,使得每個線程在某一時間訪問到的並不是同一個對象,這樣就隔離了多個線程對數據的數據共享。而Synchronized卻正好相反,它用於在多個線程間通信時能夠獲得數據共享。)
ThreadLocal的內存泄漏問題:
當使用線程池來復用線程時,一個線程使用完並不會銷毀線程,那么分發的那個實例會一直綁定在這個線程上。由於WeakReference封裝了ThreadLocal,並作為了ThreadLocalMap的Entry的Key。如果在某些時候ThreadLocal對象被賦Null的話,弱應用會被GC收集,這樣就會導致Entry的Value對象找不到,線程被復用后如果有調用ThreadLocal.get/set方法的話,方法里面會去做遍歷清除,以[ThreadLocal=Null]為Key的Entry;但如果一直沒調用ThreadLocal.get/set方法的話就會導致內存泄漏了。
ThreadLocal的經典使用場景是數據庫連接和session管理等。
18. 說一下synchronized底層實現原理?
答:
synchronized是由一對monitorenter/monitorexit指令實現的,monitor對象是同步的基本實現單元。在Java6之前,monitor的實現完全是依靠操作系統內部的互斥鎖,因為需要進行用戶態到內核態的切換,所以同步操作是一個無差別的重量級操作,性能也很低。但在Java6的時候,Java虛擬機對此進行了大刀闊斧地改進,提供了三種不同的monitor實現,也就是常說的三種不同的鎖:偏向鎖(Biased Locking)、輕量級和重量級鎖,大大改進了其性能。
19. synchronized 和 volatile 的區別是什么?
答:
①volatile是變量修飾符;synchronized是修飾類、方法、代碼段。
②volatile僅能實現變量的修改可見性,不能保證原子性;而synchronized則可以保證變量的修改可見性和原子性。
③volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。
20. synchronized 和 Lock 有什么區別?
答:
①synchronized可以給類、方法、代碼塊加鎖;而lock只能給代碼塊加鎖。
②synchronized不需要手動獲取鎖和釋放鎖,使用簡單,發生異常會自動釋放鎖,不會造成死鎖;而lock需要自己加鎖和釋放鎖,如果使用不當沒有unLock()去釋放鎖就會造成死鎖。
③通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
21. synchronized 和 ReentrantLock 區別是什么?
答:
synchronized 早期的實現比較低效,對比ReentrantLock,大多數場景性能都相差較大,但是在Java6中對synchronized進行了非常多的改進。
主要區別如下:
①ReentrantLock使用起來比較靈活,但是必須有釋放鎖的配合動作;
②ReentrantLock必須手動獲取與釋放鎖,而synchronized可用於修飾方法、代碼塊等。
③ReentrantLock只適用於代碼塊鎖,而synchronized可用於修飾方法、代碼塊等。
④ReentrantLock標記的變量不會被編譯器優化;synchronized標記的邊兩個可以被編譯器優化。
22. 說一下atomic的原理?
答:
atomic主要利用CAS(Compare And Swap)和 volatile 和 native() 方法來保證原子操作,從而避免synchronized的高開銷,執行效率大為提升。
23. 怎么解決項目中的超賣現象?
答:
可以有條件有選擇的在讀操作上加鎖,比如可以對庫存做一個判斷,當庫存小於一個量時開始加鎖,讓購買者排隊,這樣一來就解決了超賣現象。或者加樂觀鎖,即更新庫存時,必須更新versionId字段,若兩個用戶同時使得versionId=3並提交,那么有一個用戶一定被回滾。
24. 樂觀鎖vs悲觀鎖
答:
悲觀鎖:總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。傳統的關系型數據庫里邊就用到了很多這種鎖機制,比如行鎖、表鎖等,讀鎖、寫鎖等,都是在做操作之前先上鎖。再比如Java里面的同步原語synchronized關鍵字的實現和Lock的實現類也是悲觀鎖。悲觀鎖適合寫操作多的場景,先加鎖可以保證寫操作時數據正確。
樂觀鎖:顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。樂觀鎖在java中是通過無鎖編程來實現的。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。樂觀鎖適合讀操作多的場景,不加鎖的特點能夠使其讀操作的性能大幅提升。
4. 反射
1. 什么是反射?反射的原理,反射創建類實例的三種方式是什么?
答:
反射是在運行狀態中,對於任意一個類,都能知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為Java語言的發射機制。
反射的原理:如果知道一個類的名稱/或者它的一個實例對象,就能把這個類的所有方法和變量的信息(方法名,變量名,方法,修飾符,類型,方法參數等等所有信息)找出來。
發射創建類實例的三種方式:
①Class.forName("com.A");
②new A().getClass();
③A.class;
2. 反射中,Class.forName和ClassLoader區別。
答:
class.forName()除了將類的.class文件加載到jvm中之外,還會對類進行解釋,執行類中的static塊。
classLoader只干一件事情,就是將.class文件加載到jvm中,不會執行static中的內容,只有在newInstance才會去執行static塊。
2. 什么是Java序列化?什么情況下需要序列化?
答:
Java序列化是為了保存各種對象在內存中的狀態,並且可以把保存的對象狀態再讀出來。
以下情況需要使用Java序列化:
①想把內存中的對象狀態保存到一個文件中或者數據庫中的時候;
②想用套接字在網絡上傳送對象的時候;
③想通過RMI(遠程方法調用)傳輸對象的時候。
3. Java如何實現序列化和反序列化的,底層原理是怎樣的?
答:
① JDK類庫中序列化和反序列化API:
(1) java.io.ObjectOutputStream:表示對象輸出流,它的writeObject(Object obj)方法可以對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中;
(2) java.io.ObjectInputStream:表示對象輸入流,它的readObject()方法源輸入流中讀取字節列,再把它們反序列化成為一個對象,並將其返回;
② 實現序列化的要求:只有實現了Serializable或Externalizable接口的類的對象才能被序列化,否則拋出異常!
③ 實現Java對象序列化與反序列化的方法
假定一個User類,它的對象需要序列化,可以有如下三種方法:
(1) 若User類僅僅實現了Serializable接口,則可以按照如下方式進行序列化和反序列化。ObjectOutputStream采用默認的序列化方式,對User對象的非transient的實例變量進行序列化;ObjectInputStream采用默認的反序列化方式,對User對象的非transient的實例變量進行反序列化。
(2) 若User類實現了Serializable接口,並且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputStream out),則采用以下方式進行序列化和反序列化。ObjectOutputStream調用User對象的writeObject(ObjectOutputStream out)的方法進行序列化;ObjectInputStream會調用User對象的readObject(ObjectInputStream in)的方法進行反序列化。
(3) 若User類實現了Externalnalizable接口,且User類必須實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照以下方式進行序列化與反序列化。ObjectOutputStream調用User對象的writeExternal(ObjectOutput out)的方法進行序列化;ObjectInputStream調用User對象的readExternal(ObjectInput in)的方法進行反序列化。
④ JDK類庫中序列化的步驟
一,創建一個對象輸出流,它可以包裝一個其它類型的目標輸出流,如文件輸出流:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\object.out"));
二,通過對象輸出流的writeObject()方法寫對象:
oos.writeObject(new User("xuliugen", "123456", "male"));
⑤ JDK類庫中反序列化的步驟
一,創建一個對象輸入流,它可以包裝一個其它類型輸入流,如文件輸入流:
ObjectInputStream ois= new ObjectInputStream(new FileInputStream("object.out"));
二,通過對象輸出流的readObject()方法讀取對象:
User user = (User) ois.readObject();
4. 動態代理是什么?有哪些應用?
答:
動態代理是運行時動態生成代理類。
動態代理的應用有spring aop、hibernate數據查詢、測試框架的后端mock、rpc,Java 注解對象獲取等。
5. 描述動態代理的幾種實現方式,分別說出相應的優缺點?它們的底層分別是怎么實現的(jdk動態代理與cglib動態代理的區別)
答:
動態代理有兩種實現方式,分別是:jdk動態代理和cglib動態代理。
jdk動態代理的前提是目標類必須實現一個接口,代理對象跟目標類實現一個接口,從而避過虛擬機的校驗。
cglib動態代理是繼承並重寫目標類,所以目標類和方法不能被聲明成final。
7. 如何在父類中為子類自動完成所有的hashcode和equals實現?這么做有何優劣。(說一說你對java.lang.Object對象中hashCode和equals方法的理解。在什么場景下需要重新實現這兩個方法。)
答:
父類的equals不一定滿足子類的equals需求。比如所有的對象都繼承Object,默認使用的是Object的equals方法,在比較兩個對象的時候,是看他們是否指向同一個地址。
但是我們的需求是對象的某個屬性相同,就相等了,而默認的equals方法滿足不了當前的需求,所以我們要重寫equals方法。
如果重寫了equals方法就必須重寫hashcode方法,否則就會降低map等集合的索引速度。
8. 有沒有可能,2個不相等的對象有同hashcode。
答:
有可能,最簡單的方法,百分百實現的方式就是重寫hashcode()。
5. Java對象拷貝模塊
1. 為什么要使用克隆?
答:
克隆的對象可能包含一些已經修改過的屬性,而new出來的對象的屬性都還是初始化時候的值,所以當需要一個新的對象來保存當前對象的”狀態“就靠克隆方法了。
2. 深拷貝和淺拷貝區別。
答:
淺拷貝只拷貝指針,深拷貝就是拷貝他的值,重新生成的對象。
6. JavaWeb與tomcat模塊
1. tomcat集群怎么保證同步
答:
① 同步方式:關於集群的具體同步機制,tomcat提供了兩種,一種是集群增量會話管理器,另一種是集群備份會話管理器。
② 集群增量會話管理器:這是一種全節點復制模式,全節點復制指的是集群中一個節點發生改變后會同步到其余全部節點。而非全節點復制,指的是集群中一個節點發生改變后,只同步到其余一個或部分節點。除了這一特點,集群增量會話管理器還具有只同步會話增量的特點,增量是以一個完整請求為周期,也就是說在一個請求被響應之前同步到其余節點上。
③ 集群備份會話管理器:全節點復制模式存在的一個很大的問題就是用於備份的網絡流量會隨着節點數的增加而急速增加,這也就是無法構建較大規模集群的原因。為了解決這個問題,tomcat提出了集群備份會話管理器。每個會話只有一個備份。這樣就可構建大規模的集群。
④ 同步組件:在上述無論是發送還是接收信息的過程中,使用到的組件主要有三個:Manager,Cluster,tribes。簡單來說,Manager的作用是將操作的信息記錄下來,然后序列化交給Cluster,接着Cluster是依賴於tribes將信息發送出去的。其余節點收到信息后,按照相反的流程一步步傳到Manager,經過反序列化之后使該節點同步傳遞過來的操作信息。
2. Spring IoC有什么好處
答:
參考資料:https://blog.csdn.net/xuefeiliuyuxiu/article/details/79181540
要了解控制反轉,需要先了解軟件設計的一個重要思想:依賴倒置原則。
什么事依賴倒置原則?假設我們設計一輛汽車:先設計輪子,然后根據輪子大小設計底盤,接着根據底盤設計車身,最后根據車身設計好整個汽車。這里就出現了一個“依賴”關系:汽車依賴車身,車身依賴底盤,底盤依賴輪子。但這種設計維護性很低。
換一種思路:我們先設計汽車的大概樣子,然后根據汽車的樣子來設計車身,根據車身來設計底盤,最后根據底盤來設計輪子。這時候,依賴關系就倒置過來了:輪子依賴底盤,底盤依賴車身,車身依賴汽車。
這時候,上司再說要改動輪子的設計,我們就只需要改動輪子的設計,而不需要動底盤、車身、汽車的設計了。
這就是依賴倒置原則——把原本的高層建築依賴底層建築“倒置”過來,變成底層建築依賴高層建築。高層建築決定需要什么,底層去實現這樣的需求,但是高層並不用管底層是怎么實現的。
控制反轉就是依賴倒置原則的一種代碼設計的思路。具體采用的方法就是所謂的依賴注入。這幾種概念的關系大概如下:
為了理解這幾個概念,我們還是用上面汽車的例子。只不過這次換成代碼,我們先定義四個Class,車、車身、底盤、輪胎。然后初始化這輛車,最后跑這輛車。代碼結構如下:
這樣,就相當於上面第一個例子,上層建築依賴下層建築——每一個類的構造函數都直接調用了底層代碼的構造函數。假設我們需要改動一下輪胎(Tire)類,把它的尺寸變成動態的,而不是一直都是30。我們需要像上面這樣改。
由於我們修改了輪胎的定義,為了讓整個程序正常運行,我們需要做以下改動:
由此我們可以看到,僅僅是為了修改輪胎的構造函數,這種設計卻需要修改整個上層所有類的構造函數!在軟件工程中,這樣的設計幾乎是不可維護的——在實際工程項目中,有的類可能會是幾千個類的底層,如果每次修改這個類,我們都要修改所有以它作為依賴的類,那軟件的維護成本就太高了。
所以我們需要進行控制反轉(IoC),即上層控制下層,而不是下層控制着上層。我們用依賴注入(Dependency Injection)這種方式來實現控制反轉。所謂依賴注入,就是把底層類作為參數傳入上層類,實現上層類對下層類的“控制”。這里我們用構造方法傳遞的依賴注入方式重新寫車類的定義:
這里我只需要修改輪胎類就行了,不用修改其他任何上層類。這顯然是更容易維護的代碼。
這里我們采用的構造函數傳入的方式進行的依賴注入。其實還有另外兩種方法:Setter傳遞和接口傳遞,核心思路都是一樣的,都是為了實現控制反轉。
那什么是控制反轉容器(IoC Container)呢?其實上面的例子中,對車類進行初始化的那段代碼發生的地方,就是控制反轉容器。
因為采用了依賴注入,在初始化的過程中就不可避免的會寫大量的new。這里IoC容器就解決了這個問題。這個容器可以自動對你的代碼進行初始化,你只需要維護一個Configuration(可以是xml,也可以是一段代碼),而不用每次初始化一輛車都要親手去寫那一大段初始化的代碼。這是引入IoC Container的第一個好處。
IoC Container的第二個好處是:我們在創建實例的時候不需要了解其中的細節。在上面的例子中,我們自己手動創建一個車instance時候,是從底層往上層new的:
這個過程中,我們需要了解整個Car/Framework/Bottom/Tire類構造函數是怎么定義的,才能一步一步new注入。
而IoC Container在進行這個工作的時候是反過來的,它先從最上層開始往下找依賴關系,到達最底層之后再往上一步一步new。
實際項目中,有Service Class可能是十年前寫的,有幾百個類作為它的底層。假設我們新寫一個API需要實例化這個Service,我們總不可能回去搞清楚這幾百個類的構造函數吧。IoC Container的這個特性就很完美的解決了這類問題——因為這個架構要求你在寫class的時候需要寫相應的Config文件,所以你要初始化很久以前的Service類的時候,前人都已經寫好了Config文件,你直接在需要用的地方注入這個Service就可以了。這大大增加了項目的可維護性且降低了開發難度。
6. 異常模塊
1. error和exception的區別,CheckedException,RuntimeException的區別。
答:
①Error(錯誤)表示系統級的錯誤和程序不必處理的異常,是java運行環境中的內部錯誤或者硬件問題。比如:內存資源不足等。對於這種錯誤,程序基本無能為力,除了退出運行外別無選擇,它是由Java虛擬機拋出的。
②Exception(違例)表示需要捕捉或者需要程序進行處理的異常,它處理的是因為程序設計的瑕疵而引起的問題或者在外的輸入等引起的一般性問題,是程序必須處理的。
Exception又分為運行時異常,受檢查異常。
1) RuntimeException(運行時異常),表示無法讓程序恢復的異常,導致的原因通常是因為執行了錯誤的操作,建議終止程序,因此,編譯器不檢查這些異常。
2) CheckedException(受檢查異常),是表示程序可以處理的異常,也即表示程序可以修復(由程序自己接受異常並且做出處理),所以稱之為受檢查異常。
2. 請列出5個運行時異常。
答:
NullPointerException、IndexOutOfBoundsException、ClassCastException、ArrayStoreException、BufferOverflowException
6. JVM
1. 什么情況下會發生棧內存溢出?
答:
棧(JVM Stack)存放主要是棧幀(局部變量表,操作數棧,動態鏈接,方法出口信息)的地方。
與線程棧相關的內存異常有兩個:
1)、StackOverflowError(方法調用層次太深,內存不夠新建棧幀)
2)、OutOfMemoryError(線程太多,內存不夠新建線程)
發生棧內存溢出的情況可能是:
①方法創建了一個很大的對象,如List,Array
②是否產生了循環調用方法、死循環,不停的產生棧幀,棧幀充滿了整個棧后溢出
③是否引用了較大的全局變量
2. JVM的內存結構,Eden和Survivor比例。
答:
JVM分為堆內存、方法區,棧內存、本地方法棧、程序計數器
具體參考:https://www.cnblogs.com/swordfall/p/10723938.html
3. JVM中一次完整的GC流程是怎樣的,對象如何晉升到老年代,說說你知道的幾種主要的JVM參數。
答:
對象誕生即新生代 -> eden,在進行minor gc過程中,如果依舊存活,移動到from,變成Survivor,進行標記代數,如此檢查一定次數后,晉升為老年代。
對象晉升老年代一共有三個可能:
①當對象達到成年,經歷過15次GC(默認15次,可配置),對象就晉升為老年代;
②大的對象會直接在老年代創建;
③新生代跟幸存區內存不足時,對象可能晉升到老生代;
jvm參數:
-Xms:初始堆大小
-Xmx:堆最大內存
-Xss:棧內存
-XX:PermSize 初始永久帶內存
-XX:MaxPermSize最大永久帶內存
4. 你們線上應用的JVM參數有哪些。
答:
-XX:PermSize=128M
-XX:MaxPermSize=512m
-Xms512m
-Xmx1024m
-XX:NewSize=64m
-XX:MaxNewSize=256m
-verbose:gc
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
4. 你知道哪幾種垃圾收集器,各自的優缺點,重點講下cms,包括原理,流程,優缺點
答:
https://www.cnblogs.com/swordfall/p/10734403.html
5. 垃圾回收算法的實現原理
答:
常用的垃圾回收算法有兩種:引用計數和可達性分析
引用計數:是增加一個字段來標識當前的引用次數,引用計數為0的就是可以GC的。但是引用計數不能解決循環引用的問題
可達性分析:就是通過一系列GC ROOT的對象作為起點,向下搜索,搜索所有沒有與當前對象GC ROOT有引用關系的對象。這些對象就是可以GC的
6. 當出現了內存溢出,你怎么排錯
答:
①首先控制台查看錯誤日志
②然后使用jdk自帶的jvisualvm工具查看系統的堆棧日志
③定位出內存溢出的空間:堆,棧還是永久代(jdk8以后不會出現永久代的內存溢出)
④如果是堆內存溢出,看是否創建了超大的對象
⑤如果是棧內存溢出,看是否創建了超大的對象,或者產生了死循環
7. JVM內存模型的相關知識了解多少,比如重排序,內存屏障,happen-before,主內存,工作內存等。
答:
重排序:jvm虛擬機允許在不影響代碼最終結果的情況下,可以亂序執行
內存屏障:可以阻擋編譯器的優化,也可以阻擋處理器的優化
happens-before原則:
①一個線程的A操作總是在B之前,那多線程的A操作肯定在B之前
②monitor再加鎖的情況下,持有鎖的肯定先執行
③volatile修飾的情況下,寫先於讀發生
④線程啟動在一起之前start
⑤線程死亡在一切之后end
⑥線程操作在一切線程中斷之前
⑦一個對象構造函數的結束都在該對象的finalizer的開始之前
⑧傳遞性,如果A肯定在B之前,B肯定在C之前,那A肯定是在C之前
主內存:所有線程共享的內存空間
工作內存:每個線程特有的內存空間
8. g1和cms區別,吞吐量優先和響應優先的垃圾收集器選擇。
答:
CMS:並發標記清除。它的主要步驟有:初始收集,並發標記,重新標記,並發清除(刪除),重置
G1:主要步驟:初始標記,並發標記,重新標記,復制清除(整理)
CMS的缺點是對CPU的要求比較高。G1是將內存化成了多塊,所有對內段的大小有很大的要求。
CMS是清除,所以會存在很多的內存碎片。G1是整理,所以碎片空間較小。
吞吐量優先:G1
響應優先:CMS
9. gc
7. 網絡編程
1. http協議格式中get和post的區別?
答:
Http協議定義了很多與服務器交互的方法,最基本的有4種,分別是GET, POST, PUT, DELETE。一個URL地址用於描述一個網絡上的資源,而HTTP中的GET, POST, PUT, DELETE就對應着這個資源的查,改,增,刪4個操作。我們最常見的就是GET和POST了。GET一般用於獲取/查詢資源信息,而POST一般用於更新資源信息。
GET和POST的區別:
①GET提交的數據會放在URL之后,以"?"分割URL和傳輸數據,參數之間以&相連,如EditPosts.aspx?name=test1&id=123456.(注意對於用戶登錄來說,get是不安全的,網頁直接顯示你的用戶名和密碼);POST方法是把提交的數據放在HTTP包的Body中;
②GET提交的數據大小有限制(因為瀏覽器對URL的長度有限制),而POST方法提交的數據沒有限制;
③GET方式需要使用Request.QueryString來取得變量的值,而POST方式通過Request.Form來獲取變量的值;
④GET方式提交數據,會帶來安全問題,比如一個登錄頁面,通過GET方式提交數據時,用戶名和密碼將出現在URL上,如果頁面可以被緩存或者其他人可以訪問這台機器,就可以從歷史記錄獲得該用戶的賬號和密碼。
2.
8. 算法
1. 二分查找算法
答:
用二分查找在已排序的數組中查看該數組是否含有一個特定的值。速度是非常快速的。
迭代方式:
public int BinarySearchIteration(int[] array, int key) { int begin = 0; int end = array.Length - 1; while (begin <= end) { int mid = begin + (end - begin) / 2; if (array[mid] > key) { end = mid - 1; } else if (array[mid] < key) { begin = mid + 1; } else { return mid; } } return -1; }
2. 快速排序算法
9. 數據庫
1. Mysql的三大引擎是啥?
答:
mysql經常使用的引擎有InnoDB,MyISAM,Memory,默認是InnoDB。
①InnoDB:磁盤表,支持事務,支持行級鎖,B+ Tree索引
ps:
優點:具有良好的ACID特性。適用於高並發,更新操作比較多的表。需要使用事務的表。對自動災難恢復有要求的表。
缺點:讀寫效率相對MYISAM比較差。占用的磁盤空間比較大。
使用場景:InnoDB用於事務處理應用程序,具有眾多特性,包括ACID事務支持。如果應用中需要執行大量的INSERT或UPDATE操作,則應該使用InnoDB,這樣可以提高多用戶並發操作的性能。
②MyISAM:磁盤表,不支持事務,支持表級鎖,B+ Tree索引
ps:
優點:占用空間小,措置速度快(相對InnoDB來說)。
缺點:不支持事務的完整性和並發性。
使用場景:MyISAM管理非事務表。它提供高速存儲和檢索,以及全文搜索能力。如果應用中需要執行大量的SELECT查詢,做很多count的計算,那么MyISAM是更好的選擇。
③MEMORY(Heap):內存表,不支持事務,表級鎖,Hash索引,不支持Blob、Text大類型
ps:
優點:速度要求快的,臨時數據
缺點:丟失以后,對項目整體沒有或者負面影響不大的時候。
2. Mysql中,讀多寫少可用什么引擎?
答:
MyISAM。
3. 假如要統計多個表應該用什么引擎?
答:
MyISAM
4. 了解nosql嗎?redis?
答:
nosql是非關系型的數據庫。redis為鍵值對的內存存儲數據庫。支持的鍵值數據類型為字符串類型,list類型,map類型,set類型,unset類型。
5.怎么保證redis和db中的數據一致
答:
如果僅僅查詢的話,緩存的數據和數據庫的數據是沒問題的。但是,當我們要更新時候呢?各種情況很可能就造成數據庫的數據和緩存的數據不一致了。
從理論上說,只要我們設置了鍵的過期時間,我們就能保證緩存和數據庫的數據最終是一致的。因為只要緩存數據過期了,就會被刪除。隨后讀的時候,因為緩存里沒有,就可以查數據庫的數據,然后將數據庫查出來的數據寫入到緩存中。除了設置過期時間,我們還需要做更多的措施來盡量避免數據庫與緩存處於不一致的情況發生。
一般來說,執行更新操作時,我們會有兩種選擇:
- 先操作數據庫,再刪除緩存
- 先刪除緩存,再操作數據庫
首先,要明確的是,無論我們選擇哪個,我們都希望這兩個操作要么同時成功,要么同時失敗。所以,這會演變成一個分布式事務的問題。所以,如果原子性被破環了,可能會有以下的情況:①操作數據庫成功了,刪除緩存失敗了;②刪除緩存成功了,操作數據庫失敗了。所以如果第一步已經失敗了,我們直接返回Exception出去就好了,第二部根本不會執行。
緩存為什么采取刪除而不是更新呢,原因如下:
① 高並發環境下,無論是先操作數據庫還是后操作數據庫而言,如果加上更新緩存,那就更加容易導致數據庫與緩存數據不一致問題。(刪除緩存直接和簡單很多);
② 如果每次更新了數據庫,都要更新緩存(這里指的是頻繁更新的場景,這會耗費一定的性能),倒不如直接刪除掉。等再次讀取時,緩存里沒有,再到數據庫里找,在數據庫找到之后再寫到緩存里邊去(體現懶加載)。
1) 先更新數據庫,再刪除緩存
如果第一步成功(操作數據庫),第二步失敗(刪除緩存),會導致數據庫里是新數據,而緩存是舊數據;如果第一步(操作數據庫)就失敗了,我們可以直接返回錯誤(Exception),不會出現數據不一致。
刪除緩存失敗的解決思路:
- 先刪除緩存,成功;
- 自己消費消息,獲得需要刪除的key
- 不斷重試刪除操作,直到成功
2) 先刪除緩存,再更新數據庫
如果第一步成功(刪除緩存),第二步失敗(更新數據庫),數據庫和緩存的數據還是一致的;如果第一步(刪除緩存)就失敗了,我們可以直接返回錯誤(Exception),數據庫和緩存的數據還是一致的。
但是在並發場景下,還是有問題的,比如線程A刪除了緩存;線程B查詢,發現緩存已不存在;線程B去數據庫查詢得到舊值,並把舊值寫入緩存;線程A將新值寫入數據庫。所以也會導致數據庫和緩存不一致的問題。
並發下解決數據庫與緩存不一致的思路:將刪除緩存、修改數據庫、讀取緩存等的操作積壓到隊列里邊,實現串行化。
3) 其他保障數據一致的方案與資料
可以用databus或者阿里的canal監聽binlog進行更新。
10. 操作系統
1. Linux系統下你關注過哪些內核參數,說說你知道的
答:
2. 用一行命令查看文件的最后五行,前五行。
答:
tail -n 5 filename / head -n 5 filename
3. 用一行命令輸出正在運行的java進程
答:
ps -ef | grep java
4.
總結
【參考資料】
https://blog.csdn.net/youanyyou/article/details/82142014
https://blog.csdn.net/zhanglei082319/article/details/87872156
https://juejin.im/post/5b97486cf265da0ac669347f#heading-0
https://www.cnblogs.com/kkdn/p/9039601.html
https://www.cnblogs.com/xyfer1018/p/10434827.html
http://blog.sina.com.cn/s/blog_73b4b91f0102xlkm.html
http://www.vcchar.com/thread-36870-1-1.html
https://www.cnblogs.com/kevingrace/p/5685355.html
https://www.cnblogs.com/lxli/p/8205854.html
https://blog.csdn.net/yitian_66/article/details/81010434 JAVA8新特性
https://learnku.com/articles/22363