Java SE 基礎(chǔ):
封裝、繼承、多態(tài)
封裝:
- 概念:就是把對(duì)象的屬性和操作(或服務(wù))結(jié)合為一個(gè)獨(dú)立的整體,并盡可能隱藏對(duì)象的內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。
- 好處: 隱藏內(nèi)部實(shí)現(xiàn)細(xì)節(jié)。
繼承:
- 概念:繼承是從已有的類(lèi)中派生出新的類(lèi),新的類(lèi)能吸收已有類(lèi)的數(shù)據(jù)屬性和行為,并能擴(kuò)展新的能力。
- 好處:提高代碼的復(fù)用,縮短開(kāi)發(fā)周期。
多態(tài):
- 概念:多態(tài)(Polymorphism)按字面的意思就是“多種狀態(tài),即同一個(gè)實(shí)體同時(shí)具有多種形式。一般表現(xiàn)形式是程序在運(yùn)行的過(guò)程中,同一種類(lèi)型在不同的條件下表現(xiàn)不同的結(jié)果。多態(tài)也稱(chēng)為動(dòng)態(tài)綁定,一般是在運(yùn)行時(shí)刻才能確定方法的具體執(zhí)行對(duì)象。
- 好處:
- 將接口和實(shí)現(xiàn)分開(kāi),改善代碼的組織結(jié)構(gòu)和可讀性,還能創(chuàng)建可拓展的程序。
- 消除類(lèi)型之間的耦合關(guān)系。允許將多個(gè)類(lèi)型視為同一個(gè)類(lèi)型。
- 一個(gè)多態(tài)方法的調(diào)用允許有多種表現(xiàn)形式。
抽象類(lèi)與接口
- 一個(gè)子類(lèi)只能繼承一個(gè)抽象類(lèi),但能實(shí)現(xiàn)多個(gè)接口
- 抽象類(lèi)可以有構(gòu)造方法,接口沒(méi)有構(gòu)造方法
- 抽象類(lèi)可以有普通成員變量,接口沒(méi)有普通成員變量
- 抽象類(lèi)和接口都可有靜態(tài)成員變量,抽象類(lèi)中靜態(tài)成員變量訪問(wèn)類(lèi)型任意,接口只能public static final(默認(rèn))
- 抽象類(lèi)可以沒(méi)有抽象方法,抽象類(lèi)可以有普通方法,接口中都是抽象方法
- 抽象類(lèi)可以有靜態(tài)方法,接口不能有靜態(tài)方法
- 抽象類(lèi)中的方法可以是public、protected和默認(rèn);接口方法只有public abstract
靜態(tài)內(nèi)部類(lèi)和普通內(nèi)部類(lèi)
靜態(tài)內(nèi)部類(lèi)不需要有指向外部類(lèi)的引用。但非靜態(tài)內(nèi)部類(lèi)需要持有對(duì)外部類(lèi)的引用。非靜態(tài)內(nèi)部類(lèi)能夠訪問(wèn)外部類(lèi)的靜態(tài)和非靜態(tài)成員。靜態(tài)類(lèi)不能訪問(wèn)外部類(lèi)的非靜態(tài)成員。他只能訪問(wèn)外部類(lèi)的靜態(tài)成員。
集合框架:
List集合和Set集合
List接口:
List中元素存取是有序的、可重復(fù)的;Set集合中元素是無(wú)序的,不可重復(fù)的。CopyOnWriteArrayList:COW的策略,即寫(xiě)時(shí)復(fù)制的策略。適用于讀多寫(xiě)少的并發(fā)場(chǎng)景。
Set接口:
Set集合元素存取無(wú)序,且元素不可重復(fù)。
HashSet不保證迭代順序,線程不安全;LinkedHashSet是Set接口的哈希表和鏈接列表的實(shí)現(xiàn),保證迭代順序,線程不安全。
TreeSet:可以對(duì)Set集合中的元素排序,元素以二叉樹(shù)形式存放,線程不安全。
ArrayList、LinkedList、Vector的區(qū)別
首先它們均是List接口的實(shí)現(xiàn)。
ArrayList、LinkedList的區(qū)別:
- 隨機(jī)存取:ArrayList是基于可變大小的數(shù)組實(shí)現(xiàn),LinkedList是鏈接列表的實(shí)現(xiàn)。這也就決定了對(duì)于隨機(jī)訪問(wèn)的get和set的操作,ArrayList要優(yōu)于LinkedList,因?yàn)長(zhǎng)inkedList要移動(dòng)指針。
- 插入和刪除:LinkedList要好一些,因?yàn)锳rrayList要移動(dòng)數(shù)據(jù),更新索引。
- 內(nèi)存消耗:LinkedList需要更多的內(nèi)存,因?yàn)樾枰S護(hù)指向后繼結(jié)點(diǎn)的指針。
Vector從Java 1.0起就存在,在1.2時(shí)改為實(shí)現(xiàn)List接口,功能與ArrayList類(lèi)似,但是Vector具備線程安全。
Map集合
- Hashtable:基于Dictionary類(lèi),線程安全,速度快。底層是哈希表數(shù)據(jù)結(jié)構(gòu)。是同步的。 不允許null作為鍵,null作為值。
- Properties:Hashtable的子類(lèi)。用于配置文件的定義和操作,使用頻率非常高,同時(shí)鍵和值都是字符串。
- HashMap:線程不安全,底層是數(shù)組加鏈表實(shí)現(xiàn)的哈希表。允許null作為鍵,null作為值。HashMap去掉了contains方法。 注意:HashMap不保證元素的迭代順序。如果需要元素存取有序,請(qǐng)使用LinkedHashMap
- TreeMap:可以用來(lái)對(duì)Map集合中的鍵進(jìn)行排序。
- ConcurrentHashMap:是JUC包下的一個(gè)并發(fā)集合。
為什么使用ConcurrentHashMap而不是HashMap或Hashtable?
HashMap的缺點(diǎn):主要是多線程同時(shí)put時(shí),如果同時(shí)觸發(fā)了rehash操作,會(huì)導(dǎo)致HashMap中的鏈表中出現(xiàn)循環(huán)節(jié)點(diǎn),進(jìn)而使得后面get的時(shí)候,會(huì)死循環(huán),CPU達(dá)到100%,所以在并發(fā)情況下不能使用HashMap。讓HashMap同步:Map m = Collections.synchronizeMap(hashMap);而Hashtable雖然是同步的,使用synchronized來(lái)保證線程安全,但在線程競(jìng)爭(zhēng)激烈的情況下HashTable的效率非常低下。因?yàn)楫?dāng)一個(gè)線程訪問(wèn)HashTable的同步方法時(shí),其他線程訪問(wèn)HashTable的同步方法時(shí),可能會(huì)進(jìn)入阻塞或輪詢狀態(tài)。如線程1使用put進(jìn)行添加元素,線程2不但不能使用put方法添加元素,并且也不能使用get方法來(lái)獲取元素,所以競(jìng)爭(zhēng)越激烈效率越低。
ConcurrentHashMap的原理:
HashTable容器在競(jìng)爭(zhēng)激烈的并發(fā)環(huán)境下表現(xiàn)出效率低下的原因在于所有訪問(wèn)HashTable的線程都必須競(jìng)爭(zhēng)同一把鎖,那假如容器里有多把鎖,每一把鎖用于鎖容器其中一部分?jǐn)?shù)據(jù),那么當(dāng)多線程訪問(wèn)容器里不同數(shù)據(jù)段的數(shù)據(jù)時(shí),線程間就不會(huì)存在鎖競(jìng)爭(zhēng),從而可以有效的提高并發(fā)訪問(wèn)效率,這就是ConcurrentHashMap所使用的鎖分段技術(shù),首先將數(shù)據(jù)分成一段一段的存儲(chǔ),然后給每一段數(shù)據(jù)配一把鎖,當(dāng)一個(gè)線程占用鎖訪問(wèn)其中一個(gè)段數(shù)據(jù)的時(shí)候,其他段的數(shù)據(jù)也能被其他線程訪問(wèn)。
ConcurrentHashMap的結(jié)構(gòu):
ConcurrentHashMap是由Segment數(shù)組結(jié)構(gòu)和HashEntry數(shù)組結(jié)構(gòu)組成。Segment是一種可重入鎖ReentrantLock,在ConcurrentHashMap里扮演鎖的角色,HashEntry則用于存儲(chǔ)鍵值對(duì)數(shù)據(jù)。一個(gè)ConcurrentHashMap里包含一個(gè)Segment數(shù)組,Segment的結(jié)構(gòu)和HashMap類(lèi)似,是一種數(shù)組和鏈表結(jié)構(gòu), 一個(gè)Segment里包含一個(gè)HashEntry數(shù)組,每個(gè)HashEntry是一個(gè)鏈表結(jié)構(gòu)的元素,當(dāng)對(duì)某個(gè)HashEntry數(shù)組的數(shù)據(jù)進(jìn)行修改時(shí),必須首先獲得它對(duì)應(yīng)的Segment鎖。
ConcurrentHashMap的構(gòu)造、get、put操作:
構(gòu)造函數(shù):傳入?yún)?shù)分別為
- 初始容量,默認(rèn)16;
- 裝載因子 裝載因子用于rehash的判定,就是當(dāng)ConcurrentHashMap中的元素大于裝載因子*最大容量時(shí)進(jìn)行擴(kuò)容,默認(rèn)0.75;
- 并發(fā)級(jí)別 這個(gè)值用來(lái)確定Segment的個(gè)數(shù),Segment的個(gè)數(shù)是大于等于concurrencyLevel的第一個(gè)2的n次方的數(shù)。比如,如果concurrencyLevel為12,13,14,15,16這些數(shù),則Segment的數(shù)目為16(2的4次方)。默認(rèn)值為static final int DEFAULT_CONCURRENCY_LEVEL = 16;。理想情況下ConcurrentHashMap的真正的并發(fā)訪問(wèn)量能夠達(dá)到concurrencyLevel,因?yàn)橛衏oncurrencyLevel個(gè)Segment,假如有concurrencyLevel個(gè)線程需要訪問(wèn)Map,并且需要訪問(wèn)的數(shù)據(jù)都恰好分別落在不同的Segment中,則這些線程能夠無(wú)競(jìng)爭(zhēng)地自由訪問(wèn)(因?yàn)樗麄儾恍枰?jìng)爭(zhēng)同一把鎖),達(dá)到同時(shí)訪問(wèn)的效果。這也是為什么這個(gè)參數(shù)起名為“并發(fā)級(jí)別”的原因。默認(rèn)16.
初始化的一些動(dòng)作:
初始化segments數(shù)組(根據(jù)并發(fā)級(jí)別得到數(shù)組大小ssize),默認(rèn)16
初始化segmentShift和segmentMask(這兩個(gè)全局變量在定位segment時(shí)的哈希算法里需要使用),默認(rèn)情況下segmentShift為28,segmentMask為15
初始化每個(gè)Segment,這一步會(huì)確定Segment里HashEntry數(shù)組的長(zhǎng)度.put操作:
- 判斷value是否為null,如果為null,直接拋出異常。
- key通過(guò)一次hash運(yùn)算得到一個(gè)hash值。將得到hash值向右按位移動(dòng)segmentShift位,然后再與segmentMask做&運(yùn)算得到segment的索引j。即segmentFor方法
- 使用Unsafe的方式從Segment數(shù)組中獲取該索引對(duì)應(yīng)的Segment對(duì)象。向這個(gè)Segment對(duì)象中put值,這個(gè)put操作也基本是一樣的步驟(通過(guò)&運(yùn)算獲取HashEntry的索引,然后set)。
get操作:
- 和put操作一樣,先通過(guò)key進(jìn)行hash確定應(yīng)該去哪個(gè)Segment中取數(shù)據(jù)。
- 使用Unsafe獲取對(duì)應(yīng)的Segment,然后再進(jìn)行一次&運(yùn)算得到HashEntry鏈表的位置,然后從鏈表頭開(kāi)始遍歷整個(gè)鏈表(因?yàn)镠ash可能會(huì)有碰撞,所以用一個(gè)鏈表保存),如果找到對(duì)應(yīng)的key,則返回對(duì)應(yīng)的value值,如果鏈表遍歷完都沒(méi)有找到對(duì)應(yīng)的key,則說(shuō)明Map中不包含該key,返回null。
定位Segment的hash算法:(hash >>> segmentShift) & segmentMask
定位HashEntry所使用的hash算法:int index = hash & (tab.length - 1);
注:tab為HashEntry數(shù)組
Collection 和 Collections的區(qū)別
Collection是集合類(lèi)的上級(jí)接口,子接口主要有Set 和List、Queue Collections是針對(duì)集合類(lèi)的一個(gè)幫助類(lèi),提供了操作集合的工具方法:一系列靜態(tài)方法實(shí)現(xiàn)對(duì)各種集合的搜索、排序、線程安全化等操作。
Map、Set、List、Queue、Stack的特點(diǎn)與用法
Set集合類(lèi)似于一個(gè)罐子,"丟進(jìn)"Set集合里的多個(gè)對(duì)象之間沒(méi)有明顯的順序。 List集合代表元素有序、可重復(fù)的集合,集合中每個(gè)元素都有其對(duì)應(yīng)的順序索引。 Stack是Vector提供的一個(gè)子類(lèi),用于模擬"棧"這種數(shù)據(jù)結(jié)構(gòu)(LIFO后進(jìn)先出) Queue用于模擬"隊(duì)列"這種數(shù)據(jù)結(jié)構(gòu)(先進(jìn)先出 FIFO)。 Map用于保存具有"映射關(guān)系"的數(shù)據(jù),因此Map集合里保存著兩組值。
HashMap的工作原理
HashMap維護(hù)了一個(gè)Entry數(shù)組,Entry內(nèi)部類(lèi)有key,value,hash和next是個(gè)字段,其中next也是一個(gè)Entry類(lèi)型。可以將Entry數(shù)組理解為一個(gè)個(gè)的散列桶。每一個(gè)桶實(shí)際上是一個(gè)單鏈表。當(dāng)執(zhí)行put操作時(shí),會(huì)根據(jù)key的hashcode定位到相應(yīng)的桶。遍歷單鏈表檢查該key是否已經(jīng)存在,如果存在,覆蓋該value,反之,新建一個(gè)新的Entry,并放在單鏈表的頭部。當(dāng)通過(guò)傳遞key調(diào)用get方法時(shí),它再次使用key.hashCode()來(lái)找到相應(yīng)的散列桶,然后使用key.equals()方法找出單鏈表中正確的Entry,然后返回它的值。
七、HashMap和Hashtable的區(qū)別
Hashtable是基于陳舊的Dictionary的Map接口的實(shí)現(xiàn),而HashMap是基于哈希表的Map接口的實(shí)現(xiàn)
從方法上看,HashMap去掉了Hashtable的contains方法
HashTable是同步的(線程安全),而HashMap線程不安全
HashMap允許空鍵值,而Hashtable不允許
HashMap的iterator迭代器執(zhí)行快速失敗機(jī)制,也就是說(shuō)在迭代過(guò)程中修改集合結(jié)構(gòu),除非調(diào)用迭代器自身的remove方法,否則以其他任何方式的修改都將拋出并發(fā)修改異常。如果尋求迭代的時(shí)候修改Map,可以使用ConcurrentHashMap。而Hashtable返回的Enumeration不是快速失敗的。
Map的實(shí)現(xiàn)類(lèi)的介紹
HashMap基于散列表來(lái)的實(shí)現(xiàn),即使用hashCode()進(jìn)行快速查詢?cè)氐奈恢?,顯著提高性能。插入和查詢“鍵值對(duì)”的開(kāi)銷(xiāo)是固定的??梢酝ㄟ^(guò)設(shè)置容量和裝載因子,以調(diào)整容器的性能。
LinkedHashMap, 類(lèi)似于HashMap,但是迭代遍歷它時(shí),保證迭代的順序是其插入的次序,因?yàn)樗褂面湵砭S護(hù)內(nèi)部次序。此外可以在構(gòu)造器中設(shè)定LinkedHashMap,使之采用LRU算法。使沒(méi)有被訪問(wèn)過(guò)的元素或較少訪問(wèn)的元素出現(xiàn)在前面,訪問(wèn)過(guò)的或訪問(wèn)多的出現(xiàn)在后面。這對(duì)于需要定期清理元素以節(jié)省空間的程序員來(lái)說(shuō),此功能使得程序員很容易得以實(shí)現(xiàn)。
TreeMap, 是基于紅黑樹(shù)的實(shí)現(xiàn)。同時(shí)TreeMap實(shí)現(xiàn)了SortedMap接口,該接口可以確保鍵處于排序狀態(tài)。所以查看“鍵”和“鍵值對(duì)”時(shí),所有得到的結(jié)果都是經(jīng)過(guò)排序的,次序由自然排序或提供的Comparator決定。SortedMap接口擁有其他額外的功能,如:返回當(dāng)前Map使用的Comparator比較強(qiáng),firstKey(),lastKey(),headMap(toKey),tailMap(fromKey)以及可以返回一個(gè)子樹(shù)的subMap()方法等。
WeakHashMap,表示弱鍵映射,WeakHashMap 的工作與正常的 HashMap 類(lèi)似,但是使用弱引用作為 key,意思就是當(dāng) key 對(duì)象沒(méi)有任何引用時(shí),key/value 將會(huì)被回收。
ConcurrentHashMap, 在HashMap基礎(chǔ)上分段鎖機(jī)制實(shí)現(xiàn)的線程安全的HashMap。
IdentityHashMap 使用==代替equals() 對(duì)“鍵”進(jìn)行比較的散列映射。專(zhuān)為解決特殊問(wèn)題而設(shè)計(jì)。
HashTable:基于Dictionary類(lèi)的Map接口的實(shí)現(xiàn),它是線程安全的。
LinkedList 和 PriorityQueue 的區(qū)別
它們均是Queue接口的實(shí)現(xiàn)。擁有FIFO的特點(diǎn),它們的區(qū)別在于排序行為。LinkedList 支持雙向列表操作, PriorityQueue 按優(yōu)先級(jí)組織的隊(duì)列,元素的出隊(duì)次序由元素的自然排序或者由Comparator比較器指定。
線程安全的集合類(lèi)。Vector、HashTable、Properties和Stack
BlockingQueue
Java.util.concurrent.BlockingQueue是一個(gè)隊(duì)列,在進(jìn)行獲取元素時(shí),它會(huì)等待隊(duì)列變?yōu)榉强?;?dāng)在添加一個(gè)元素時(shí),它會(huì)等待隊(duì)列中的可用空間。BlockingQueue接口是Java集合框架的一部分,主要用于實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模式。我們不需要擔(dān)心等待生產(chǎn)者有可用的空間,或消費(fèi)者有可用的對(duì)象,因?yàn)樗荚贐lockingQueue的實(shí)現(xiàn)類(lèi)中被處理了。Java提供了集中BlockingQueue的實(shí)現(xiàn),比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。
如何對(duì)一組對(duì)象進(jìn)行排序
如果需要對(duì)一個(gè)對(duì)象數(shù)組進(jìn)行排序,我們可以使用Arrays.sort()方法。如果我們需要排序一個(gè)對(duì)象列表,我們可以使用Collections.sort()方法。排序時(shí)是默認(rèn)根據(jù)元素的自然排序(使用Comparable)或使用Comparator外部比較器。Collections內(nèi)部使用數(shù)組排序方法,所有它們兩者都有相同的性能,只是Collections需要花時(shí)間將列表轉(zhuǎn)換為數(shù)組。
Comparable和Comparator接口區(qū)別
Comparator位于包java.util下,而Comparable位于包java.lang下
如果我們需要使用Arrays或Collections的排序方法對(duì)對(duì)象進(jìn)行排序時(shí),我們需要在自定義類(lèi)中實(shí)現(xiàn)Comparable接口并重寫(xiě)compareTo方法,compareTo方法接收一個(gè)參數(shù),如果this對(duì)象比傳遞的參數(shù)小,相等或大時(shí)分別返回負(fù)整數(shù)、0、正整數(shù)。Comparable被用來(lái)提供對(duì)象的自然排序。String、Integer實(shí)現(xiàn)了該接口。
Comparator比較器的compare方法接收2個(gè)參數(shù),根據(jù)參數(shù)的比較大小分別返回負(fù)整數(shù)、0和正整數(shù)。 Comparator 是一個(gè)外部的比較器,當(dāng)這個(gè)對(duì)象自然排序不能滿足你的要求時(shí),你可以寫(xiě)一個(gè)比較器來(lái)完成兩個(gè)對(duì)象之間大小的比較。用 Comparator 是策略模式(strategy design pattern),就是不改變對(duì)象自身,而用一個(gè)策略對(duì)象(strategy object)來(lái)改變它的行為。
與Java集合框架相關(guān)的有哪些最好的實(shí)踐
- 根據(jù)需要選擇正確的集合類(lèi)型。比如,如果指定了大小,我們會(huì)選用Array而非ArrayList。如果我們想根據(jù)插入順序遍歷一個(gè)Map,我們需要使用TreeMap。如果我們不想重復(fù),我們應(yīng)該使用Set。
- 一些集合類(lèi)允許指定初始容量,所以如果我們能夠估計(jì)到存儲(chǔ)元素的數(shù)量,我們可以使用它,就避免了重新哈希或大小調(diào)整。
- 基于接口編程,而非基于實(shí)現(xiàn)編程,它允許我們后來(lái)輕易地改變實(shí)現(xiàn)。
- 總是使用類(lèi)型安全的泛型,避免在運(yùn)行時(shí)出現(xiàn)ClassCastException。 > 5. 使用JDK提供的不可變類(lèi)作為Map的key,可以避免自己實(shí)現(xiàn)hashCode()和equals()。
IO和NIO
在以前的Java IO中,都是阻塞式IO,NIO引入了非阻塞式IO。 第一種方式:我從硬盤(pán)讀取數(shù)據(jù),然后程序一直等,數(shù)據(jù)讀完后,繼續(xù)操作。這種方式是最簡(jiǎn)單的,叫阻塞IO。 第二種方式:我從硬盤(pán)讀取數(shù)據(jù),然后程序繼續(xù)向下執(zhí)行,等數(shù)據(jù)讀取完后,通知當(dāng)前程序(對(duì)硬件來(lái)說(shuō)叫中斷,對(duì)程序來(lái)說(shuō)叫回調(diào)),然后此程序可以立即處理數(shù)據(jù),也可以執(zhí)行完當(dāng)前操作在讀取數(shù)據(jù)。
流與塊的比較
原來(lái)的 I/O 以流的方式處理數(shù)據(jù),而 NIO 以塊的方式處理數(shù)據(jù)。面向流 的 I/O 系統(tǒng)一次一個(gè)字節(jié)地處理數(shù)據(jù)。一個(gè)輸入流產(chǎn)生一個(gè)字節(jié)的數(shù)據(jù),一個(gè)輸出流消費(fèi)一個(gè)字節(jié)的數(shù)據(jù)。這樣做是相對(duì)簡(jiǎn)單的。不利的一面是,面向流的 I/O 通常相當(dāng)慢。 一個(gè) 面向塊 的 I/O 系統(tǒng)以塊的形式處理數(shù)據(jù)。每一個(gè)操作都在一步中產(chǎn)生或者消費(fèi)一個(gè)數(shù)據(jù)塊。按塊處理數(shù)據(jù)比按(流式的)字節(jié)處理數(shù)據(jù)要快得多。但是面向塊的 I/O 缺少一些面向流的 I/O 所具有的優(yōu)雅性和簡(jiǎn)單性。
通道與流
Channel是一個(gè)對(duì)象,可以通過(guò)它讀取和寫(xiě)入數(shù)據(jù)。通道與流功能類(lèi)似,不同之處在于通道是雙向的。而流只是在一個(gè)方向上移動(dòng)(一個(gè)流必須是 InputStream 或者 OutputStream 的子類(lèi)), 而通道可以用于讀、寫(xiě)或者同時(shí)用于讀寫(xiě)。
緩沖區(qū)Buffer
在 NIO 庫(kù)中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。在 NIO 庫(kù)中,所有數(shù)據(jù)都是用緩沖區(qū)處理的。
Position: 表示下一次訪問(wèn)的緩沖區(qū)位置 Limit: 表示當(dāng)前緩沖區(qū)存放的數(shù)據(jù)容量。 Capacity:表示緩沖區(qū)最大容量
flip()方法:它將 limit 設(shè)置為當(dāng)前 position。它將 position 設(shè)置為 0
clear方法:它將 limit 設(shè)置為與 capacity 相同。它設(shè)置 position 為 0。
線程
什么是線程
線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。程序員可以通過(guò)它進(jìn)行多處理器編程,可以使用多線程對(duì)運(yùn)算密集型任務(wù)提速。比如,如果一個(gè)線程完成一個(gè)任務(wù)要100 毫秒,那么用十個(gè)線程完成改任務(wù)只需 10 毫秒。Java在語(yǔ)言層面對(duì)多線程提供了很好的支持。
線程和進(jìn)程有什么區(qū)別
- 從概念上: 進(jìn)程:一個(gè)程序?qū)σ粋€(gè)數(shù)據(jù)集的動(dòng)態(tài)執(zhí)行過(guò)程,是分配資源的基本單位。 線程:存在于進(jìn)程內(nèi),是進(jìn)程內(nèi)的基本調(diào)度單位。共享進(jìn)程的資源。
- 從執(zhí)行過(guò)程中來(lái)看: 進(jìn)程:擁有獨(dú)立的內(nèi)存單元,而多個(gè)線程共享內(nèi)存,從而提高了應(yīng)用程序的運(yùn)行效率。 線程:每一個(gè)獨(dú)立的線程,都有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列、和程序的出口。但是線程不能夠獨(dú)立的執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線程執(zhí)行控制。
- 從邏輯角度來(lái)看:(重要區(qū)別) 多線程的意義在于一個(gè)應(yīng)用程序中,有多個(gè)執(zhí)行部分可以同時(shí)執(zhí)行。但是,操作系統(tǒng)并沒(méi)有將多個(gè)線程看做多個(gè)獨(dú)立的應(yīng)用,來(lái)實(shí)現(xiàn)進(jìn)程的調(diào)度和管理及資源分配。
簡(jiǎn)言之,一個(gè)程序至少有一個(gè)進(jìn)程,一個(gè)進(jìn)程至少有一個(gè)線程。進(jìn)程是資源分配的基本單位,線程共享進(jìn)程的資源。
如何在 Java 中實(shí)現(xiàn)線程
繼承Thread類(lèi)或?qū)崿F(xiàn)Runnable接口。
用 Runnable 還是 Thread
Java 不支持類(lèi)的多重繼承,但允許你調(diào)用多個(gè)接口。所以如果你要繼承其他類(lèi),當(dāng)然是實(shí)現(xiàn)Runnable接口好了。
Thread 類(lèi)中的 start () 和 run () 方法有什么區(qū)別
start ()方法被用來(lái)啟動(dòng)新創(chuàng)建的線程,而且 start ()內(nèi)部調(diào)用了 run ()方法,這和直接調(diào)用 run ()方法的效果不一樣。當(dāng)你調(diào)用 run ()方法的時(shí)候,只會(huì)是在原來(lái)的線程中調(diào)用,沒(méi)有新的線程啟動(dòng),start ()方法才會(huì)啟動(dòng)新線程。也就是用start方法來(lái)啟動(dòng)線程,才是真正實(shí)現(xiàn)了多線程。而run方法只是一個(gè)普通方法。
Java 中 Runnable 和 Callable 有什么不同
Runnable和 Callable 都代表那些要在不同的線程中執(zhí)行的任務(wù)。Runnable 從 JDK1.0 開(kāi)始就有了,Callable 是在 JDK1.5 增加的。它們的主要區(qū)別是 Callable 的 call () 方法可以返回值和拋出異常,而 Runnable 的 run ()方法沒(méi)有這些功能。
Java 中 CyclicBarrier 和 CountDownLatch 有什么不同
它們都是JUC下的類(lèi),CyclicBarrier 和 CountDownLatch 都可以用來(lái)讓一組線程等待其它線程。區(qū)別在于CountdownLatch計(jì)數(shù)無(wú)法被重置。如果需要重置計(jì)數(shù),請(qǐng)考慮使用 CyclicBarrier。
Java 內(nèi)存模型是什么
Java 內(nèi)存模型規(guī)定和指引Java 程序在不同的內(nèi)存架構(gòu)、CPU 和操作系統(tǒng)間有確定性地行為。它在多線程的情況下尤其重要。Java內(nèi)存模型對(duì)一個(gè)線程所做的變動(dòng)能被其它線程可見(jiàn)提供了保證,它們之間是先行發(fā)生關(guān)系。這個(gè)關(guān)系定義了一些規(guī)則讓程序員在并發(fā)編程時(shí)思路更清晰。
線程內(nèi)的代碼能夠按先后順序執(zhí)行,這被稱(chēng)為程序次序規(guī)則。
對(duì)于同一個(gè)鎖,一個(gè)解鎖操作一定要發(fā)生在時(shí)間上后發(fā)生的另一個(gè)鎖定操作之前,也叫做管程鎖定規(guī)則。
前一個(gè)對(duì)volatile的寫(xiě)操作在后一個(gè)volatile的讀操作之前,也叫volatile變量規(guī)則。
一個(gè)線程內(nèi)的任何操作必需在這個(gè)線程的 start ()調(diào)用之后,也叫作線程啟動(dòng)規(guī)則。
一個(gè)線程的所有操作都會(huì)在線程終止之前,線程終止規(guī)則。
一個(gè)對(duì)象的終結(jié)操作必需在這個(gè)對(duì)象構(gòu)造完成之后,也叫對(duì)象終結(jié)規(guī)則。
傳遞性
Java 中的 volatile 變量是什么
Java 語(yǔ)言提供了一種稍弱的同步機(jī)制,即volatile變量。但是volatile并不容器完全被正確、完整的理解。 一般來(lái)說(shuō),volatile具備2條語(yǔ)義,或者說(shuō)2個(gè)特性。
第一是保證volatile修飾的變量對(duì)所有線程的可見(jiàn)性,這里的可見(jiàn)性是指當(dāng)一條線程修改了該變量,新值對(duì)于其它線程來(lái)說(shuō)是立即可以得知的。而普通變量做不到這一點(diǎn)。
第二條語(yǔ)義是禁止指令重排序優(yōu)化,這條語(yǔ)義在JDK1.5才被修復(fù)。
關(guān)于第一點(diǎn):根據(jù)JMM,所有的變量存儲(chǔ)在主內(nèi)存,而每個(gè)線程還有自己的工作內(nèi)存,線程的工作內(nèi)存保存該線程使用到的變量的主內(nèi)存副本拷貝,線程對(duì)變量的操作在工作內(nèi)存中進(jìn)行,不能直接讀寫(xiě)主內(nèi)存的變量。在volatile可見(jiàn)性這一點(diǎn)上,普通變量做不到的原因正因如此。比如,線程A修改了一個(gè)普通變量的值,然后向主內(nèi)存進(jìn)行回寫(xiě),線程B在線程A回寫(xiě)完成后再?gòu)闹鲀?nèi)存讀取,新變量才能對(duì)線程B可見(jiàn)。其實(shí),按照虛擬機(jī)規(guī)范,volatile變量依然有工作內(nèi)存的拷貝,要借助主內(nèi)存來(lái)實(shí)現(xiàn)可見(jiàn)性。但由于volatile的特殊規(guī)則保證了新值能立即同步回主內(nèi)存,以及每次使用從主內(nèi)存刷新,以此保證了多線程操作volatile變量的可見(jiàn)性。
關(guān)于第二點(diǎn):先說(shuō)指令重排序,指令重排序是指CPU采用了允許將多條指令不按規(guī)定順序分開(kāi)發(fā)送給相應(yīng)的處理單元處理,但并不是說(shuō)任意重排,CPU需要正確處理指令依賴(lài)情況確保最終的正確結(jié)果,指令重排序是機(jī)器級(jí)的優(yōu)化操作。那么為什么volatile要禁止指令重排序呢,又是如何去做的。舉例,DCL(雙重檢查加鎖)的單例模式。volatile修飾后,代碼中將會(huì)插入許多內(nèi)存屏障指令保證處理器不發(fā)生亂序執(zhí)行。同時(shí)由于Happens-before規(guī)則的保證,在剛才的例子中寫(xiě)操作會(huì)發(fā)生在后續(xù)的讀操作之前。
除了以上2點(diǎn),volatile還保證對(duì)于64位long和double的讀取是原子性的。因?yàn)樵贘MM中允許虛擬機(jī)對(duì)未被volatile修飾的64位的long和double讀寫(xiě)操作分為2次32位的操作來(lái)執(zhí)行,這也就是所謂的long和double的非原子性協(xié)定。
基于以上幾點(diǎn),我們知道volatile雖然有這些語(yǔ)義和特性在并發(fā)的情況下仍然不能保證線程安全。大部分情況下仍然需要加鎖。
除非是以下2種情況:
- 運(yùn)算結(jié)果不依賴(lài)變量的當(dāng)前值,或者能夠確保只有單一線程修改變量的值;
- 變量不需要與其他的狀態(tài)變量共同參與不變約束。
Java 中,編寫(xiě)多線程程序的時(shí)候你會(huì)遵循哪些最佳實(shí)踐?
- 給線程命名,這樣可以幫助調(diào)試。
- 最小化同步的范圍,而不是將整個(gè)方法同步,只對(duì)關(guān)鍵部分做同步。
- 如果可以,更偏向于使用 volatile 而不是 synchronized。
- 使用更高層次的并發(fā)工具,而不是使用 wait() 和 notify() 來(lái)實(shí)現(xiàn)線程間通信,如 BlockingQueue,CountDownLatch 及 Semeaphore。
- 優(yōu)先使用并發(fā)集合,而不是對(duì)集合進(jìn)行同步。并發(fā)集合提供更好的可擴(kuò)展性。
什么是線程安全?Vector 是一個(gè)線程安全類(lèi)嗎
如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。一個(gè)線程安全的計(jì)數(shù)器類(lèi)的同一個(gè)實(shí)例對(duì)象在被多個(gè)線程使用的情況下也不會(huì)出現(xiàn)計(jì)算失誤。很顯然你可以將集合類(lèi)分成兩組,線程安全和非線程安全的。Vector 是用同步方法來(lái)實(shí)現(xiàn)線程安全的, 而和它相似的 ArrayList 不是線程安全的。
Java 中什么是競(jìng)態(tài)條件? 舉個(gè)例子說(shuō)明。
競(jìng)態(tài)條件會(huì)導(dǎo)致程序在并發(fā)情況下出現(xiàn)一些 bugs。多線程對(duì)一些資源的競(jìng)爭(zhēng)的時(shí)候就會(huì)產(chǎn)生競(jìng)態(tài)條件,如果首先要執(zhí)行的程序競(jìng)爭(zhēng)失敗排到后面執(zhí)行了,那么整個(gè)程序就會(huì)出現(xiàn)一些不確定的 bugs。這種 bugs 很難發(fā)現(xiàn)而且會(huì)重復(fù)出現(xiàn),因?yàn)榫€程間的隨機(jī)競(jìng)爭(zhēng)。一個(gè)例子就是無(wú)序處理。
Java 中如何停止一個(gè)線程
當(dāng) run () 或者 call () 方法執(zhí)行完的時(shí)候線程會(huì)自動(dòng)結(jié)束,如果要手動(dòng)結(jié)束一個(gè)線程,你可以用 volatile 布爾變量來(lái)退出 run ()方法的循環(huán)或者是取消任務(wù)來(lái)中斷線程。其他情形:異常 - 停止執(zhí)行 休眠 - 停止執(zhí)行 阻塞 - 停止執(zhí)行
一個(gè)線程運(yùn)行時(shí)發(fā)生異常會(huì)怎樣
簡(jiǎn)單的說(shuō),如果異常沒(méi)有被捕獲該線程將會(huì)停止執(zhí)行。Thread.UncaughtExceptionHandler 是用于處理未捕獲異常造成線程突然中斷情況的一個(gè)內(nèi)嵌接口。當(dāng)一個(gè)未捕獲異常將造成線程中斷的時(shí)候 JVM 會(huì)使用 Thread.getUncaughtExceptionHandler ()來(lái)查詢線程的 UncaughtExceptionHandler 并將線程和異常作為參數(shù)傳遞給 handler 的 uncaughtException ()方法進(jìn)行處理。
如何在兩個(gè)線程間共享數(shù)據(jù)?
通過(guò)共享對(duì)象來(lái)實(shí)現(xiàn)這個(gè)目的,或者是使用像阻塞隊(duì)列這樣并發(fā)的數(shù)據(jù)結(jié)構(gòu)
Java 中 notify 和 notifyAll 有什么區(qū)別
notify ()方法不能喚醒某個(gè)具體的線程,所以只有一個(gè)線程在等待的時(shí)候它才有用武之地。而 notifyAll ()喚醒所有線程并允許他們爭(zhēng)奪鎖確保了至少有一個(gè)線程能繼續(xù)運(yùn)行。
為什么 wait, notify 和 notifyAll 這些方法不在 thread 類(lèi)里面
一個(gè)很明顯的原因是 JAVA 提供的鎖是對(duì)象級(jí)的而不是線程級(jí)的。如果線程需要等待某些鎖那么調(diào)用對(duì)象中的 wait ()方法就有意義了。如果 wait ()方法定義在 Thread 類(lèi)中,線程正在等待的是哪個(gè)鎖就不明顯了。簡(jiǎn)單的說(shuō),由于 wait,notify 和 notifyAll 都是鎖級(jí)別的操作,所以把他們定義在 Object 類(lèi)中因?yàn)殒i屬于對(duì)象。
什么是ThreadLocal
ThreadLocal,線程局部變量。
當(dāng)使用ThreadLocal維護(hù)變量時(shí),ThreadLocal為每個(gè)使用該變量的線程提供獨(dú)立的變量副本,每個(gè)線程都可以獨(dú)立地改變自己的副本,而不會(huì)影響其它線程所對(duì)應(yīng)的副本,是線程隔離的。線程隔離的秘密在于ThreadLocalMap類(lèi)(ThreadLocal的靜態(tài)內(nèi)部類(lèi))
線程局部變量是局限于線程內(nèi)部的變量,屬于線程自身所有,不在多個(gè)線程間共享。Java 提供 ThreadLocal 類(lèi)來(lái)支持線程局部變量,是一種實(shí)現(xiàn)線程安全的方式。但是在管理環(huán)境下(如 web 服務(wù)器)使用線程局部變量的時(shí)候要特別小心,在這種情況下,工作線程的生命周期比任何應(yīng)用變量的生命周期都要長(zhǎng)。任何線程局部變量一旦在工作完成后沒(méi)有釋放,Java 應(yīng)用就存在內(nèi)存泄露的風(fēng)險(xiǎn)。
ThreadLocal的方法:void set(T value)、T get()以及T initialValue()。
ThreadLocal是如何為每個(gè)線程創(chuàng)建變量的副本的:
首先,在每個(gè)線程Thread內(nèi)部有一個(gè)ThreadLocal.ThreadLocalMap類(lèi)型的成員變量threadLocals,這個(gè)threadLocals就是用來(lái)存儲(chǔ)實(shí)際的變量副本的,鍵值為當(dāng)前ThreadLocal變量,value為變量副本(即T類(lèi)型的變量)。初始時(shí),threadLocals為空,當(dāng)通過(guò)ThreadLocal變量調(diào)用get()方法或者set()方法,就會(huì)對(duì)Thread類(lèi)中的threadLocals進(jìn)行初始化,并且以當(dāng)前ThreadLocal變量為鍵值,以ThreadLocal要保存的副本變量為value,存到threadLocals。然后在當(dāng)前線程里面,如果要使用副本變量,就可以通過(guò)get方法在threadLocals里面查找。
總結(jié):
- 實(shí)際通過(guò)ThreadLocal創(chuàng)建的副本是存儲(chǔ)在每個(gè)線程自己的threadLocals中的
- 為何threadLocals的鍵為T(mén)hreadLocal對(duì)象,因?yàn)槊總€(gè)線程中可有多個(gè)threadLocal變量,就像上面代碼中的longLocal和stringLocal;
- 在進(jìn)行g(shù)et之前,必須先set,否則會(huì)報(bào)空指針異常;如果想在get之前不需要調(diào)用set就能正常訪問(wèn)的話,必須重寫(xiě)initialValue()方法
什么是 FutureTask?
在 Java 并發(fā)程序中 FutureTask 表示一個(gè)可以取消的異步運(yùn)算。它有啟動(dòng)和取消運(yùn)算、查詢運(yùn)算是否完成和取回運(yùn)算結(jié)果等方法。只有當(dāng)運(yùn)算完成的時(shí)候結(jié)果才能取回,如果運(yùn)算尚未完成 get 方法將會(huì)阻塞。一個(gè) FutureTask 對(duì)象可以對(duì)調(diào)用了 Callable 和 Runnable 的對(duì)象進(jìn)行包裝,由于 FutureTask 也是調(diào)用了 Runnable 接口所以它可以提交給 Executor 來(lái)執(zhí)行。
Java 中 interrupted 和 isInterruptedd 方法的區(qū)別
interrupted是靜態(tài)方法,isInterruptedd是一個(gè)普通方法;
如果當(dāng)前線程被中斷(沒(méi)有拋出中斷異常,否則中斷狀態(tài)就會(huì)被清除),你調(diào)用interrupted方法,第一次會(huì)返回true。然后,當(dāng)前線程的中斷狀態(tài)被方法內(nèi)部清除了。第二次調(diào)用時(shí)就會(huì)返回false。如果你剛開(kāi)始一直調(diào)用isInterrupted,則會(huì)一直返回true,除非中間線程的中斷狀態(tài)被其他操作清除了。也就是說(shuō)isInterrupted 只是簡(jiǎn)單的查詢中斷狀態(tài),不會(huì)對(duì)狀態(tài)進(jìn)行修改。
為什么 wait 和 notify 方法要在同步塊中調(diào)用
如果不這么做,代碼會(huì)拋出 IllegalMonitorStateException異常。還有一個(gè)原因是為了避免 wait 和 notify 之間產(chǎn)生競(jìng)態(tài)條件。
為什么你應(yīng)該在循環(huán)中檢查等待條件?
處于等待狀態(tài)的線程可能會(huì)收到錯(cuò)誤警報(bào)和偽喚醒,如果不在循環(huán)中檢查等待條件,程序就會(huì)在沒(méi)有滿足結(jié)束條件的情況下退出。因此,當(dāng)一個(gè)等待線程醒來(lái)時(shí),不能認(rèn)為它原來(lái)的等待狀態(tài)仍然是有效的,在 notify ()方法調(diào)用之后和等待線程醒來(lái)之前這段時(shí)間它可能會(huì)改變。這就是在循環(huán)中使用 wait ()方法效果更好的原因。
Java 中的同步集合與并發(fā)集合有什么區(qū)別
同步集合與并發(fā)集合都為多線程和并發(fā)提供了合適的線程安全的集合,不過(guò)并發(fā)集合的可擴(kuò)展性更高。在 Java1.5 之前程序員們只有同步集合來(lái)用且在多線程并發(fā)的時(shí)候會(huì)導(dǎo)致?tīng)?zhēng)用,阻礙了系統(tǒng)的擴(kuò)展性。Java1.5加入了并發(fā)集合像 ConcurrentHashMap,不僅提供線程安全還用鎖分離和內(nèi)部分區(qū)等現(xiàn)代技術(shù)提高了可擴(kuò)展性。它們大部分位于JUC包下。
Java 中堆和棧有什么不同
每個(gè)線程都有自己的棧內(nèi)存,用于存儲(chǔ)本地變量,方法參數(shù)和棧調(diào)用,一個(gè)線程中存儲(chǔ)的變量對(duì)其它線程是不可見(jiàn)的。而堆是所有線程共享的一片公用內(nèi)存區(qū)域。對(duì)象都在堆里創(chuàng)建,為了提升效率線程會(huì)從堆中弄一個(gè)緩存到自己的棧,如果多個(gè)線程使用該變量就可能引發(fā)問(wèn)題,這時(shí) volatile 變量就可以發(fā)揮作用了,它要求線程從主存中讀取變量的值。
什么是線程池? 為什么要使用它?
創(chuàng)建線程要花費(fèi)昂貴的資源和時(shí)間,如果任務(wù)來(lái)了才創(chuàng)建線程那么響應(yīng)時(shí)間會(huì)變長(zhǎng),而且一個(gè)進(jìn)程能創(chuàng)建的線程數(shù)有限。為了避免這些問(wèn)題,在程序啟動(dòng)的時(shí)候就創(chuàng)建若干線程來(lái)響應(yīng)處理,它們被稱(chēng)為線程池,里面的線程叫工作線程。從 JDK1.5 開(kāi)始,Java API 提供了 Executor 框架讓你可以創(chuàng)建不同的線程池。比如單線程池,每次處理一個(gè)任務(wù);數(shù)目固定的線程池或者是緩存線程池(一個(gè)適合很多生存期短的任務(wù)的程序的可擴(kuò)展線程池)。
如何寫(xiě)代碼來(lái)解決生產(chǎn)者消費(fèi)者問(wèn)題?
在現(xiàn)實(shí)中你解決的許多線程問(wèn)題都屬于生產(chǎn)者消費(fèi)者模型,就是一個(gè)線程生產(chǎn)任務(wù)供其它線程進(jìn)行消費(fèi),你必須知道怎么進(jìn)行線程間通信來(lái)解決這個(gè)問(wèn)題。比較低級(jí)的辦法是用 wait 和 notify 來(lái)解決這個(gè)問(wèn)題,比較贊的辦法是用 Semaphore 或者 BlockingQueue 來(lái)實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模型。
如何避免死鎖?
死鎖是指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過(guò)程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,若無(wú)外力作用,它們都將無(wú)法推進(jìn)下去。這是一個(gè)嚴(yán)重的問(wèn)題,因?yàn)樗梨i會(huì)讓你的程序掛起無(wú)法完成任務(wù),死鎖的發(fā)生必須滿足以下四個(gè)條件:
- 互斥條件:一個(gè)資源每次只能被一個(gè)進(jìn)程使用。
- 請(qǐng)求與保持條件:一個(gè)進(jìn)程因請(qǐng)求資源而阻塞時(shí),對(duì)已獲得的資源保持不放。
- 不剝奪條件:進(jìn)程已獲得的資源,在末使用完之前,不能強(qiáng)行剝奪。
- 循環(huán)等待條件:若干進(jìn)程之間形成一種頭尾相接的循環(huán)等待資源關(guān)系。
避免死鎖最簡(jiǎn)單的方法就是阻止循環(huán)等待條件,將系統(tǒng)中所有的資源設(shè)置標(biāo)志位、排序,規(guī)定所有的進(jìn)程申請(qǐng)資源必須以一定的順序(升序或降序)做操作來(lái)避免死鎖。
Java 中活鎖和死鎖有什么區(qū)別?
活鎖和死鎖類(lèi)似,不同之處在于處于活鎖的線程或進(jìn)程的狀態(tài)是不斷改變的,活鎖可以認(rèn)為是一種特殊的饑餓。一個(gè)現(xiàn)實(shí)的活鎖例子是兩個(gè)人在狹小的走廊碰到,兩個(gè)人都試著避讓對(duì)方好讓彼此通過(guò),但是因?yàn)楸茏尩姆较蚨家粯訉?dǎo)致最后誰(shuí)都不能通過(guò)走廊。簡(jiǎn)單的說(shuō)就是,活鎖和死鎖的主要區(qū)別是前者進(jìn)程的狀態(tài)可以改變但是卻不能繼續(xù)執(zhí)行。
怎么檢測(cè)一個(gè)線程是否擁有鎖
在 java.lang.Thread 中有一個(gè)方法叫 holdsLock (),當(dāng)且僅當(dāng)當(dāng)前線程擁有某個(gè)具體對(duì)象的鎖時(shí)它返回true。
你如何在 Java 中獲取線程堆棧
eak 組合鍵來(lái)獲取線程堆棧,Linux 下用 kill -3 命令。你也可以用 jstack 這個(gè)工具來(lái)獲取,它對(duì)線程 id 進(jìn)行操作,你可以用 jps 這個(gè)工具找到 id。
JVM內(nèi)存配置參數(shù)
- -Xmx:最大堆大小
- -Xms:初始堆大小(最小內(nèi)存值)
- -Xmn:年輕代大小
- -XXSurvivorRatio:3 意思是Eden:Survivor=3:2
- -Xss棧容量
- -XX:+PrintGC 輸出GC日志
- -XX:+PrintGCDetails 輸出GC的詳細(xì)日志
Java 中 synchronized 和 ReentrantLock 有什么不同
Java 在過(guò)去很長(zhǎng)一段時(shí)間只能通過(guò) synchronized 關(guān)鍵字來(lái)實(shí)現(xiàn)互斥,它有一些缺點(diǎn)。比如你不能擴(kuò)展鎖之外的方法或者塊邊界,嘗試獲取鎖時(shí)不能中途取消等。Java 5 通過(guò) Lock 接口提供了更復(fù)雜的控制來(lái)解決這些問(wèn)題。 ReentrantLock 類(lèi)實(shí)現(xiàn)了 Lock,它擁有與 synchronized 相同的并發(fā)性和內(nèi)存語(yǔ)義且它還具有可擴(kuò)展性。
有三個(gè)線程 T1,T2,T3,怎么確保它們按順序執(zhí)行
可以用線程類(lèi)的 join ()方法。具體操作是在T3的run方法中調(diào)用t2.join(),讓t2執(zhí)行完再執(zhí)行t3;T2的run方法中調(diào)用t1.join(),讓t1執(zhí)行完再執(zhí)行t2。這樣就按T1,T2,T3的順序執(zhí)行了
Thread 類(lèi)中的 yield 方法有什么作用
Yield 方法可以暫停當(dāng)前正在執(zhí)行的線程對(duì)象,讓其它有相同優(yōu)先級(jí)的線程執(zhí)行。它是一個(gè)靜態(tài)方法而且只保證當(dāng)前線程放棄 CPU 占用而不能保證使其它線程一定能占用 CPU,執(zhí)行 yield ()的線程有可能在進(jìn)入到暫停狀態(tài)后馬上又被執(zhí)行。
Java 中 ConcurrentHashMap 的并發(fā)度是什么
ConcurrentHashMap 把實(shí)際 map 劃分成若干部分來(lái)實(shí)現(xiàn)它的可擴(kuò)展性和線程安全。這種劃分是使用并發(fā)度獲得的,它是 ConcurrentHashMap 類(lèi)構(gòu)造函數(shù)的一個(gè)可選參數(shù),默認(rèn)值為 16,這樣在多線程情況下就能避免爭(zhēng)用。
Java 中 Semaphore是什么
JUC下的一種新的同步類(lèi),它是一個(gè)計(jì)數(shù)信號(hào)。從概念上講,Semaphore信號(hào)量維護(hù)了一個(gè)許可集合。如有必要,在許可可用前會(huì)阻塞每一個(gè) acquire (),然后再獲取該許可。每個(gè) release ()添加一個(gè)許可,從而可能釋放一個(gè)正在阻塞的獲取者。但是,不使用實(shí)際的許可對(duì)象,Semaphore 只對(duì)可用許可的號(hào)碼進(jìn)行計(jì)數(shù),并采取相應(yīng)的行動(dòng)。信號(hào)量常常用于多線程的代碼中,比如數(shù)據(jù)庫(kù)連接池。
如果你提交任務(wù)時(shí),線程池隊(duì)列已滿。會(huì)發(fā)會(huì)生什么?
這個(gè)問(wèn)題問(wèn)得很狡猾,許多程序員會(huì)認(rèn)為該任務(wù)會(huì)阻塞直到線程池隊(duì)列有空位。事實(shí)上如果一個(gè)任務(wù)不能被調(diào)度執(zhí)行那么 ThreadPoolExecutor’s submit ()方法將會(huì)拋出一個(gè) RejectedExecutionException 異常。
Java 線程池中 submit () 和 execute ()方法有什么區(qū)別
兩個(gè)方法都可以向線程池提交任務(wù),execute ()方法的返回類(lèi)型是 void,它定義在 Executor 接口中, 而 submit ()方法可以返回持有計(jì)算結(jié)果的 Future 對(duì)象,它定義在 ExecutorService 接口中,它擴(kuò)展了 Executor 接口,其它線程池類(lèi)像 ThreadPoolExecutor 和 ScheduledThreadPoolExecutor 都有這些方法。
什么是阻塞式方法?
阻塞式方法是指程序會(huì)一直等待該方法完成期間不做其他事情,ServerSocket 的 accept ()方法就是一直等待客戶端連接。這里的阻塞是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起,直到得到結(jié)果之后才會(huì)返回。此外,還有異步和非阻塞式方法在任務(wù)完成前就返回。
Swing 是線程安全的嗎?
你可以很肯定的給出回答,Swing 不是線程安全的。你不能通過(guò)任何線程來(lái)更新 Swing 組件,如 JTable、JList 或 JPanel,事實(shí)上,它們只能通過(guò) GUI 或 AWT 線程來(lái)更新。這就是為什么 Swing 提供 invokeAndWait() 和 invokeLater() 方法來(lái)獲取其他線程的 GUI 更新請(qǐng)求。這些方法將更新請(qǐng)求放入 AWT 的線程隊(duì)列中,可以一直等待,也可以通過(guò)異步更新直接返回結(jié)果。
Java 中 invokeAndWait 和 invokeLater 有什么區(qū)別
這兩個(gè)方法是 Swing API 提供給 Java 開(kāi)發(fā)者用來(lái)從當(dāng)前線程而不是事件派發(fā)線程更新 GUI 組件用的。InvokeAndWait ()同步更新 GUI 組件,比如一個(gè)進(jìn)度條,一旦進(jìn)度更新了,進(jìn)度條也要做出相應(yīng)改變。如果進(jìn)度被多個(gè)線程跟蹤,那么就調(diào)用 invokeAndWait ()方法請(qǐng)求事件派發(fā)線程對(duì)組件進(jìn)行相應(yīng)更新。而 invokeLater ()方法是異步調(diào)用更新組件的。
Swing API 中那些方法是線程安全的?
雖然Swing不是線程安全的但是有一些方法是可以被多線程安全調(diào)用的。如repaint (), revalidate ()。 JTextComponent 的 setText ()方法和 JTextArea 的 insert () 和 append () 方法也是線程安全的。
如何在 Java 中創(chuàng)建 Immutable 對(duì)象
Immutable 對(duì)象可以在沒(méi)有同步的情況下共享,降低了對(duì)該對(duì)象進(jìn)行并發(fā)訪問(wèn)時(shí)的同步化開(kāi)銷(xiāo)??墒?Java 沒(méi)有@Immutable 這個(gè)注解符,要?jiǎng)?chuàng)建不可變類(lèi),要實(shí)現(xiàn)下面幾個(gè)步驟:通過(guò)構(gòu)造方法初始化所有成員、對(duì)變量不要提供 setter 方法、將所有的成員聲明為私有的,這樣就不允許直接訪問(wèn)這些成員、在 getter 方法中,不要直接返回對(duì)象本身,而是克隆對(duì)象,并返回對(duì)象的拷貝。
Java 中的 ReadWriteLock 是什么?
一般而言,讀寫(xiě)鎖是用來(lái)提升并發(fā)程序性能的鎖分離技術(shù)的成果。Java 中的 ReadWriteLock 是 Java 5 中新增的一個(gè)接口,一個(gè) ReadWriteLock 維護(hù)一對(duì)關(guān)聯(lián)的鎖,一個(gè)用于只讀操作一個(gè)用于寫(xiě)。在沒(méi)有寫(xiě)線程的情況下一個(gè)讀鎖可能會(huì)同時(shí)被多個(gè)讀線程持有。寫(xiě)鎖是獨(dú)占的,你可以使用 JDK 中的 ReentrantReadWriteLock 來(lái)實(shí)現(xiàn)這個(gè)規(guī)則,它最多支持 65535 個(gè)寫(xiě)鎖和 65535 個(gè)讀鎖。
多線程中的忙循環(huán)是什么?
忙循環(huán)就是程序員用循環(huán)讓一個(gè)線程等待,不像傳統(tǒng)方法 wait (), sleep () 或 yield () 它們都放棄了 CPU 控制,而忙循環(huán)不會(huì)放棄 CPU,它就是在運(yùn)行一個(gè)空循環(huán)。這么做的目的是為了保留 CPU 緩存,在多核系統(tǒng)中,一個(gè)等待線程醒來(lái)的時(shí)候可能會(huì)在另一個(gè)內(nèi)核運(yùn)行,這樣會(huì)重建緩存。為了避免重建緩存和減少等待重建的時(shí)間就可以使用它了。
volatile 變量和 atomic 變量有什么不同
volatile 變量和 atomic 變量看起來(lái)很像,但功能卻不一樣。volatile 變量可以確保先行關(guān)系,即寫(xiě)操作會(huì)發(fā)生在后續(xù)的讀操作之前, 但它并不能保證原子性。例如用 volatile 修飾 count 變量那么 count++ 操作并不是原子性的。而 AtomicInteger 類(lèi)提供的 atomic 方法可以讓這種操作具有原子性如 getAndIncrement ()方法會(huì)原子性的進(jìn)行增量操作把當(dāng)前值加一,其它數(shù)據(jù)類(lèi)型和引用變量也可以進(jìn)行相似操作。
如果同步塊內(nèi)的線程拋出異常會(huì)發(fā)生什么?
無(wú)論你的同步塊是正常還是異常退出的,里面的線程都會(huì)釋放鎖,所以對(duì)比鎖接口我更喜歡同步塊,因?yàn)樗挥梦一ㄙM(fèi)精力去釋放鎖,該功能可以在 finally block 里釋放鎖實(shí)現(xiàn)。
如何在 Java 中創(chuàng)建線程安全的 Singleton
5種:急加載,同步方法,雙檢鎖,靜態(tài)內(nèi)部類(lèi),枚舉
如何強(qiáng)制啟動(dòng)一個(gè)線程?
這個(gè)問(wèn)題就像是如何強(qiáng)制進(jìn)行 Java 垃圾回收,目前還沒(méi)有覺(jué)得方法,雖然你可以使用 System.gc ()來(lái)進(jìn)行垃圾回收,但是不保證能成功。在 Java 里面沒(méi)有辦法強(qiáng)制啟動(dòng)一個(gè)線程,它是被線程調(diào)度器控制著且 Java 沒(méi)有公布相關(guān)的 API。
Java 中的 fork join 框架是什么?
fork join 框架是 JDK7 中出現(xiàn)的一款高效的工具,Java 開(kāi)發(fā)人員可以通過(guò)它充分利用現(xiàn)代服務(wù)器上的多處理器。它是專(zhuān)門(mén)為了那些可以遞歸劃分成許多子模塊設(shè)計(jì)的,目的是將所有可用的處理能力用來(lái)提升程序的性能。fork join 框架一個(gè)巨大的優(yōu)勢(shì)是它使用了工作竊取算法,可以完成更多任務(wù)的工作線程可以從其它線程中竊取任務(wù)來(lái)執(zhí)行。
Java 多線程中調(diào)用 wait () 和 sleep ()方法有什么不同?
Java 程序中 wait 和 sleep 都會(huì)造成某種形式的暫停,它們可以滿足不同的需要。wait ()方法意味著條件等待,如果等待條件為真且其它線程被喚醒時(shí)它會(huì)釋放鎖,而 sleep ()方法僅僅釋放 CPU 資源或者讓當(dāng)前線程短暫停頓,但不會(huì)釋放鎖。
雙親委派模型中的方法
findLoadedClass(),LoadClass(),findBootstrapClassOrNull(),findClass(),resolveClass()
NIO、AIO、BIO
- BIO即同步阻塞IO,適用于連接數(shù)目較小且固定的架構(gòu),這種方式對(duì)服務(wù)器資源要求比較高,并發(fā)局限于應(yīng)用中,JDK1.4之前的唯一選擇,但程序直觀、簡(jiǎn)單、易理解。
- NIO即同步非阻塞IO,適用于連接數(shù)目多且連接比較短的架構(gòu),比如聊天服務(wù)器,并發(fā)局限于應(yīng)用中,編程比較復(fù)雜,JDK1.4開(kāi)始支持。
- AIO即異步非阻塞IO,適用于連接數(shù)目多且連接比較長(zhǎng)的架構(gòu),如相冊(cè)服務(wù)器,充分調(diào)用OS參與并發(fā)操作,編程比較復(fù)雜,JDK1.7開(kāi)始支持
多線程、并發(fā)及線程的基礎(chǔ)問(wèn)題
> Java 中能創(chuàng)建 volatile 數(shù)組嗎?
能,Java 中可以創(chuàng)建 volatile 類(lèi)型數(shù)組,不過(guò)只是一個(gè)指向數(shù)組的引用,而不是整個(gè)數(shù)組。我的意思是,如果改變引用指向的數(shù)組,將會(huì)受到 volatile 的保護(hù),但是如果多個(gè)線程同時(shí)改變數(shù)組的元素,volatile 標(biāo)示符就不能起到之前的保護(hù)作用了。
volatile 能使得一個(gè)非原子操作變成原子操作嗎?
一個(gè)典型的例子是在類(lèi)中有一個(gè) long 類(lèi)型的成員變量。如果你知道該成員變量會(huì)被多個(gè)線程訪問(wèn),如計(jì)數(shù)器、價(jià)格等,你最好是將其設(shè)置為 volatile。為什么?因?yàn)?Java 中讀取 long 類(lèi)型變量不是原子的,需要分成兩步,如果一個(gè)線程正在修改該 long 變量的值,另一個(gè)線程可能只能看到該值的一半(前 32 位)。但是對(duì)一個(gè) volatile 型的 long 或 double 變量的讀寫(xiě)是原子。
volatile 修飾符的有過(guò)什么實(shí)踐?
一種實(shí)踐是用 volatile 修飾 long 和 double 變量,使其能按原子類(lèi)型來(lái)讀寫(xiě)。double 和 long 都是64位寬,因此對(duì)這兩種類(lèi)型的讀是分為兩部分的,第一次讀取第一個(gè) 32 位,然后再讀剩下的 32 位,這個(gè)過(guò)程不是原子的,但 Java 中 volatile 型的 long 或 double 變量的讀寫(xiě)是原子的。volatile 修復(fù)符的另一個(gè)作用是提供內(nèi)存屏障(memory barrier),例如在分布式框架中的應(yīng)用。簡(jiǎn)單的說(shuō),就是當(dāng)你寫(xiě)一個(gè) volatile 變量之前,Java 內(nèi)存模型會(huì)插入一個(gè)寫(xiě)屏障(write barrier),讀一個(gè) volatile 變量之前,會(huì)插入一個(gè)讀屏障(read barrier)。意思就是說(shuō),在你寫(xiě)一個(gè) volatile 域時(shí),能保證任何線程都能看到你寫(xiě)的值,同時(shí),在寫(xiě)之前,也能保證任何數(shù)值的更新對(duì)所有線程是可見(jiàn)的,因?yàn)閮?nèi)存屏障會(huì)將其他所有寫(xiě)的值更新到緩存。
volatile 類(lèi)型變量提供什么保證?
volatile 變量提供順序和可見(jiàn)性保證,例如,JVM 或者 JIT為了獲得更好的性能會(huì)對(duì)語(yǔ)句重排序,但是 volatile 類(lèi)型變量即使在沒(méi)有同步塊的情況下賦值也不會(huì)與其他語(yǔ)句重排序。 volatile 提供 happens-before 的保證,確保一個(gè)線程的修改能對(duì)其他線程是可見(jiàn)的。某些情況下,volatile 還能提供原子性,如讀 64 位數(shù)據(jù)類(lèi)型,像 long 和 double 都不是原子的,但 volatile 類(lèi)型的 double 和 long 就是原子的。
10 個(gè)線程和 2 個(gè)線程的同步代碼,哪個(gè)更容易寫(xiě)?
從寫(xiě)代碼的角度來(lái)說(shuō),兩者的復(fù)雜度是相同的,因?yàn)橥酱a與線程數(shù)量是相互獨(dú)立的。但是同步策略的選擇依賴(lài)于線程的數(shù)量,因?yàn)樵蕉嗟木€程意味著更大的競(jìng)爭(zhēng),所以你需要利用同步技術(shù),如鎖分離,這要求更復(fù)雜的代碼和專(zhuān)業(yè)知識(shí)。
你是如何調(diào)用 wait()方法的?使用 if 塊還是循環(huán)?為什么?
wait() 方法應(yīng)該在循環(huán)調(diào)用,因?yàn)楫?dāng)線程獲取到 CPU 開(kāi)始執(zhí)行的時(shí)候,其他條件可能還沒(méi)有滿足,所以在處理前,循環(huán)檢測(cè)條件是否滿足會(huì)更好。下面是一段標(biāo)準(zhǔn)的使用 wait 和 notify 方法的代碼:
// The standard idiom for using the wait methodsynchronized (obj) { while (condition does not hold) obj.wait(); // (Releases lock, and reacquires on wakeup)... // Perform action appropriate to condition }參見(jiàn) Effective Java 第 69 條,獲取更多關(guān)于為什么應(yīng)該在循環(huán)中來(lái)調(diào)用 wait 方法的內(nèi)容。
什么是多線程環(huán)境下的偽共享(false sharing)?
偽共享是多線程系統(tǒng)(每個(gè)處理器有自己的局部緩存)中一個(gè)眾所周知的性能問(wèn)題。偽共享發(fā)生在不同處理器的上的線程對(duì)變量的修改依賴(lài)于相同的緩存行,如下圖所示:
圖-1.gif
偽共享問(wèn)題很難被發(fā)現(xiàn),因?yàn)榫€程可能訪問(wèn)完全不同的全局變量,內(nèi)存中卻碰巧在很相近的位置上。如其他諸多的并發(fā)問(wèn)題,避免偽共享的最基本方式是仔細(xì)審查代碼,根據(jù)緩存行來(lái)調(diào)整你的數(shù)據(jù)結(jié)構(gòu)。
GC、內(nèi)存相關(guān)
對(duì)哪些區(qū)域回收 Java運(yùn)行時(shí)數(shù)據(jù)區(qū)域:程序計(jì)數(shù)器、JVM棧、本地方法棧、方法區(qū)和堆。
由于程序計(jì)數(shù)器、JVM棧、本地方法棧3個(gè)區(qū)域隨線程而生隨線程而滅,對(duì)這幾個(gè)區(qū)域內(nèi)存的回收和分配具有確定性。而方法區(qū)和堆則不一樣,程序需要在運(yùn)行時(shí)才知道創(chuàng)建哪些對(duì)象,對(duì)這部分內(nèi)存的分配是動(dòng)態(tài)的,GC關(guān)注的也就是這部分內(nèi)存。
如何判定對(duì)象需要回收
引用計(jì)數(shù)法:給對(duì)象加上一個(gè)計(jì)數(shù)器,當(dāng)有一個(gè)地方引用它,計(jì)數(shù)器+1,引用失效時(shí),計(jì)數(shù)器-1,當(dāng)計(jì)數(shù)器為0時(shí),判定該對(duì)象可回收。引用計(jì)數(shù)法優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,python,flashplayer等使用引用計(jì)數(shù)法進(jìn)行內(nèi)存管理。引用計(jì)數(shù)法的缺點(diǎn)在于無(wú)法解決循環(huán)引用的問(wèn)題。
在Java中使用可達(dá)性分析算法法判定對(duì)象是否“死亡”??蛇_(dá)性分析法是指通過(guò)稱(chēng)為GC-Roots的對(duì)象為起始點(diǎn),從這些結(jié)點(diǎn)向下搜索,當(dāng)從GCRoots到這個(gè)對(duì)象不可達(dá)時(shí),被判定為可收回的對(duì)象。
可作為GC Roots的對(duì)象
可作為GC Roots的對(duì)象:虛擬機(jī)棧中引用的對(duì)象 方法區(qū)中靜態(tài)屬性引用的對(duì)象 方法區(qū)中常量引用的對(duì)象 本地方法棧中JNI引用的對(duì)象
對(duì)象的自我救贖
即使在可達(dá)性算法中判定為不可達(dá)時(shí),也并非一定被回收。對(duì)象存在自我救贖的可能。要真正宣告對(duì)象的死亡,需要經(jīng)歷2次標(biāo)記的過(guò)程。如果對(duì)象經(jīng)過(guò)可達(dá)性分析法發(fā)現(xiàn)不可達(dá)時(shí),對(duì)象將被第一次標(biāo)記被進(jìn)行篩選,篩選的條件是此對(duì)象是否有必要執(zhí)行finalize方法。如果對(duì)象沒(méi)有重寫(xiě)finalize方法或finalize方法已經(jīng)被JVM調(diào)用過(guò),則判定為不需要執(zhí)行。
如果對(duì)象被判定為需要執(zhí)行finalize方法,該對(duì)象將被放置在一個(gè)叫做F-Queue的隊(duì)列中,JVM會(huì)建立一個(gè)低優(yōu)先級(jí)的線程執(zhí)行finalize方法,如果對(duì)象想要完成自我救贖需要在finalize方法中與引用鏈上的對(duì)象關(guān)聯(lián),比如把自己也就是this賦值給某個(gè)類(lèi)變量。當(dāng)GC第二次對(duì)F-Queue中對(duì)象標(biāo)記時(shí),該對(duì)象將被移出“即將回收”的集合,完成自我救贖。簡(jiǎn)言之,finalize方法是對(duì)象逃脫死亡命運(yùn)的最后機(jī)會(huì),并且任何對(duì)象的finalize方法只會(huì)被JVM調(diào)用一次。
垃圾回收算法
- Mark-Sweep法:標(biāo)記清除法,容易產(chǎn)生內(nèi)存碎片,導(dǎo)致分配較大對(duì)象時(shí)沒(méi)有足夠的連續(xù)內(nèi)存空間而提前出發(fā)GC。這里涉及到另一個(gè)問(wèn)題,即對(duì)象創(chuàng)建時(shí)的內(nèi)存分配,對(duì)象創(chuàng)建內(nèi)存分配主要有2種方法,分別是指針碰撞法和空閑列表法:
- 指針碰撞法:使用的內(nèi)存在一側(cè),空閑的在另一側(cè),中間使用一個(gè)指針作為分界點(diǎn)指示器,對(duì)象內(nèi)存分配時(shí)只要指針向空閑的移動(dòng)對(duì)象大小的距離即可。
- 空閑列表法:使用的和空閑的內(nèi)存相互交錯(cuò)無(wú)法進(jìn)行指針碰撞,JVM必須維護(hù)一個(gè)列表記錄哪些內(nèi)存塊可用,分配時(shí)從列表中找出一個(gè)足夠的分配給對(duì)象,并更新列表記錄。
所以,當(dāng)采用Mark-Sweep算法的垃圾回收器時(shí),內(nèi)存分配通常采用空閑列表法。
- Copy法:將內(nèi)存分為2塊,每次使用其中的一塊,當(dāng)一塊滿了,將存活的對(duì)象復(fù)制到另一塊,把使用過(guò)的那一塊一次性清除。顯然,Copy法解決了內(nèi)存碎片的問(wèn)題,但算法的代價(jià)是內(nèi)存縮小為原來(lái)的一半。現(xiàn)代的垃圾收集器對(duì)新生代采用的正是Copy算法。但通常不執(zhí)行1:1的策略,HotSpot虛擬機(jī)默認(rèn)Eden區(qū)Survivor區(qū)8:1。每次使用Eden和其中一塊Survivor區(qū)。也就是說(shuō)新生代可用內(nèi)存為新生代內(nèi)存空間的90%。
- Mark-Compact法:標(biāo)記整理法。它的第一階段與Mark-Sweep法一樣,但不直接清除,而是將存活對(duì)象向一端移動(dòng),然后清除端邊界以外的內(nèi)存,這樣也不存在內(nèi)存碎片。
- 分代收集算法:將堆內(nèi)存劃分為新生代,老年代,根據(jù)新生代老年代的特點(diǎn)選取不同的收集算法。因?yàn)樾律鷮?duì)象大多朝生夕死,而老年代對(duì)象存活率高,沒(méi)有額外空間進(jìn)行分配擔(dān)保,通常對(duì)新生代執(zhí)行復(fù)制算法,老年代執(zhí)行Mark-Sweep算法或Mark-Compact算法。
垃圾收集器
通常來(lái)說(shuō),新生代老年代使用不同的垃圾收集器。新生代的垃圾收集器有Serial(單線程)、ParNew(Serial的多線程版本)、ParallelScavenge(吞吐量?jī)?yōu)先的垃圾收集器),老年代有SerialOld(單線程老年代)、ParallelOld(與ParallelScavenge搭配的多線程執(zhí)行標(biāo)記整理算法的老年代收集器)、CMS(標(biāo)記清除算法,容易產(chǎn)生內(nèi)存碎片,可以開(kāi)啟內(nèi)存整理的參數(shù)),以及當(dāng)前最先進(jìn)的垃圾收集器G1,G1通常面向服務(wù)器端的垃圾收集器,在我自己的Java應(yīng)用程序中通過(guò)-XX:+PrintGCDetails,發(fā)現(xiàn)自己的垃圾收集器是使用了ParallelScavenge+ParallelOld的組合。
內(nèi)存分配和回收的策略
- 對(duì)象優(yōu)先在Eden區(qū)分配,默認(rèn)Eden與Survivor的比例為8:1
- 大對(duì)象直接進(jìn)入老年代
- 長(zhǎng)期存活的進(jìn)入老年代:JVM給每個(gè)對(duì)象定義一個(gè)年齡計(jì)數(shù)器,當(dāng)對(duì)象在Eden區(qū)出生并躲過(guò)一次MinorGC,并且Survivor可以容納的話,將被移入Survivor區(qū),年齡設(shè)為1。以后每在Survivor區(qū)躲過(guò)一次MinorGC,年齡加一歲,當(dāng)對(duì)象年齡加到15歲時(shí),晉升到老年代。當(dāng)然15歲的默認(rèn)值可以通過(guò)-XX虛擬機(jī)參數(shù)設(shè)置。
- 動(dòng)態(tài)對(duì)象年齡判定:有的時(shí)候無(wú)需到達(dá)15歲即晉升老年代。判定方法是如果Survivor區(qū)中相同年齡的所有對(duì)象大小的總和大于Survivor區(qū)空間的一半,年齡大于或等于該年齡的對(duì)象直接進(jìn)入老年代
- 空間分配擔(dān)保
在發(fā)生MinorGC之前,虛擬機(jī)會(huì)檢查老年代最大可用連續(xù)空間是否大于新生代所有對(duì)象總和,如果成立,確保這次MinorGC安全。否則,虛擬機(jī)會(huì)查看HandlePromotionFailure設(shè)置是否允許擔(dān)保失敗。如果允許,虛擬機(jī)會(huì)接著查看老年代最大連續(xù)可用空間是否大于歷次晉升到老年代對(duì)象的平均大小,如果大于,則進(jìn)行一次MinorGC,盡管這次MinorGC是有風(fēng)險(xiǎn)的,如果小于或者HandlePromotionFailure設(shè)置為不允許,要改為一次FullGC
方法區(qū)的回收
方法區(qū)通常會(huì)與永久代劃等號(hào),實(shí)際上二者并不等價(jià),只不過(guò)是HotSpot虛擬機(jī)設(shè)計(jì)者用永久代實(shí)現(xiàn)方法區(qū),并將GC分代擴(kuò)展至方法區(qū)。 永久代垃圾回收通常包括兩部分內(nèi)容:廢棄常量和無(wú)用的類(lèi)。常量的回收與堆區(qū)對(duì)象的回收類(lèi)似,當(dāng)沒(méi)有其他地方引用該字面量時(shí),如果有必要,將被清理出常量池。
判定無(wú)用的類(lèi)的3個(gè)條件:
- 該類(lèi)的所有實(shí)例都已經(jīng)被回收,也就是說(shuō)堆中不存在該類(lèi)的任何實(shí)例
- 加載該類(lèi)的ClassLoader已經(jīng)被回收
- 該類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)反射訪問(wèn)該類(lèi)的方法。
當(dāng)然,這也僅僅是判定,不代表立即卸載該類(lèi)。
Java中有內(nèi)存泄漏嗎?
內(nèi)存泄露的定義: 當(dāng)某些對(duì)象不再被應(yīng)用程序所使用,但是由于仍然被引用而導(dǎo)致垃圾收集器不能釋放。
內(nèi)存泄漏的原因:對(duì)象的生命周期不同。比如說(shuō)
對(duì)象A引用了對(duì)象B,A的生命周期比B的要長(zhǎng)得多,當(dāng)對(duì)象B在應(yīng)用程序中不會(huì)再被使用以后,對(duì)象 A 仍然持有著B(niǎo)的引用。 (根據(jù)虛擬機(jī)規(guī)范)在這種情況下GC不能將B從內(nèi)存中釋放。這種情況很可能會(huì)引起內(nèi)存問(wèn)題,倘若A還持有著其他對(duì)象的引用,那么這些被引用的(無(wú)用)對(duì)象也不會(huì)被回收,并占用著內(nèi)存空間。甚至有可能B也持有一大堆其他對(duì)象的引用。這些對(duì)象由于被 B 所引用,也不會(huì)被垃圾收集器所回收,所有這些無(wú)用的對(duì)象將消耗大量寶貴的內(nèi)存空間。并可能導(dǎo)致內(nèi)存泄漏
怎樣防止:
1、當(dāng)心集合類(lèi),比如HashMap、ArrayList等,因?yàn)檫@是最容易發(fā)生內(nèi)存泄露的地方。當(dāng)集合對(duì)象被聲明為static時(shí),他們的生命周期一般和整個(gè)應(yīng)用程序一樣長(zhǎng)。
OOM解決辦法:
內(nèi)存溢出的空間:Permanent Generation和Heap Space,也就是永久代和堆區(qū)
第一種情況永久代的溢出:出現(xiàn)這種問(wèn)題的原因可能是應(yīng)用程序加載了大量的jar或class,使虛擬機(jī)裝載類(lèi)的空間不夠,與Permanent Generation Space的大小有關(guān)。
解決辦法有2種:
- 通過(guò)虛擬機(jī)參數(shù)-XX:PermSize和-XX:MaxPermSize調(diào)整永久代大小
- 清理程序中的重復(fù)的Jar文件,減少類(lèi)的重復(fù)加載
第二種堆區(qū)的溢出:發(fā)生這種問(wèn)題的原因是java虛擬機(jī)創(chuàng)建的對(duì)象太多,在進(jìn)行垃圾回收之間,虛擬機(jī)分配的到堆內(nèi)存空間已經(jīng)用滿了,與Heap Space的size有關(guān)。解決這類(lèi)問(wèn)題有兩種思路:
- 檢查程序,看是否存在死循環(huán)或不必要地重復(fù)創(chuàng)建大量對(duì)象,定位原因,修改程序和算法。
- 通過(guò)虛擬機(jī)參數(shù)-Xms和-Xmx設(shè)置初始堆和最大堆的大小
DirectMemory直接內(nèi)存
直接內(nèi)存并不是Java虛擬機(jī)規(guī)范定義的內(nèi)存區(qū)域的一部分,但是這部分內(nèi)存也被頻繁使用,而且也可能導(dǎo)致OOM異常的出現(xiàn)。
JDK1.4引入了NIO,這是一種基于通道和緩沖區(qū)的非阻塞IO模式,它可以使用Native函數(shù)庫(kù)分配直接堆外內(nèi)存,然后通過(guò)一個(gè)存儲(chǔ)在Java堆中的DirectByteBuffer對(duì)象作為這塊內(nèi)存的引用進(jìn)行操作,使得在某些場(chǎng)合顯著提高性能,因?yàn)樗苊饬嗽贘ava堆和本地堆之間來(lái)回復(fù)制數(shù)據(jù)。
