1.ArrayList和LinkedList的了解?
2.HashMap和Hashtable的了解谢床?
3.對于上面兩個容器的初始值和每次擴充容量大小扒腕,以及為什么HashMap的長度是那個規(guī)律?
4.HashMap多線程操作會導(dǎo)致什么問題萤悴?-
5.談?wù)剆ynchronized關(guān)鍵字的理解瘾腰?
6.原子性的理解?鎖的理解覆履?
7.JVM的理解蹋盆?Java1.8版本有什么了解?
8.什么是OOM硝全?StackOverflowError和OutOfMemoryError栖雾?
9.JVM的常用參數(shù)調(diào)優(yōu)?
10.內(nèi)存快照抓取和MAT分析hprof文件伟众?-
————————————————
- ArrayList和LinkedList的了解析藕?
ArrayList底層使用的是Object數(shù)組,所以執(zhí)行數(shù)據(jù)的插入和刪除元素的時間可能會移動相應(yīng)的元素凳厢。LinkedList底層使用的是雙向鏈表數(shù)據(jù)結(jié)構(gòu)账胧,所以插入和刪除不受元素位置的影響竞慢。但對于訪問集合中的元素,ArrayList可以根據(jù)下標(biāo)索引序號快速響應(yīng)治泥,而LinkedList不支持快速隨機訪問元素筹煮。
- HashMap和Hashtable的了解?
對于不同的JDK版本有著不同的底層結(jié)構(gòu)居夹。HashMap在JDK1.8之前底層是數(shù)組和鏈表結(jié)合而成的鏈表散列败潦。HashMap通過key的hashCode處理得到hash值,然后通過(數(shù)組長度-1)&hash判斷當(dāng)前元素存放的位置准脂,如果當(dāng)前位置存在元素的話劫扒,就判斷該元素與要存入的元素的hash值以及key是否相同,如果相同的話就覆蓋狸膏,否則就通過拉鏈法解決沖突沟饥。JDK1.8之后當(dāng)鏈表長度大于閥值就轉(zhuǎn)化為紅黑樹減少搜索時間。
HashMap和Hashtable的區(qū)別
HashMap和Hashtable都實現(xiàn)了Map接口环戈,但決定用哪一個之前先要弄清楚它們之間的分別。主要的區(qū)別有:線程安全性澎灸,同步(synchronization)院塞,以及速度。
HashMap幾乎可以等價于Hashtable性昭,除了HashMap是非synchronized的拦止,并可以接受null(HashMap可以接受為null的鍵值(key)和值(value),而Hashtable則不行)糜颠。
HashMap是非synchronized汹族,而Hashtable是synchronized,這意味著Hashtable是線程安全的其兴,多個線程可以共享一個Hashtable顶瞒;而如果沒有正確的同步的話,多個線程是不能共享HashMap的元旬。Java 5提供了ConcurrentHashMap榴徐,它是HashTable的替代,比HashTable的擴展性更好匀归。
另一個區(qū)別是HashMap的迭代器(Iterator)是fail-fast迭代器坑资,而Hashtable的enumerator迭代器不是fail-fast的。所以當(dāng)有其它線程改變了HashMap的結(jié)構(gòu)(增加或者移除元素)穆端,將會拋出ConcurrentModificationException袱贮,但迭代器本身的remove()方法移除元素則不會拋出ConcurrentModificationException異常。但這并不是一個一定發(fā)生的行為体啰,要看JVM攒巍。這條同樣也是Enumeration和Iterator的區(qū)別嗽仪。
由于Hashtable是線程安全的也是synchronized,所以在單線程環(huán)境下它比HashMap要慢窑业。如果你不需要同步钦幔,只需要單一線程,那么使用HashMap性能要好過Hashtable常柄。
HashMap不能保證隨著時間的推移Map中的元素次序是不變的鲤氢。
要注意的一些重要術(shù)語:
sychronized意味著在一次僅有一個線程能夠更改Hashtable。就是說任何線程要更新Hashtable時要首先獲得同步鎖西潘,其它線程要等到同步鎖被釋放之后才能再次獲得同步鎖更新Hashtable卷玉。
Fail-safe和iterator迭代器相關(guān)。如果某個集合對象創(chuàng)建了Iterator或者ListIterator喷市,然后其它的線程試圖“結(jié)構(gòu)上”更改集合對象相种,將會拋出ConcurrentModificationException異常。但其它線程可以通過set()方法更改集合對象是允許的品姓,因為這并沒有從“結(jié)構(gòu)上”更改集合寝并。但是假如已經(jīng)從結(jié)構(gòu)上進(jìn)行了更改,再調(diào)用set()方法腹备,將會拋出IllegalArgumentException異常衬潦。
結(jié)構(gòu)上的更改指的是刪除或者插入一個元素,這樣會影響到map的結(jié)構(gòu)植酥。
我們能否讓HashMap同步镀岛?
HashMap可以通過下面的語句進(jìn)行同步:
Map m = Collections.synchronizeMap(hashMap);
結(jié)論
Hashtable和HashMap有幾個主要的不同:線程安全以及速度。僅在你需要完全的線程安全的時候使用Hashtable友驮,而如果你使用Java 5或以上的話漂羊,請使用ConcurrentHashMap吧。
- 對于上面兩個容器的初始值和每次擴充容量大小卸留,以及為什么HashMap的長度是那個規(guī)律走越?
如果不指定容器的初始值,Hashtable默認(rèn)為11耻瑟,每次擴充為原來的2n+1买喧,HashMap默認(rèn)初始為16,每次擴充為2的冪次方大小匆赃。
為什么長度要是2的冪次方呢
Hash 值的范圍值-2147483648到2147483647淤毛,前后加起來大概40億的映射空間。那當(dāng)然是不能直接拿的算柳。
只能通過映射低淡,就是對數(shù)組取模。“ (n - 1) & hash”蔗蹋。(n代表數(shù)組長度)這是公式何荚。
取余(%)操作中如果除數(shù)是2的冪次則等價于與其除數(shù)減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)猪杭〔吞粒” 并且 采用二進(jìn)制位操作 &,相對于%能夠提高運算效率皂吮,
- HashMap多線程操作會導(dǎo)致什么問題戒傻?
大多數(shù)javaer都知道HashMap是線程不安全的,多線程環(huán)境下數(shù)據(jù)可能會發(fā)生錯亂蜂筹,一定要謹(jǐn)慎使用需纳。這個結(jié)論是沒錯,可是HashMap的線程不安全遠(yuǎn)遠(yuǎn)不是數(shù)據(jù)臟讀這么簡單艺挪,它還有可能會發(fā)生死鎖不翩,造成內(nèi)存飆升100%的問題。
死鎖的四個條件
1)互斥條件:指進(jìn)程對所分配到的資源進(jìn)行排它性使用麻裳,即在一段時間內(nèi)某資源只由一個進(jìn)程占用口蝠。如果此時還有其它進(jìn)程請求資源,則請求者只能等待津坑,直至占有資源的進(jìn)程用畢釋放妙蔗。
2)請求和保持條件:指進(jìn)程已經(jīng)保持至少一個資源,但又提出了新的資源請求国瓮,而該資源已被其它進(jìn)程占有灭必,此時請求進(jìn)程阻塞狞谱,但又對自己已獲得的其它資源保持不放乃摹。
3)不剝奪條件:指進(jìn)程已獲得的資源,在未使用完之前跟衅,不能被剝奪孵睬,只能在使用完時由自己釋放。
4)環(huán)路等待條件:指在發(fā)生死鎖時伶跷,必然存在一個進(jìn)程——資源的環(huán)形鏈掰读,即進(jìn)程集合{P0,P1叭莫,P2蹈集,···,Pn}中的P0正在等待一個P1占用的資源雇初;P1正在等待P2占用的資源拢肆,……,Pn正在等待已被P0占用的資源。
我們來分析一下鏈表的互相引用符不符合上面四個條件:
①互斥條件:鏈表上的節(jié)點同一時間此時被兩個線程占用郭怪,兩個線程占用訪問節(jié)點的權(quán)利支示,符合該條件
②請求和保持條件:Thread1保持著節(jié)點e1,又提出了占用節(jié)點e2(此時尚未釋放e2)鄙才;而Thread2此時占用e2,又提出了占用節(jié)點e1颂鸿,Thread1占用著Thread2接下來要用的e1,而Thread2又占用著Thread1接下來要用的e2,符合該條件
③:不剝奪條件:線程是由自己的退出的攒庵,此時并沒有任何中斷機制(sleep或者wait方法或者interuppted中斷)嘴纺,只能由自己釋放,滿足條件
④:環(huán)路等待條件:e1叙甸、e2颖医、e3等形成了資源的環(huán)形鏈條,滿足該條件
- 談?wù)剆ynchronized關(guān)鍵字的理解裆蒸?
解決了多線程訪問共享資源造成的數(shù)據(jù)紊亂熔萧,所以采用該關(guān)鍵字修飾的方法或者代碼塊在任何時刻只能由一個線程執(zhí)行。
原子性的理解僚祷?
多線程操作的一系列復(fù)合操作佛致,要么全部成功,要么全部失敗回到操作前的初始值辙谜。
鎖的理解俺榆?
鎖的加入就是為了防止多線程同時對共享資源的操作造成數(shù)據(jù)的紊亂。
6.****JVM的理解装哆?Java1.8版本有什么了解罐脊?
JVM運行時數(shù)據(jù)區(qū),分為線程共享部分(方法區(qū)蜕琴、堆)和線程隔離區(qū)(虛擬機棧萍桌、本地方法棧和程序計數(shù)器)。
** 1.方法區(qū)**
用于存儲已被虛擬機加載的類信息凌简、常量上炎、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)雏搂。
運行時常量池(Runtime Constant Pool)是方法區(qū)的一部分藕施。.Class文件中除了有類的版本/字段/方法/接口等描述信息外,還有一項信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將類在加載后進(jìn)入方法區(qū)的運行時常量池中存放.
運行時常量區(qū)的內(nèi)容并不只是在編譯期間產(chǎn)生,通過String.intern()也可以實現(xiàn)在運行時向常量區(qū)中添加內(nèi)容凸郑。
需要注意的是:從JDK8開始裳食,方法區(qū)被元數(shù)據(jù)區(qū)替代了。
2.堆
是JVM中最大的一塊內(nèi)存區(qū)域芙沥,該區(qū)域的目的只是用于存儲對象實例及數(shù)組诲祸。該區(qū)域也是GC的最主要區(qū)域尘盼。
根據(jù)Java虛擬機規(guī)范的規(guī)定,Java堆可以處于物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可,就像我們的磁盤空間一樣.在實現(xiàn)時,既可以實現(xiàn)固定大小的,也可以是可擴展的,不過當(dāng)前主流的虛擬機都是按照可擴展來實現(xiàn)的(通過-Xmx和-Xms控制).如果在堆中沒有內(nèi)存完成實例分配,并且堆也無法再擴展時,就會拋出OutOfMemoryError異常。
3.虛擬機棧
每個線程方法在執(zhí)行時都會創(chuàng)建一個棧幀烦绳,包含局部變量表卿捎、返回地址、操作數(shù)棧等信息径密。每個方法的執(zhí)行與完成就對應(yīng)的棧幀的入棧與出棧過程午阵。局部變量表占用空間的大小在編譯期就確定了。
這里需要注意:如果線程請求的棧深度大于虛擬機所允許的深度享扔,將會拋出StackOverflowError異常底桂;如果虛擬機棧可以動態(tài)擴展(當(dāng)前大部分的Java虛擬機都可動態(tài)擴展惧眠,只不過Java虛擬機規(guī)范中也允許固定長度的虛擬機棧)籽懦,當(dāng)擴展時無法申請到足夠的內(nèi)存時將會拋出OutOfMemoryError異常。
** 4.本地方法棧**
與虛擬機棧類似氛魁,不過其中執(zhí)行是本地方法暮顺。對于HotSpot虛擬機而言,本地方法棧和虛擬機棧是統(tǒng)一的秀存。
** 5.程序計數(shù)器**
是一個小的內(nèi)存空間捶码,如果線程正在執(zhí)行的是一個java方法,則此內(nèi)存區(qū)域記錄正在執(zhí)行的虛擬機字節(jié)碼指令的地址或链;如果線程正在執(zhí)行的是native方法惫恼,則計算器中的值為空。此內(nèi)存區(qū)域是唯一一個在JAVA虛擬機規(guī)范中沒有規(guī)定任何OutOfMemoryError情況的區(qū)域澳盐。
JVM中ClassLoader類加載器的認(rèn)識
1.加載
加載指的是將類的class文件讀入到內(nèi)存祈纯,并為之創(chuàng)建一個java.lang.Class對象,也就是說叼耙,當(dāng)程序中使用任何類時腕窥,系統(tǒng)都會為之建立一個java.lang.Class對象。
類的加載由類加載器完成旬蟋,類加載器通常由JVM提供油昂,這些類加載器也是前面所有程序運行的基礎(chǔ)革娄,JVM提供的這些類加載器通常被稱為系統(tǒng)類加載器倾贰。除此之外,開發(fā)者可以通過繼承ClassLoader基類來創(chuàng)建自己的類加載器拦惋。
通過使用不同的類加載器匆浙,可以從不同來源加載類的二進(jìn)制數(shù)據(jù),通常有如下幾種來源厕妖。
1.從本地文件系統(tǒng)加載class文件首尼,這是前面絕大部分示例程序的類加載方式。
2.從JAR包加載class文件,這種方式也是很常見的软能,前面介紹JDBC編程時用到的數(shù)據(jù)庫驅(qū)動類就放在JAR文件中迎捺,JVM可以從JAR文件中直接加載該class文件。
3.通過網(wǎng)絡(luò)加載class文件查排。
4.把一個Java源文件動態(tài)編譯凳枝,并執(zhí)行加載。
類加載器通常無須等到“首次使用”該類時才加載該類跋核,Java虛擬機規(guī)范允許系統(tǒng)預(yù)先加載某些類岖瑰。
2.鏈接
當(dāng)類被加載之后,系統(tǒng)為之生成一個對應(yīng)的Class對象砂代,接著將會進(jìn)入連接階段蹋订,連接階段負(fù)責(zé)把類的二進(jìn)制數(shù)據(jù)合并到JRE中。類連接又可分為如下3個階段刻伊。
1)驗證:驗證階段用于檢驗被加載的類是否有正確的內(nèi)部結(jié)構(gòu)露戒,并和其他類協(xié)調(diào)一致。Java是相對C++語言是安全的語言捶箱,例如它有C++不具有的數(shù)組越界的檢查玫锋。這本身就是對自身安全的一種保護(hù)。驗證階段是Java非常重要的一個階段讼呢,它會直接的保證應(yīng)用是否會被惡意入侵的一道重要的防線撩鹿,越是嚴(yán)謹(jǐn)?shù)尿炞C機制越安全。驗證的目的在于確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機要求悦屏,不會危害虛擬機自身安全节沦。其主要包括四種驗證,文件格式驗證础爬,元數(shù)據(jù)驗證甫贯,字節(jié)碼驗證,符號引用驗證看蚜。
四種驗證做進(jìn)一步說明:
文件格式驗證:主要驗證字節(jié)流是否符合Class文件格式規(guī)范叫搁,并且能被當(dāng)前的虛擬機加載處理。例如:主供炎,次版本號是否在當(dāng)前虛擬機處理的范圍之內(nèi)渴逻。常量池中是否有不被支持的常量類型。指向常量的中的索引值是否存在不存在的常量或不符合類型的常量音诫。
** 元數(shù)據(jù)驗證:**對字節(jié)碼描述的信息進(jìn)行語義的分析惨奕,分析是否符合java的語言語法的規(guī)范。
** 字節(jié)碼驗證:**最重要的驗證環(huán)節(jié)竭钝,分析數(shù)據(jù)流和控制梨撞,確定語義是合法的雹洗,符合邏輯的。主要的針對元數(shù)據(jù)驗證后對方法體的驗證卧波。保證類方法在運行時不會有危害出現(xiàn)时肿。
符號引用驗證:主要是針對符號引用轉(zhuǎn)換為直接引用的時候,是會延伸到第三解析階段港粱,主要去確定訪問類型等涉及到引用的情況嗜侮,主要是要保證引用一定會被訪問到,不會出現(xiàn)類等無法訪問的問題啥容。
2)準(zhǔn)備:類準(zhǔn)備階段負(fù)責(zé)為類的靜態(tài)變量分配內(nèi)存锈颗,并設(shè)置默認(rèn)初始值。
3)解析:將類的二進(jìn)制數(shù)據(jù)中的符號引用替換成直接引用咪惠。說明一下:符號引用:符號引用是以一組符號來描述所引用的目標(biāo)击吱,符號可以是任何的字面形式的字面量,只要不會出現(xiàn)沖突能夠定位到就行遥昧。布局和內(nèi)存無關(guān)覆醇。直接引用:是指向目標(biāo)的指針,偏移量或者能夠直接定位的句柄炭臭。該引用是和內(nèi)存中的布局有關(guān)的永脓,并且一定加載進(jìn)來的。
3.初始化
初始化是為類的靜態(tài)變量賦予正確的初始值鞋仍,準(zhǔn)備階段和初始化階段看似有點矛盾帆锋,其實是不矛盾的谨敛,如果類中有語句:private static int a = 10,它的執(zhí)行過程是這樣的,首先字節(jié)碼文件被加載到內(nèi)存后摄乒,先進(jìn)行鏈接的驗證這一步驟哀托,驗證通過后準(zhǔn)備階段劫恒,給a分配內(nèi)存谷异,因為變量a是static的,所以此時a等于int類型的默認(rèn)初始值0吸申,即a=0,然后到解析(后面在說)梗劫,到初始化這一步驟時,才把a的真正的值10賦給a,此時a=10截碴。
- 什么是OOM梳侨?StackOverflowError和OutOfMemoryError?
OOM:java.lang.OutOfMemoryError 內(nèi)存溢出
StackOverflowError:遞歸過深隐岛,遞歸沒有出口猫妙。
OutOfMemoryError:JVM空間溢出瓷翻,創(chuàng)建對象速度高于GC回收速度聚凹。
JVM的常用參數(shù)調(diào)優(yōu)割坠?
在調(diào)優(yōu)之前,我們需要記住下面的原則:
多數(shù)的 Java 應(yīng)用不需要在服務(wù)器上進(jìn)行 GC 優(yōu)化妒牙; 多數(shù)導(dǎo)致 GC 問題的 Java 應(yīng)用彼哼,都不是因為我們參數(shù)設(shè)置錯誤,而是代碼問題湘今; 在應(yīng)用上線之前敢朱,先考慮將機器的 JVM 參數(shù)設(shè)置到最優(yōu)(最適合); 減少創(chuàng)建對象的數(shù)量摩瞎; 減少使用全局變量和大對象拴签; GC 優(yōu)化是到最后不得已才采用的手段; 在實際使用中旗们,分析 GC 情況優(yōu)化代碼比優(yōu)化 GC 參數(shù)要多得多蚓哩。
GC 調(diào)優(yōu)目的
將轉(zhuǎn)移到老年代的對象數(shù)量降低到最小上渴; 減少 GC 的執(zhí)行時間岸梨。
策略 1:將新對象預(yù)留在新生代,由于 Full GC 的成本遠(yuǎn)高于 Minor GC稠氮,因此盡可能將對象分配在新生代是明智的做法曹阔,實際項目中根據(jù) GC 日志分析新生代空間大小分配是否合理,適當(dāng)通過“-Xmn”命令調(diào)節(jié)新生代大小隔披,最大限度降低新對象直接進(jìn)入老年代的情況赃份。
策略 2:大對象進(jìn)入老年代,雖然大部分情況下奢米,將對象分配在新生代是合理的芥炭。但是對于大對象這種做法卻值得商榷,大對象如果首次在新生代分配可能會出現(xiàn)空間不足導(dǎo)致很多年齡不夠的小對象被分配的老年代恃慧,破壞新生代的對象結(jié)構(gòu)园蝠,可能會出現(xiàn)頻繁的 full gc。因此痢士,對于大對象彪薛,可以設(shè)置直接進(jìn)入老年代(當(dāng)然短命的大對象對于垃圾回收老說簡直就是噩夢)。-XX:PretenureSizeThreshold 可以設(shè)置直接進(jìn)入老年代的對象大小怠蹂。
策略 3:合理設(shè)置進(jìn)入老年代對象的年齡善延,-XX:MaxTenuringThreshold 設(shè)置對象進(jìn)入老年代的年齡大小,減少老年代的內(nèi)存占用城侧,降低 full gc 發(fā)生的頻率易遣。
策略 4:設(shè)置穩(wěn)定的堆大小,堆大小設(shè)置有兩個參數(shù):-Xms 初始化堆大小嫌佑,-Xmx 最大堆大小豆茫。
策略5:注意: 如果滿足下面的指標(biāo)侨歉,則一般不需要進(jìn)行 GC 優(yōu)化:
MinorGC 執(zhí)行時間不到50ms; Minor GC 執(zhí)行不頻繁揩魂,約10秒一次幽邓; Full GC 執(zhí)行時間不到1s; Full GC 執(zhí)行頻率不算頻繁火脉,不低于10分鐘1次牵舵。