1.java中的數據結構
數組、鏈表、哈希表、棧、堆、隊列、樹、圖
2.什麼是跨域?跨域的三要素
跨域指的是瀏覽器不能執行其他網站的腳本。它是由瀏覽器的同源策略造成的,是瀏覽器施加的安全限制
協議、域名、端口
注意:localhost和127.0.0.1雖然都指向本機,但也屬於跨域
3.tomcat三個默認端口及其作用
8005:這個端口負責監聽關閉tomcat的請求。
8009:接受其他服務器的請求
8080:用於監聽瀏覽器發送的請求
4.throw 和 throws 的區別?
throw:拋出一個異常。
throws:聲明一個異常。
5.説一下你熟悉的設計模式
單例模式: 保證被創建一次,節省系統開銷。
工廠模式: 解耦代碼。
觀察者模式: 定義了對象之間的一對多的依賴,這樣一來,當一個對象改變時,它的所有的依賴者都會收到通知並自動更新。
代理模式: 代理對象具備被代理對象的功能,並代替被代理對象完成相應操作,並能夠在操作執行的前後,對操作進行增強處理。
模板模式: 較少代碼冗餘。例如:redis模板。
6.實例化對象有哪幾種方式
① new
② clone()
③ 反射
④先序列化在反序列化
7.java中什麼樣的類不能被實例化
抽象類: abstract關鍵字修飾的類。
8.序列化和反序列化
序列化: 把對象轉為字節序列的過程,在傳遞和保存對象時,保證了對象的完整性和可傳遞性,便於在網絡傳輸和保存在本地文件中。
反序列化: 把字節序列轉為對象的過程,通過字節流的狀態和信息描述,來重建對象。
9.序列化的優點
將對象轉為字節流存儲到硬盤上,當JVM噶了的話,字節流還會在硬盤上等待,等待下一次JVM的啓動,把序列化的對象,通過反序列化為原來的對象,減少儲存空間和方便網絡傳輸(因為是二進制)。
10.你知道什麼是單點登錄嗎?
單點登錄(SSO:Single Sign On): 同一賬號在多系統中,只登錄一次,就可以訪問其他系統。多個系統,統一登錄。
列如:在一個公司下,有多個系統,比如淘寶和天貓,你登錄上淘寶,就不用再去登錄天貓了。
11.實現單點登錄的方式
① Cookie: 用cookie為媒介,存放用户憑證。登錄上父應用,返回一個加密的cookie,訪問子應用的時候,會對cookie解密校驗,通過就可以登錄。不安全和不能跨域免登。
② 分佈式session實現: 用户第一次登錄,會把用户信息記錄下來,寫入session,再次登錄查看session是否含有對應信息。session系統不共享,使用緩存等方式來解決。
③重定向: 父應用提供一個GET方式的登錄接口A,用户通過子應用重定向連接的方式訪問這個接口,如果用户還沒有登錄,則返回一個登錄頁面,用户輸入賬號密碼進行登錄,如果用户已經登錄了,則生成加密的token,並且重定向到子應用提供的驗證token的接口B,通過解密和校驗之後,子應用登錄當前用户,雖然解決了安全和跨域,但是沒前兩種簡單。
12.sso(單點登錄)與OAuth2.0(授權)的區別?
單點登錄: 就是一個公司多個子系統登錄問題。
OAuth2.0: 是授權問題,比如微信授權問題。是一種具體的協議。
13.如何防止表單提交
①js屏蔽提交按鈕。
②給數據庫添加唯一約束。
③利用Session防止表單重複提交。會有一個token標記,表單提交的時候攔截器會檢查是否一致,不一致就不通過。
④使用AOP切入實現。自定義註解,然後新增切入點,然後每次都記錄過期時間,然後做比較。
14.泛型是什麼?有什麼好處?
本質是參數化類型,也就是説所操作的數據類型被指定為一個參數。
好處:
①類型安全
②消除強制類型轉換
③提高性能
④提高代碼的複用性
15.值傳遞和引用傳遞
值傳遞: 函數調用時會把實際參數,複製一份到函數中,函數中對參數進行操作,並不會影響參數實際的值。
引用傳遞: 將實際參數的地址值傳遞到函數中,函數對參數進行操作,會影響到實際參數的值。
注意: java中不存在引用傳遞(即使傳的是對象,那也只是傳遞了對象的引用地址的副本,也屬於值傳遞)。
二.java集合
1.List、Set、Map的區別
List集合有序、可重複的單例集合。
Set集合無序、不可重複的單例集合。
Map集合無序、k不可重複,v可重複的雙例集合。
2.List、Set、Map常用集合有哪些?
List
vector: 底層是數組,方法加了synchronized來保證線程安全,所以效率較慢,使用ArrayList替代。
ArrayList: 線程不安全,底層是數組,因為數組都是連續的地址,所以查詢比較快。增刪比較慢,增會生成一個新數組,把新增的元素和原有元素放到新數組中,刪除會導致元素移動,所以增刪速度較慢。
LinkedList: 線程不安全,底層是鏈表,因為地址不是連續的,都是一個節點和一個節點相連,每次查詢都得重頭開始查詢,所以查詢慢,增刪只是斷裂某個節點對整體影響不大,所以增刪速度較快。
Set
HashSet: 底層是哈希表(數組+鏈表或數組+紅黑樹),在鏈表長度大於8時轉為紅黑樹,在紅黑樹節點小於6時轉為鏈表。其實就是實現了HashMap,值存入key,value是一個final修飾的對象。
TreeSet: 底層是紅黑樹結構,就是TreeMap實現,可以實現有序的集合。String和Integer可以根據值進行排序。如果是對象需要實現Comparator接口,重寫compareTo()方法制定比較規則。
LinkedHashSet: 實現了HashSet,多一條鏈表來記錄位置,所以是有序的。
Map<key,value>雙例結構
TreeMap: 底層是紅黑樹,key可以按順序排列。
HashMap: 底層是哈希表,可以很快的儲存和檢索,無序,大量迭代情況不佳。
LinkedHashMap: 底層是哈希表+鏈表,有序,大量迭代情況佳。
3.ArrayList的初始容量是多少?擴容機制是什麼?擴容過程是怎樣?
初始容量: 默認10,也可以通過構造方法傳入大小。
擴容機制: 原數組長度 + 原數組長度/2(源碼中是原數組右移一位,也就相當於除以2)
注意:擴容後的ArrayList底層數組不是原來的數組。
擴容過程: 因為ArrayList底層是數組,所以它的擴容機制和數組一樣,首先新建一個新數組,長度是原數組的1.5倍,然後調用Arrays.copyof()複製原數組的值,然後賦值給新數組。
4.什麼是哈希表
根據關鍵碼值(Key value)而直接進行訪問的數據結構,在一個表中,通過H(key)計算出key在表中的位置,H(key)就是哈希函數,表就是哈希表。
5.什麼是哈希衝突
不同的key通過哈希函數計算出相同的儲存地址,這就是哈希衝突。
6.解決哈希衝突
(1)開放地址法
如果發生哈希衝突,就會以當前地址為基準,再去尋找計算另一個位置,直到不發生哈希衝突。
尋找的方法有:① 線性探測 1,2,3,m
② 二次探測 1的平方,-1的平方,2的平方,-2的平方,k的平方,-k的平方,k<=m/2
③ 隨機探測 生成一個隨機數,然後從隨機地址+隨機數++。
(2)鏈地址法
衝突的哈希值,連到到同一個鏈表上。
(3)再哈希法(再散列方法)
多個哈希函數,發生衝突,就在用另一個算計,直到沒有衝突。
(4)建立公共溢出區
哈希表分成基本表和溢出表,與基本表發生衝突的都填入溢出表。
7.HashMap的hash()算法,為什麼不是h=key.hashcode(),而是key.hashcode()^ (h>>>16)
得到哈希值然後右移16位,然後進行異或運算,這樣使哈希值的低16位也具有了一部分高16位的特性,增加更多的變化性,減少了哈希衝突。
8.為什麼HashMap的初始容量和擴容都是2的次冪
因為計算元素存儲的下標是(n-1)&哈希值,數組初始容量-1,得到的二進制都是1,這樣可以減少哈希衝突,可以更好的均勻插入。
9.HashMap如果指定了不是2的次冪的容量會發生什麼?
會獲得一個大於指定的初始值的最接近2的次冪的值作為初始容量。
10.HashMap為什麼線程不安全
jdk1.7中因為使用頭插法,再擴容的時候,可能會造成閉環和數據丟失。
jdk1.8中使用尾插法,不會出現閉環和數據丟失,但是在多線程下,會發生數據覆蓋。(put操作中,在putVal函數裏) 值的覆蓋還有長度的覆蓋。
11.解決Hashmap的線程安全問題
(1)使用Hashtable解決,在方法加同步關鍵字,所以效率低下,已經被棄用。
(2)使用Collections.synchronizedMap(new HashMap<>()),不常用。
(3)ConcurrentHashMap(常用)
12.ConcurrentHashMap的原理
jdk1.7: 採用分段鎖,是由Segment(繼承ReentrantLock:可重入鎖,默認是16,併發度是16)和HashEntry內部類組成,每一個Segment(鎖)對應1個HashEntry(key,value)數組,數組之間互不影響,實現了併發訪問。
jdk1.8: 拋棄分段鎖,採用CAS(樂觀鎖)+synchronized實現更加細粒度的鎖,Node數組+鏈表+紅黑樹結構。只要鎖住鏈表的頭節點(樹的根節點),就不會影響其他數組的讀寫,提高了併發度。
13.為什麼用synchronized代替ReentrantLock
①節省內存開銷。ReentrantLock基於AQS來獲得同步支持,但不是每個節點都需要同步支持,只有鏈表頭節點或樹的根節點需要同步,所以使用ReentrantLock會帶來很大的內存開銷。
②獲得jvm支持,可重入鎖只是api級別,而synchronized是jvm直接支持的,能夠在jvm運行時做出相應的優化。
③在jdk1.6之後,對synchronized做了大量的優化,而且有多種鎖狀態,會從 無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖一步步轉換。
AQS (Abstract Queued Synchronizer): 一個抽象的隊列同步器,通過維護一個共享資源狀態( Volatile Int State )和一個先進先出( FIFO )的線程等待隊列來實現一個多線程訪問共享資源的同步框架。
14.HashMap為什麼使用鏈表
減少和解決哈希衝突,把衝突的值放在同一鏈表下。
15.HashMap為什麼使用紅黑樹
當數據過多,鏈表遍歷較慢,所以引入紅黑樹。
16.HashMap為什麼不一上來就使用紅黑樹
維護成本較大,紅黑樹在插入新的數據後,可能會進行變色、左旋、右旋來保持平衡,所以當數據少時,就不需要紅黑樹。
17.説説你對紅黑樹的理解
①根節點是黑色。
②節點是黑色或紅色。
③葉子節點是黑色。
④紅色節點的子節點都是黑色。
⑤從任意節點到其子節點的所有路徑都包含相同數目的黑色節點。
紅黑樹從根到葉子節點的最長路徑不會超過最短路徑的2倍。保證了紅黑樹的高效。
18.為什麼鏈表長度大於8,並且表的長度大於64的時候,鏈表會轉換成紅黑樹?
因為鏈表長度越長,哈希衝突概率就越小,當鏈表等於8時,哈希衝突就非常低了,是千萬分之一,我們的map也不會存那麼多數據,如果真要存那麼多數據,那就轉為紅黑樹,提高查詢和插入的效率。
19.為什麼轉成紅黑樹是8呢?而重新轉為鏈表閾值是6呢?
因為如果都是8的話,那麼會頻繁轉換,會浪費資源。
20.為什麼負載因子是0.75?
加載因子越大,填滿的元素越多,空間利用率越高,但發生衝突的機會變大了;
加載因子越小,填滿的元素越少,衝突發生的機會減小,但空間浪費了更多了,而且還會提高擴容rehash操作的次數。
“衝突的機會”與“空間利用率”之間,尋找一種平衡與折中。
又因為根據泊松分佈,當負載因子是0.75時,平均值時0.5,帶入可得,當鏈表為8時,哈希衝突發生概率就很低了。
21.什麼時候會擴容?
元素個數 > 數組長度 * 負載因子 例如 16 * 0.75 = 12,當元素超過12個時就會擴容。
鏈表長度大於8並且表長小於64,也會擴容
22.為什麼不是滿了擴容?
因為元素越多,空間利用率是高了,但是發生哈希衝突的機率也增加了。
23.擴容過程
jdk1.7: 會生成一個新table,重新計算每個節點放進新table,因為是頭插法,在線程不安全的時候,可能會出現閉環和數據丟失。
jdk1.8: 會生成一個新table,新位置只需要看(e.hash & oldCap)結果是0還是1,0就放在舊下標,1就是舊下標+舊數組長度。避免了對每個節點進行hash計算,大大提高了效率。e.hash是數組的hash值,,oldCap是舊數組的長度。
24.HashMap和Hashtable的區別
①HashMap,運行key和value為null,Hashtable不允許為null。
②HashMap線程不安全,Hashtable線程安全。
25.集合為什麼要用迭代器(Iterator)
更加安全,因為它可以確保,在當前遍歷的集合元素被更改的時候,就會拋出 ConcurrentModificationException 異常。
如果不用迭代器,只能for循環,還必須知道集合的數據結構,複用性不強。
三.多線程
1.線程是什麼?多線程是什麼?
線程: 是最小的調度單位,包含在進程中。
多線程: 多個線程併發執行的技術。
2.守護線程和用户線程
守護線程: jvm給的線程。比如:GC守護線程。
用户線程: 用户自己定義的線程。比如:main()線程。
拓展:
Thread.setDaemon(false)設置為用户線程
Thread.setDaemon(true)設置為守護線程
3.線程的各個狀態
新建(New): 新建一個線程。
就緒(Runnable): 搶奪cpu的使用權。
運行(Running): 開始執行任務。
阻塞(Blocked): 讓線程等待,等待結束進入就緒隊列。
死亡(Dead): 線程正常結束或異常結束。
4.線程相關的基本方法有 wait,notify,notifyAll,sleep,join,yield 等
wait(): 線程等待,會釋放鎖,用於同步代碼塊或同步方法中,進入等待狀態
sleep(): 線程睡眠,不會釋放鎖,進入超時等待狀態
yield(): 線程讓步,會使線程讓出cpu使用權,進入就緒狀態
join(): 指定的線程加入到當前線程,可以將兩個交替執行的線程合併為順序執行的線程。
notify(): 隨機喚醒一個在等待中的線程,進入就緒狀態。
notifyAll(): 喚醒全部在等待中的線程,進入就緒狀態。
5.wait()和sleep()的區別?
① wait() 來自Object,sleep()來自Thread。
② wait()會釋放鎖,sleep()不會釋放鎖。
③ wait()只能用在同步方法或代碼塊中,sleep()可以用在任何地方。
④ wait()不需要捕獲異常,sleep()需要捕獲異常。
6.為什麼 wait()、notify()、notifyAll()方法定義在 Object 類裏面,而不是 Thread 類?
① 鎖可以是任何對象,如果在Thread類中,那隻能是Thread類的對象才能調用上面的方法了。
② java中進入臨界區(同步代碼塊或同步方法),線程只需要拿到鎖就行,而並不關心鎖被那個線程持有。
③ 上面方法是java兩個線程之間的通信機制,如果不能通過類似synchronized這樣的Java關鍵字來實現這種機制,那麼Object類中就是定義它們最好的地方,以此來使任何Java對象都可以擁有實現線程通信機制的能力。
7.start()和run()的區別
start()方法: 是啓動線程,調用了之後線程會進入就緒狀態,一旦拿到cpu使用權就開始執行run()方法,不能重複調用start(),否則會報異常。
run()方法: 就相當於一個普通的方法而已。直接調用run()方法就還只有一個主線程,還是會順序執行,也可以重複調用run()方法。
8.實現多線程的方式
①繼承Thread類。
②實現Runnable接口
③實現Callable接口
④線程池
9.Runnable和Callable的區別
①Runnable沒有返回值,Callable有返回值。
②Runnable只能拋出異常,不能捕獲,Callable 能拋出異常,也能捕獲。
10.線程池的好處
① 線程是稀缺資源,使用線程池可以減少線程的創建和銷燬,每個線程都可重複使用。
② 可以根據系統的需求,調整線程池裏面線程的個數,防止了因為消耗內存過多導致服務器崩潰。
11.線程池的七大參數
corePoolSize: 核心線程數,創建不能被回收,可以設置被回收。
maximumPoolSize: 最大線程數。
keepAliveTime: 空閒線程存活時間。
unit: 單位。
workQueue: 等待隊列。
threadFactory: 線程工程,用於創建線程。
handler: 拒絕策略。
12.線程池的執行過程
①接到任務,判斷核心線程池是否滿了,沒滿執行任務,滿了放入等待隊列。
②等待隊列沒滿,存入隊列,等待執行,滿了去查看最大線程數。
③最大線程數沒滿,執行任務,滿了執行拒絕策略。
13.四大方法
①ExecutorService executor = Executors.newCachedThreadPool(): 創建一個緩存線程池,靈活回收線程,任務過多,會oom。
②ExecutorService executor = Executors.newFixedThreadPool(): 創建一個指定線程數量的線程池。提高了線程池的效率和線程的創建的開銷,等待隊列可能堆積大量請求,導致oom。
③ExecutorService executor = Executors.newSingleThreadPool(): 創建一個單線程,保證線程的有序,出現異常再次創建,速度沒那麼快。
④ExecutorService executor = Executors.newScheduleThreadPool(): 創建一個定長的線程池,支持定時及週期性任務執行。
14.四大拒絕策略
①new ThreadPoolExecutor.AbortPolicy(): 添加線程池被拒絕,會拋出異常(默認策略)。
②new ThreadPoolExecutor.CallerRunsPolicy(): 添加線程池被拒絕,不會放棄任務,也不會拋出異常,會讓調用者線程去執行這個任務(就是不會使用線程池裏的線程去執行任務,會讓調用線程池的線程去執行)。
③new ThreadPoolExecutor.DiscardPolicy(): 添加線程池被拒絕,丟掉任務,不拋異常。
④new ThreadPoolExecutor.DiscardOldestPolicy(): 添加線程池被拒絕,會把線程池隊列中等待最久的任務放棄,把拒絕任務放進去。
15.shutdown 和 shutdownNow 的區別?
① shutdown沒有返回值,shutdownNow會返回沒有執行完任務的集合。
②shutdown不會拋出異常,shutdownNow會拋出異常。
③shutdown會等待執行完線程池的任務在關閉,shutdownNow會給所以線程發送中斷信號,然後中斷任務,關閉線程池。
16.什麼是死鎖?
各進程互相等待對方手裏的資源,導致各進程都阻塞,無法向前推進的現象。
17.造成死鎖的四個必要條件
互斥: 當資源被一個線程佔用時,別的線程不能使用。
不可搶佔: 進程阻塞時,對佔用的資源不釋放。
不剝奪: 進程獲得資源未使用完,不能被強行剝奪。
循環等待: 若干進程之間形成頭尾相連的循環等待資源關係。
18.線程安全主要是三方面
原子性: 一個或多個操作,要麼全部執行,要麼全部不執行(執行的過程中是不會被任何因素打斷的)。
可見性: 一個線程對主內存的修改可以及時的被其他線程觀察到。
有序性: 程序執行的順序按照代碼的先後順序執行。
保證原子性
使用鎖 synchronized和 lock。
使用CAS (compareAndSet:比較並交換),CAS是cpu的併發原語)。
保證可見性
使用鎖 synchronized和 lock。
使用volatile關鍵字 。
保證有序性
使用 volatile 關鍵字
使用 synchronized 關鍵字。
19.volatile和synchronized的區別
① volatile僅能使用在變量級別的,synchronized可以使用在變量、方法、類級別的
② volatile不具備原子性,具備可見性,synchronized有原子性和可見性。
③ volatile不會造成線程阻塞,synchronized會造成線程阻塞。
④ volatile關鍵字是線程同步的輕量級實現,所以volatile性能肯定比synchronized要好。
20.synchronized和lock的區別
① synchronized是關鍵字,lock是java類,默認是不公平鎖(源碼)。
② synchronized適合少量同步代碼,lock適合大量同步代碼。
③ synchronized會自動釋放鎖,lock必須放在finally中手工unlock釋放鎖,不然容易死鎖。
21.JMM(java內存模型)
java內存模型,一個抽象的概念,不是真是存在,描述的是一種規則或規範,和多線程相關的規則。需要每個JVM都遵循。
22.JMM的約定
①線程解鎖前,必須把共享變量立即刷回主存。
②線程加鎖前,必須讀取主存中的最新值到工作內存中。
③加鎖和解鎖必須是同一把鎖。
23.JMM的八個命令
為了支持JMM,定義了8條原子操作,用於主存和工作內存的交互。
lock(鎖定): 作用於主內存的變量,把一個變量標識為一條線程獨佔狀態。
unlock(解鎖): 作用於主內存變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量才可以被其他線程鎖定。
read(讀取): 作用於主內存變量,把一個變量值從主內存傳輸到線程的工作內存中,以便隨後的load動作使用。
load(載入): 作用於工作內存的變量,它把read操作從主內存中得到的變量值放入工作內存的變量副本中。
use(使用): 作用於工作內存的變量,把工作內存中的一個變量值傳遞給執行引擎。
assign(賦值): 作用於工作內存的變量,它把一個從執行引擎接收到的值賦值給工作內存的變量。
store(存儲): 作用於工作內存的變量,把工作內存中的一個變量的值傳送到主內存中,以遍隨後的write的操作。
write(寫入): 作用於主內存的變量,它把store操作從工作內存中的一個變量的值傳送到主內存的變量中。
24.為什麼要有JMM,用來解決什麼問題?
解決由於多線程通過共享內存進行通信時,存在的本地內存數據不一致、編譯器會對代碼指令重排序、處理器會對代碼亂序執行等帶來的問題。
四.jvm
1.jvm是什麼?
java虛擬機,是實現java跨平台的核心組件。
2.jvm的作用
java中所有的類,必須被裝載到jvm中才能使用,裝載由類加載器完成,.class這個類型可以在虛擬機運行,但不是直接和操作系統交互,需要jvm解釋給操作系統,解釋的時候需要java類庫,這樣就能和操作系統交互。
3.java文件的加載過程
.java -> .class -> 類加載器 -> jvm
4.jdk、jre、jvm的區別
jdk: 包含java運行環境和開發環境、jvm、java類庫。
jre: 包含java運行環境和jvm、java類庫。
jvm: java虛擬機,是跨平台的核心組件。
5.類加載器的作用
將.class文件裝載到jvm中,實質就是把文件從硬盤寫到內存。
6.類加載器的類型
引導類加載器(Bootstrap ClassLoader): c++編寫,jvm自帶的加載器,負責加載java核心類庫,該加載器無法直接獲取。
拓展類加載器(Extension ClassLoader): 加載jre/lib/etc目錄下的jar包。
系統類加載器(Application ClassLoader): 加載當前項目目錄下的類或jar包,最常用的加載器。
自定義加載器(Custom ClassLoader): 開發人員自定義的。需要繼承ClassLoader
7.雙親委派機制的加載過程
①接到類加載的請求。
②向上委託給父類加載器,直到引導類加載器。
③引導類加載器檢查能否加載當前這個類,如果能,使用當前加載器,請求結束,如果不能,拋出異常,通知子加載器進行加載。
④重複③。
8.雙親委派機制的優缺點
優點:保證類加載的安全性,不管那個類被加載,都會被委託給引導類加載器,只有類加載器不能加載,才會讓子加載器加載,這樣保證最後得到的對象都是同樣的一個。
缺點:子加載器可以使用父加載器加載的類,而父加載器不能使用子加載器加載的類。
9.為什麼要打破雙親委派機制
子加載器可以使用父加載器加載的類,而父加載器不能使用子加載器加載的類。
例如:使用JDBC連接數據庫,需要用到 com.mysql.jdbc.Driver和DriverManager類。然而DriverManager被引導類加載器所加載,而com.mysql.jdbc.Driver被當前調用者的加載器加載,使用引導類加載器加載不到,所以要打破雙親委派機制。
10.打破雙親委派機制的方式
① 自定義類加載器,重寫loadclass方法。
② 使用線程上下文類(ServiceLoader:使父加載器可以加載子加載器的類)。
11.jvm的每個部分儲存的都是什麼
方法區(線程共享): 常量池、靜態(static)變量以及方法信息(方法名、返回值、參數、修飾符等)等。
堆(線程共享): 是虛擬機內存中最大的一塊,儲存的是實例對象和數組。
本地方法棧(線程不共享): 調用的本地方法,被native修飾的方法,java不能直接操作操作系統,所以需要native修飾的方法幫助。
虛擬機棧(線程不共享): 8大基本類型、對象引用、實例方法。
程序計數器(線程不共享): 每個線程啓動是都會創建一個程序計數器,保存的是正在執行的jvm指令,程序計數器總是指向下一條將被執行指令的地址。
12.內存溢出(oom)和棧溢出
內存溢出的原因: (1)內存使用過多或者無法垃圾回收的內存過多,使運行需要的內存大於提供的內存。
(2)長期持有某些資源並且不釋放,從而使資源不能及時釋放,也稱為內存泄漏。
解決: (1)進行jvm調優。-Xmx:jvm最大內存。-Xms:啓動初始內存。-Xmn:新生代大小。 -Xss:每個虛擬機棧的大小。
(2)使用專業工具測試。
手動製造: 一直new對象就ok。
棧溢出原有: 線程請求的棧容量大於分配的棧容量。
解決: (1)修改代碼 (2)調優 -Xss
手動製造: 一直調用實例方法。
13.垃圾回收的作用區域
作用在方法區和堆,主要實在堆中的伊甸園區。年輕代分為(伊甸園區和倖存區)
14.怎麼判斷對象是否可回收
可達性分析算法: 簡單來説就是一個根對象通過引用鏈向下走,能走到的對象都是不可回收的。可作為根對象有: 虛擬機棧的引用的對象,本地棧的引用的對象,方法區引用的靜態和常量對象。
引用計數算法: 每個對象都添加一個計數器,每多一個引用指向對象,計數器就加一,如果計數器為零,那麼就是可回收的。
15.四種引用類型 強引用 軟引用 弱引用 虛引用
強引用: 基於可達性分析算法,只有當對象不可達才能被回收,否則就算jvm滿了,也不會被回收,會拋出oom。
軟引用: 一些有用但是非必須的對象,當jvm即將滿了,會將軟引用關聯對象回收,回收之後如果內存還是不夠,會拋出oom。
弱引用: 不論內存是否夠,只要開始垃圾回收,軟引用的關聯對象就會被回收。
虛引用: 最弱的引用和沒有一樣,隨時可能被回收。
16.垃圾回收算法
(1)標記-清除算法(適用老年代): 先把可回收的對象進行標記,然後再進行清除。
優點: 算法簡單。
缺點: 產生大量的內存碎片,效率低。
(2)複製算法(適用年輕代): 把內存分成兩個相同的塊,一個是from,一個是to,每次只使用一個塊,當一個塊滿了,就把存活的對象放到另一個塊中,然後清空當前塊。主要用在年輕區中的倖存區。
優點: 效率較高,沒有內存碎片。
缺點: 內存利用率低。
(3)標記-整理算法(適用老年代): 標記-清除算法的升級版,也叫標記-壓縮算法,先進行標記,然後讓存活對象向一端移動,然後清除掉邊界以外的內存。
有點: 解決了內存利用率低和避免了內存碎片。
缺點: 增加了一個移動成本。
17.輕GC(Minor GC)和 重GC(Full GC)
輕GC: 普通GC,當新對象在伊甸園區申請內存失敗時,進行輕GC,會回收可回收對象,沒有被回收的對象進入倖存區,新對象分配內存極大部分都是在伊甸園區,所以這個區GC比較頻繁。一個對象經歷15次GC,會進入老年區,可以設置。
重GC: 全局GC,對整個堆進行回收,所以要比輕GC慢,因此要減少重GC,我們所説的jvm調優,大部分都是針對重GC。
18.什麼時候會發生重GC
①當老年區滿了會重GC:年輕區對象進入或創建大對象會滿。
②永久代滿了會重GC。
③方法區滿了會重GC。
④system.gc()會重GC 。
⑤輕GC後,進入老年代的大小大於老年代的可用內存會,第一次輕GC進入老年代要2MB,第二次的時候會判斷是否大於2MB,不滿足就會重GC。
五.鎖
1.悲觀鎖和樂觀鎖
悲觀鎖: 在修改數據時,一定有別的線程來使用,所以在獲取數據的時候會加鎖。java中的synchronized和Lock都是悲觀鎖。
樂觀鎖: 在修改數據時,一定沒有別的線程來使用,所以不會添加鎖。但是在更新數據的時候,會查看有沒有線程修改數據。比如:版本號和CAS原理(無鎖算法)。
2.悲觀鎖和樂觀鎖的場景
悲觀鎖: 更適合寫操作多的場景,因為先加鎖可以保證數據的正確。
樂觀鎖: 更適合讀操作多的場景,因為不加鎖會讓讀操作的性能提升。
3.自旋鎖和自適應自旋鎖
前言:因為線程競爭,會導致線程阻塞或者掛起,但是如果同步資源的鎖定時間很短,那麼阻塞和掛起的花費的資源就得不償失。
自旋鎖: 當競爭的同步資源鎖定時間短,就讓線程自旋,如果自旋完成後,資源釋放了鎖,那線程就不用阻塞,直接獲取資源,減少了切換線程的開銷。實現原理是CAS。
缺點:佔用了處理器的時間,如果鎖被佔用的時間短還好,如果長那就白白浪費了處理器的時間。所以要限定自旋次數(默認是10次,可以使用-XX:PreBlockSpin來更改)沒有成功獲得鎖,就應當掛起線程。
自適應自旋鎖: 自旋次數不固定,是由上一個在同一個鎖上的自旋時間和鎖擁有者的狀態決定。如果在同一個鎖對象上,自旋剛剛獲得鎖,並且持有鎖的線程在運行,那麼虛擬機會認為這次自旋也可能成功,那麼自旋的時間就會比較長,如果某個鎖,自旋沒成功獲得過,那麼可能就會直接省掉自旋,進入阻塞,避免浪費處理器時間。
4.無鎖、偏向鎖、輕量級鎖、重量級鎖
這四個鎖是專門針對synchronized的,在 JDK1.6 中,對 synchronized 鎖的實現引入了大量的優化,並且 synchronized 有多種鎖狀態。級別從低到高依次是:無鎖、偏向鎖、輕量級鎖和重量級鎖。鎖狀態只能升級不能降級。
無鎖: 就是樂觀鎖。
偏向鎖: 當只有一個線程訪問加鎖的資源,不存在多線程競爭的情況下,那麼線程不需要重複獲取鎖,這時候就會給線程加一個偏向鎖。(對比Mark Word解決加鎖問題,避免CAS操作)
輕量級鎖: 是指當鎖是偏向鎖的時候,被另外的線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,從而提高性能。(CAS+自旋)
重量級鎖: 若當前只有一個等待線程,則該線程通過自旋進行等待。但是當自旋超過一定的次數或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖升級為重量級鎖。(將除了擁有鎖的線程以外的線程都阻塞)