一、JAVA部分
1、JAVA基礎
1.1 Java 常用包總結
java常用包:
java.lang–語言包:Java語言的基礎類,包括Object類、Thread類、String、Math、System、Runtime、Class、Exception、Process等,是Java的核心類庫
java.util–實用工具包:Scanner、Date、Calendar、LinkedList、AarryList、concurrent、Hashtable、Stack、TreeSet等;
java.NET–網絡功能包:URL、Socket、ServerSocket等;
java.sql–數據庫連接包:實現JDBC的類庫;
java.io–輸入輸出包:提供與流相關的各種包;
Java 常用第三方jar包:
log4j:一個非常常用的log日誌jar包。
JUnit:java單元測試;
maven:項目管理的;
gson:Google 的Json解析庫;
jsoup:html解析;
apache commons:包含了大量組件,很多實用小工具。
Java常用接口:
Comparable ,Collection,Set, List, Map, Runnable Iterable Iterator 等等
1.2 Java標識符
標識符是什麼?
標識符就是用於Java程序中變量,類,方法等命名的符號。
使用標識符時,需要遵守幾條規則:
-
標識符可以由字母,數字,下劃線(——),美元($)組成,但是不能包含@,%,空格等其他的特殊符號,不能以數字開頭。例如 123name 就是不合法的
-
標識符不能是Java關鍵字和保留字(Java預留的關鍵字,或者以後升級版本中有可能作爲關鍵字),但可以包含關鍵字和保留字~例如:不可以使用void 作爲標識符,但是Myvoid 可以
-
標識符是嚴格卻分大小寫的,所以一定要分清alibaba和ALIbaba是兩個不同的標識符哦
-
標識符的命名最好能反應出其作用,做到見名知意
1.3 Java特性
封裝、抽象、繼承、多態
1.4 匿名對象
匿名對象使用方法
當對象對方法僅進行一次調用的時候,就可以簡化成匿名對象。
new Car().run()
new Car().run()
匿名對象可以作爲實際參數進行傳遞。
public static void show(Car c)
{
//......
}
show(new Car());
匿名對象要注意的事項:
我們一般不會給匿名對象賦予屬性值,因爲永遠無法獲取到。
兩個匿名對象永遠都不可能是同一個對象。
1.5 繼承
Java之中只允許多層繼承,不允許多重繼承,Java存在單繼承侷限。
父類私有屬性發現無法直接進行訪問,但是卻發現可以通過setter、getter方法間接的進行操作
現在默認調用的是無參構造,而如果這個時候父類沒有無參構造,則子類必須通過super()調用指定參數的構造方法.
1.6 抽象類和接口
抽象類
定義:抽象方法必須用abstract關鍵字進行修飾。如果一個類含有抽象方法,則稱這個類爲抽象類,抽象類必須在類前用abstract關鍵字修飾。因爲抽象類中含有無具體實現的方法,所以不能用抽象類創建對象。
說明:包含抽象方法的類稱爲抽象類,但並不意味着抽象類中只能有抽象方法,它和普通類一樣,同樣可以擁有成員變量和普通的成員方法。注意,抽象類和普通類的主要有三點區別:
-
抽象方法必須爲public或者protected(因爲如果爲private,則不能被子類繼承,子類便無法實現該方法),缺省情況下默認爲public。
-
抽象類不能用來創建對象;
-
如果一個類繼承於一個抽象類,則子類必須實現父類的抽象方法。如果子類沒有實現父類的抽象方法,則必須將子類也定義爲爲abstract類。
接口
一、基本概念
接口(Interface),在JAVA編程語言中是一個抽象類型,是抽象方法的集合。接口通常以interface來聲明。一個類通過繼承接口的方式,從而來繼承接口的抽象方法。
如果一個類只由抽象方法和全局常量組成,那麼這種情況下不會將其定義爲一個抽象類,只會定義爲一個接口。所以接口嚴格的來講屬於一個特殊的類,而這個類裏面只有抽象方法和全局常量,就連構造方法也沒有。
範例:定義一個接口
interface A{//定義一個接口
public static final String MSG = "hello";//全局常量
public abstract void print();//抽象方法
}
class B implements A{
public void print(){...};
}
public class TestDemo {
public static void main(String[] args){
A test = new B();
A.print();
}
}
二、接口的使用
1.由於接口裏面存在抽象方法,所以接口對象不能直接使用關鍵字new進行實例化。接口的使用原則如下:
- (1)接口必須要有子類,但此時一個子類可以使用implements關鍵字實現多個接口;
- (2)接口的子類(如果不是抽象類),那麼必須要覆寫接口中的全部抽象方法;
- (3)接口的對象可以利用子類對象的向上轉型進行實例化。
2.對於子類而言,除了實現接口外,還可以繼承抽象類。若既要繼承抽象類,同時還要實現接口的話,使用一下語法格式:
class 子類 [extends 父類] [implemetns 接口1,接口2,...] {}
class X extends C implements A,B {}
3.在Java中,一個抽象類只能繼承一個抽象類,但一個接口卻可以使用extends關鍵字同時繼承多個接口(但接口不能繼承抽象類)。
範例:
interface A{
public void funA();
}
interface B{
public void funB();
}
//C接口同時繼承了A和B兩個接口
interface C extends A,B{//使用的是extends
public void funC();
}
抽象類和接口的異同
由此可見,從繼承關係來說接口的限制比抽象類少:
(1)一個抽象類只能繼承一個抽象父類,而接口可以繼承多個接口;
(2)一個子類只能繼承一個抽象類,卻可以實現多個接口(在Java中,接口的主要功能是解決單繼承侷限問題)
設計層面上的區別:
(1)抽象類是對一種事物的抽象,即對類抽象,而接口是對行爲的抽象。抽象類是對整個類整體進行抽象,包括屬性、行爲,但是接口卻是對類局部(行爲)進行抽象。
(2)設計層面不同,抽象類作爲很多子類的父類,它是一種模板式設計。而接口是一種行爲規範,它是一種輻射式設計。
1.7 內部類
在Java中,可以將一個類定義在另一個類裏面或者一個方法裏面,這樣的類稱爲內部類。廣泛意義上的內部類一般來說包括這四種:成員內部類、局部內部類、匿名內部類和靜態內部類。下面就先來了解一下這四種內部類的用法。
匿名內部類創建線程的兩種方式:
public class ThreadDemo {
public static void main(String[] args) {
// 繼承thread類實現多線程
new Thread() {
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "--"
+ x);
}
}
}.start();
;
// 實現runnable藉口,創建多線程並啓動
new Thread(new Runnable() {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(Thread.currentThread().getName() + "--"
+ x);
}
}
}) {
}.start();
}
}
當匿名內部類要使用外部定義的變量時,這個變量必須爲final.
1.8 Static方法和非Static方法
Static方法可以訪問靜態方法和靜態變量,不可以訪問非靜態方法和變量;非Static方法可以訪問靜態方法和變量;
1.9 JAVA創建對象的五種方式
- new對象
- Class 類的 newInstance() 方法 —>Person p2 = (Person) Class.forName(“com.ys.test.Person”).newInstance();
- Constructor 類的 newInstance 方法 —>Person p3 = (Person) Person.class.getConstructors()[0].newInstance();
- 利用 Clone 方法 —>Person p4 = (Person) p3.clone();
- 反序列化
1.10 final、finally、finalize的區別
- final修飾符(關鍵字)
- finally是在異常處理時提供finally塊來執行任何清除操作。
- finalize是方法名。java技術允許使用finalize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。
1.11 JAVA異常
1.11.1 概念
如果某個方法不能按照正常的途徑完成任務,就可以通過另一種路徑退出方法。在這種情況下會拋出一個封裝了錯誤信息的對象。此時,這個方法會立刻退出同時不返回任何值。另外,調用這個方法的其他代碼也無法繼續執行,異常處理機制會將代碼執行交給異常處理器。
1.11.2 處理機制
在 Java 應用程序中,異常處理機制爲:拋出異常,捕捉異常。throw/throws就是一個甩手掌櫃,它只會把異常一層層地往上拋,直到有人去處理它。而try…catch就是那個勞苦工人,負責獲取相應的異常並對它進行處理。
1.11.3 throw和throws的區別
- throws
- 用在方法聲明後面,跟的是異常類名
- 可以跟多個異常類名,用逗號隔開
- 表示拋出異常,由該方法的調用者來處理
- throws表示出現異常的一種可能性,並不一定會發生這些異常
- throw
- 用在方法體內,跟的是異常對象名
- 只能拋出一個異常對象名
- 表示拋出異常,由方法體內的語句處理
- throw則是拋出了異常,執行throw則一定拋出了某種異常
1.11.4 try catch finally
finally總會被執行;常見的問題return,在try和catch代碼塊中有return時,在執行return之前,會先執行finally代碼塊,然後在return;finally中改變變量的值,不會影響try和catch的值;
1.12 JAVA反射和動態代理
- 反射
反射機制是 Java 語言提供的一種基礎功能,賦予程序在運行時自省(introspect,官方用語)的能力。通過反射我們可以直接操作類或者對象,比如獲取某個對象的類定義,獲取類聲明的屬性和方法,調用方法或者構造對象,甚至可以運行時修改類定義。
- 動態代理
JDK Proxy 是通過實現 InvocationHandler 接口來實現的,代碼如下:
interface Animal {
void eat();
}
class Dog implements Animal {
@Override
public void eat() {
System.out.println("The dog is eating");
}
}
class Cat implements Animal {
@Override
public void eat() {
System.out.println("The cat is eating");
}
}
// JDK 代理類
class AnimalProxy implements InvocationHandler {
private Object target; // 代理對象
public Object getInstance(Object target) {
this.target = target;
// 取得代理對象
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("調用前");
Object result = method.invoke(target, args); // 方法調用
System.out.println("調用後");
return result;
}
}
public static void main(String[] args) {
// JDK 動態代理調用
AnimalProxy proxy = new AnimalProxy();
Animal dogProxy = (Animal) proxy.getInstance(new Dog());
dogProxy.eat();
}
1.13 JAVA註解
概念
定義:註解(Annotation),也叫元數據。一種代碼級別的說明。它是JDK1.5及以後版本引入的一個特性,與類、接口、枚舉是在同一個層次。它可以聲明在包、類、字段、方法、局部變量、方法參數等的前面,用來對這些元素進行說明,註釋。
作用
- 編寫文檔:通過代碼裏標識的元數據生成文檔【生成文檔doc文檔】
- 代碼分析:通過代碼裏標識的元數據對代碼進行分析【使用反射】
- 編譯檢查:通過代碼裏標識的元數據讓編譯器能夠實現基本的編譯檢查【Override】
1.14 JAVA泛型
- 泛型方法
- 泛型接口
- 泛型類
- 泛型通配符
- 泛型上下界
- 類型擦除
1.15 JAVA序列化和反序列化
把對象轉換爲字節序列的過程稱爲對象的序列化。把字節序列恢復爲對象的過程稱爲對象的反序列化。
對象的序列化主要有兩種用途:
- 把對象的字節序列永久地保存到硬盤上,通常存放在一個文件中;(web服務器session對象)
- 在網絡上傳送對象的字節序列(遠程通信)
1.16 淺拷貝和深拷貝
- 淺拷貝:創建一個新對象,然後將當前對象的非靜態字段複製到該新對象,如果字段是值類型的,那麼對該字段執行復制;如果該字段是引用類型的話,則複製引用但不復制引用的對象。因此,原始對象及其副本引用同一個對象。注意:調用對象的 clone 方法,必須要讓類實現 Cloneable 接口,並且覆寫 clone 方法。
- 深拷貝:創建一個新對象,然後將當前對象的非靜態字段複製到該新對象,無論該字段是值類型的還是引用類型,都複製獨立的一份。當你修改其中一個對象的任何內容時,都不會影響另一個對象的內容。可以利用序列化實現深拷貝。
2、JAVA集合
2.1 總體框架
- Java集合是java提供的工具包,包含了常用的數據結構:集合、鏈表、隊列、棧、數組、映射等。 Java集合工具包位置是java.util.* 。
- Java集合主要可以劃分爲4個部分:List列表、Set集合、Map映射、工具類(Iterator迭代器、Enumeration枚舉類、Arrays和Collections)。
- Java集合工具包框架圖(如下):
2.2 總體介紹
- Collection是一個接口,是高度抽象出來的集合,它包含了集合的基本操作和屬性。Collection包含了List和Set兩大分支。
-
List是一個有序的隊列,每一個元素都有它的索引。第一個元素的索引值是0。List的實現類有LinkedList, ArrayList, Vector, Stack。
-
Set是一個不允許有重複元素的集合。Set的實現類有HastSet和TreeSet。HashSet依賴於HashMap,它實際上是通過HashMap實現的;TreeSet依賴於TreeMap,它實際上是通過TreeMap實現的。
-
Map是一個映射接口,即key-value鍵值對。Map中的每一個元素包含一個key和key對應的value,每一個key是唯一的。
- AbstractMap是個抽象類,它實現了Map接口中的大部分API。而HashMap,TreeMap,WeakHashMap都是繼承於AbstractMap。
- Hashtable雖然繼承於Dictionary,但它實現了Map接口。
-
接下來,再看Iterator。它是遍歷集合的工具,即我們通常通過Iterator迭代器來遍歷集合。我們說Collection依賴於Iterator,是因爲Collection的實現類都要實現iterator()函數,返回一個Iterator對象。其中,ListIterator是專門爲遍歷List而存在的。
-
再看Enumeration,它是JDK 1.0引入的抽象類。作用和Iterator一樣,也是遍歷集合;但是Enumeration的功能要比Iterator少。在上面的框圖中,Enumeration只能在Hashtable, Vector, Stack中使用。
-
最後,看Arrays和Collections。它們是操作數組、集合的兩個工具類。
2.3 List
Java的List是非常常用的數據類型。List是有序的Collection。Java List一共三個實現類:分別是ArrayList、Vector和LinkedList。
2.3.1 ArrayList(數組)
ArrayList是最常用的List實現類,內部是通過數組實現的,它允許對元素進行快速隨機訪問。數組的缺點是每個元素之間不能有間隔,當數組大小不滿足時需要增加存儲能力,就要將已經有數組的數據複製到新的存儲空間中。當從ArrayList的中間位置插入或者刪除元素時,需要對數組進行復制、移動、代價比較高。因此,它適合隨機查找和遍歷,不適合插入和刪除。
Arraylist是如何實現動態擴容的
2.3.2 Vector(數組實現、線程同步)
Vector與ArrayList一樣,也是通過數組實現的,不同的是它支持線程的同步,即某一時刻只有一個線程能夠寫Vector,避免多線程同時寫而引起的不一致性,但實現同步需要很高的花費,因此,訪問它比訪問ArrayList慢。
2.3.3 LinkList(鏈表)
LinkedList是用鏈表結構存儲數據的,很適合數據的動態插入和刪除,隨機訪問和遍歷速度比較慢。另外,他還提供了List接口中沒有定義的方法,專門用於操作表頭和表尾元素,可以當作堆棧、隊列和雙向隊列使用。
2.4 Set
Set注重獨一無二的性質,該體系集合用於存儲無序(存入和取出的順序不一定相同)元素,值不能重複。對象的相等性本質是對象hashCode值(java是依據對象的內存地址計算出的此序號)判斷的,如果想要讓兩個不同的對象視爲相等的,就必須覆蓋Object的hashCode方法和equals方法。要計算set集合中的元素對象是否相等,就必須重寫Object的hashcode方法和equals方法。插入元素對象時先比較hashcode方法,在比較equals方法.
說明:
1. equals()相等的兩個對象他們的hashCode()肯定相等,也就是用equals()對比是絕對可靠的。
2. hashCode()相等的兩個對象他們的equals()不一定相等,也就是hashCode()不是絕對可靠的。
3. 重寫了equals方法,必須重寫hashcode方法
4. set和map接口存儲的的對象一般需要重寫euqals方法和hashcode方法
2.4.1 HashSet(Hash表)
哈希表邊存放的是哈希值。HashSet存儲元素的順序並不是按照存入時的順序(和List顯然不同) 而是按照哈希值來存的所以取數據也是按照哈希值取得。元素的哈希值是通過元素的hashcode方法來獲取的, HashSet首先判斷兩個元素的哈希值,如果哈希值一樣,接着會比較equals方法 如果 equls結果爲true ,HashSet就視爲同一個元素。如果equals 爲false就不是同一個元素。
哈希值相同equals爲false的元素是怎麼存儲呢,就是在同樣的哈希值下順延(可以認爲哈希值相同的元素放在一個哈希桶中)。也就是哈希一樣的存一列。
2.4.2 TreeSet(二叉樹)
- TreeSet()是使用二叉樹的原理對新add()的對象按照指定的順序排序(升序、降序),每增加一個對象都會進行排序,將對象插入的二叉樹指定的位置。
- Integer和String對象都可以進行默認的TreeSet排序,而自定義類的對象是不可以的,自己定義的類必須實現Comparable接口,並且覆寫相應的compareTo()函數,纔可以正常使用。
- 在覆寫compare()函數時,要返回相應的值才能使TreeSet按照一定的規則來排序
- 比較此對象與指定對象的順序。如果該對象小於、等於或大於指定對象,則分別返回負整數、零或正整數。
2.4.3 LinkHashSet(HashSet+LinkedHashMap)
對於LinkedHashSet而言,它繼承與HashSet、又基於LinkedHashMap來實現的。LinkedHashSet底層使用LinkedHashMap來保存所有元素,它繼承與HashSet,其所有的方法操作上又與HashSet相同,因此LinkedHashSet 的實現上非常簡單,只提供了四個構造方法,並通過傳遞一個標識參數,調用父類的構造器,底層構造一個LinkedHashMap來實現,在相關操作上與父類HashSet的操作相同,直接調用父類HashSet的方法即可。
2.5 Map
2.5.1. HashMap(數組+鏈表+紅黑樹)
HashMap根據鍵的hashCode值存儲數據,大多數情況下可以直接定位到它的值,因而具有很快的訪問速度,但遍歷順序卻是不確定的。 HashMap最多隻允許一條記錄的鍵爲null,允許多條記錄的值爲null。HashMap非線程安全,即任一時刻可以有多個線程同時寫HashMap,可能會導致數據的不一致。如果需要滿足線程安全,可以用 Collections的synchronizedMap方法使HashMap具有線程安全的能力,或者使用ConcurrentHashMap。
2.5.2 HashTable(線程安全)
Hashtable是遺留類,很多映射的常用功能與HashMap類似,不同的是它承自Dictionary類,並且是線程安全的,任一時間只有一個線程能寫Hashtable,併發性不如ConcurrentHashMap,因爲ConcurrentHashMap引入了分段鎖。Hashtable不建議在新代碼中使用,不需要線程安全的場合可以用HashMap替換,需要線程安全的場合可以用ConcurrentHashMap替換。
2.5.3 TreeMap(可排序)
TreeMap實現SortedMap接口,能夠把它保存的記錄根據鍵排序,默認是按鍵值的升序排序,也可以指定排序的比較器,當用Iterator遍歷TreeMap時,得到的記錄是排過序的。 如果使用排序的映射,建議使用TreeMap。 在使用TreeMap時,key必須實現Comparable接口或者在構造TreeMap傳入自定義的Comparator,否則會在運行時拋出java.lang.ClassCastException類型的異常。
2.5.4 LinkHashMap(記錄插入順序)
LinkedHashMap是HashMap的一個子類,保存了記錄的插入順序,在用Iterator遍歷LinkedHashMap時,先得到的記錄肯定是先插入的,也可以在構造時帶參數,按照訪問次序排序。
2.5.5 ConcurrentHashMap
ConcurrentHashMap 和 HashMap 思路是差不多的,但是因爲它支持併發操作,所以要複雜一些。整個 ConcurrentHashMap 由一個個 Segment 組成,Segment 代表”部分“或”一段“的意思,所以很多地方都會將其描述爲分段鎖。注意,行文中,我很多地方用了“槽”來代表一個 segment。簡單理解就是,ConcurrentHashMap 是一個 Segment 數組,Segment 通過繼承 ReentrantLock 來進行加鎖,所以每次需要加鎖的操作鎖住的是一個 segment,這樣只要保證每個 Segment 是線程安全的,也就實現了全局的線程安全。
2.6 集合遍歷方法總結
2.6.1 Java中提供的遍歷方式
- 傳統的for循環遍歷,基於計數器的:
遍歷者自己在集合外部維護一個計數器,然後依次讀取每一個位置的元素,當讀取到最後一個元素後,停止。主要就是需要按元素的位置來讀取元素。這也是最原始的集合遍歷方法。寫法爲:
for (int i = 0; i < list.size(); i++) {
list.get(i);
}
- 迭代器遍歷,Iterator:
Iterator本來是OO的一個設計模式,主要目的就是屏蔽不同數據集合的特點,統一遍歷集合的接口。Java作爲一個OO語言,自然也在Collections中支持了Iterator模式。寫法爲:
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
iterator.next();
}
- foreach循環遍歷:
屏蔽了顯式聲明的Iterator和計數器。優點:代碼簡潔,不易出錯。缺點:只能做簡單的遍歷,不能在遍歷過程中操作(刪除、替換)數據集合。寫法爲:
for (ElementType element : list) {
}
2.6.2 每個遍歷方法的實現原理是什麼?
- 傳統的for循環遍歷,基於計數器的:
遍歷者自己在集合外部維護一個計數器,然後依次讀取每一個位置的元素,當讀取到最後一個元素後,停止。主要就是需要按元素的位置來讀取元素。
- 迭代器遍歷,Iterator:
每一個具體實現的數據集合,一般都需要提供相應的Iterator。相比於傳統for循環,Iterator取締了顯式的遍歷計數器。所以基於順序存儲集合的Iterator可以直接按位置訪問數據。而基於鏈式存儲集合的Iterator,正常的實現,都是需要保存當前遍歷的位置。然後根據當前位置來向前或者向後移動指針。
- foreach循環遍歷:
根據反編譯的字節碼可以發現,foreach內部也是採用了Iterator的方式實現,只不過Java編譯器幫我們生成了這些代碼。
2.6.3 各遍歷方式的適用於什麼場合?
1、傳統的for循環遍歷,基於計數器的:
順序存儲:讀取性能比較高。適用於遍歷順序存儲集合。
鏈式存儲:時間複雜度太大,不適用於遍歷鏈式存儲的集合。
2、迭代器遍歷,Iterator:
順序存儲:如果不是太在意時間,推薦選擇此方式,畢竟代碼更加簡潔,也防止了Off-By-One的問題。
鏈式存儲:意義就重大了,平均時間複雜度降爲O(n),還是挺誘人的,所以推薦此種遍歷方式。
3、foreach循環遍歷:
foreach只是讓代碼更加簡潔了,但是他有一些缺點,就是遍歷過程中不能操作數據集合(刪除等),所以有些場合不使用。而且它本身就是基於Iterator實現的,但是由於類型轉換的問題,所以會比直接使用Iterator慢一點,但是還好,時間複雜度都是一樣的。所以怎麼選擇,參考上面兩種方式,做一個折中的選擇。
2.7 各個集合的使用
2.7.1 ArrayList
import java.lang.String;
import java.util.ArrayList;
import java.util.Iterator;
public class HelloWorld {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
list.add("Hello");
list.add("world");
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
2.7.2 Vector
import java.lang.String;
import java.util.Vector;
import java.util.Iterator;
import java.util.List;
public class HelloWorld {
public static void main(String[] args) {
List<String> list = new Vector<String>();
list.add("Hello");
list.add("world");
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
2.7.3 LinkedList
import java.lang.String;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.List;
public class HelloWorld {
public static void main(String[] args) {
List<String> list = new LinkedList<String>();
list.add("Hello");
list.add("world");
Iterator<String> it = list.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
2.7.4 HashSet
import java.lang.String;
import java.util.Iterator;
import java.util.Set;
import java.util.HashSet;
public class HelloWorld {
public static void main(String[] args) {
Set<String> st = new HashSet<String>();
st.add("Hello");
st.add("world");
Iterator<String> it = st.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
2.7.5 TreeSet
import java.lang.String;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class HelloWorld {
public static void main(String[] args) {
Set<Integer> st = new TreeSet<Integer>();
st.add(100);
st.add(600);
st.add(400);
st.add(200);
st.add(300);
Iterator<Integer> it = st.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
2.7.6 LinkedHashSet
import java.lang.String;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
public class HelloWorld {
public static void main(String[] args) {
Set<Integer> st = new LinkedHashSet<Integer>();
st.add(100);
st.add(600);
st.add(400);
st.add(200);
st.add(300);
Iterator<Integer> it = st.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
}
}
2.7.7 HashMap
import java.lang.String;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class HelloWorld {
public static void main(String[] args) {
Map<String,String> mmp = new HashMap<String,String>();
mmp.put("b", "hello");
if(!mmp.containsKey("a")){
mmp.put("a", "world");
}
mmp.put("b", null); //會覆蓋之前的值
Iterator<String> it = mmp.keySet().iterator();
while(it.hasNext()){
String key = it.next();
System.out.println(mmp.get(key));
}
}
}
2.7.8 HashTable
import java.lang.String;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
public class HelloWorld {
public static void main(String[] args) {
Map<String,String> mmp = new Hashtable<String,String>();
mmp.put("b", "hello");
mmp.put("a", "world");
Iterator<Map.Entry<String,String>> it = mmp.entrySet().iterator();
while(it.hasNext()){
Map.Entry<String,String> entry = it.next();
System.out.println(entry.getValue());
}
}
}
2.7.9 TreeMap
import java.lang.String;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
public class HelloWorld {
public static void main(String[] args) {
Map<Integer,String> mmp = new TreeMap<Integer,String>();
mmp.put(new Integer(10), "hello");
mmp.put(new Integer(2), "world");
mmp.put(new Integer(5), "world");
mmp.put(new Integer(9), "world");
mmp.put(new Integer(3), "world");
for(Entry<Integer, String> entry:mmp.entrySet()){
System.out.println(entry.getKey());
}
}
}
2.7.10 ConcurrentHashMap
addThread類
public class addThread extends Thread{
private Map<Integer,String> mmp;
addThread(Map<Integer,String> mmp){
this.mmp = mmp;
}
public void run(){
while(true){
Random random = new Random();
mmp.put(random.nextInt(), "Hello world");
}
}
}
removeThread類
public class testThread extends Thread {
private Map<Integer,String> mmp;
testThread(Map<Integer,String> mmp){
this.mmp = mmp;
}
public void run(){
try {
sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
Iterator<Map.Entry<Integer, String>> it = mmp.entrySet().iterator();
while(it.hasNext()){
Map.Entry<Integer, String> entry = it.next();
System.out.println("remove key:"+entry.getKey());
mmp.remove(entry.getKey());
try {
sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
Main方法
public class HelloWorld {
public static void main(String[] args) {
Map<Integer,String> mmp = new ConcurrentHashMap<Integer,String>();
Thread threadone = new addThread(mmp);
Thread threadtwo = new testThread(mmp);
threadone.start();
threadtwo.start();
}
}
3、JAVA併發
3.1 concurrent包下的工具類
3.2 創建線程的三種方式
- 繼承Thread類創建線程類
- 通過Runnable接口創建線程類
- 通過Callable和Future創建線程
public class HelloWorld {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//thread類方式創建
new Thread(){
public void run(){
System.out.println("Thread方式創建");
}
}.start();
//Runnable接口
new Thread(new Runnable(){
public void run(){
System.out.println("runnable接口");
}
}).start();
//Callable接口和Future創建
FutureTask<Integer> ft = new FutureTask<Integer>(new Callable<Integer>(){
public Integer call(){
System.out.println("callable接口");
return 1994;
}
});
new Thread(ft).start();
System.out.println("線程的返回值:"+ft.get());
}
}
3.3 線程池的四種使用方法
Java裏面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。下面這張圖完整描述了線程池的類體系結構。
- newSingleThreadExecutor:創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。
- newFixedThreadPool:創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。
- newCachedThreadPool:創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
- newScheduledThreadPool:創建一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。
public class HelloWorld {
public static void main(String[] args) throws InterruptedException,ExecutionException {
ExecutorService pool = Executors.newCachedThreadPool();//創建線程池
FutureTask<Integer> ft = new FutureTask<Integer>(new Callable<Integer>(){
public Integer call(){
System.out.println("callable接口");
return 1994;
}
});
Thread thread = new Thread(ft);//執行線程
pool.execute(thread);
pool.execute(new Thread(){ //執行匿名內部類的線程
public void run(){
System.out.println("Thread方式創建");
}
});
pool.execute(new Thread(new Runnable(){//執行匿名內部類的線程
public void run(){
System.out.println("runnable接口");
}
}));
System.out.println("線程的返回值:"+ft.get());
}
}
多線程Socket通信
3.4 線程的生命週期
當線程被創建並啓動以後,它既不是一啓動就進入了執行狀態,也不是一直處於執行狀態。在線程的生命週期中,它要經過新建(New)、就緒(Runnable)、運行(Running)、阻塞(Blocked)和死亡(Dead)5種狀態。尤其是當線程啓動以後,它不可能一直"霸佔"着CPU獨自運行,所以CPU需要在多條線程之間切換,於是線程狀態也會多次在運行、阻塞之間切換。
3.4.1 新建狀態(NEW)
當程序使用new關鍵字創建了一個線程之後,該線程就處於新建狀態,此時僅由JVM爲其分配內存,並初始化其成員變量的值
3.4.2 就緒狀態(RUNNABLE)
當線程對象調用了start()方法之後,該線程處於就緒狀態。Java虛擬機會爲其創建方法調用棧和程序計數器,等待調度運行。
3.4.3運行狀態(RUNNING)
如果處於就緒狀態的線程獲得了CPU,開始執行run()方法的線程執行體,則該線程處於運行狀態。
3.4.4 阻塞狀態(BLOCKED)
阻塞狀態是指線程因爲某種原因放棄了cpu 使用權,也即讓出了cpu timeslice,暫時停止運行。直到線程進入可運行(runnable)狀態,纔有機會再次獲得cpu timeslice 轉到運行(running)狀態。阻塞的情況分三種:
- 等待阻塞(o.wait->等待對列): 運行(running)的線程執行o.wait()方法,JVM會把該線程放入等待隊列(waitting queue)中。
- 同步阻塞(lock->鎖池) 運行(running)的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(lock pool)中。
- 其他阻塞(sleep/join) 運行(running)的線程執行Thread.sleep(long ms)或t.join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入可運行(runnable)狀態。
3.4.5 線程死亡(DEAD)
線程會以下面三種方式結束,結束後就是死亡狀態。 正常結束
- run()或call()方法執行完成,線程正常結束。 異常結束
- 線程拋出一個未捕獲的Exception或Error。 調用stop
- 直接調用該線程的stop()方法來結束該線程—該方法通常容易導致死鎖,不推薦使用。
3.5 終止線程4種方式
- 正常運行結束
- 使用退出標誌退出線程
- Interrupt方法結束線程
- stop方法終止線程(線程不安全)
3.6 JAVA鎖
- 樂觀鎖
- 悲觀鎖
- 自旋鎖
- Synchronized同步鎖
- ReentrantLock
- AtomicInteger
- 可重入鎖(遞歸鎖)
- ReadWriteLock讀寫鎖
- 共享鎖和獨佔鎖
- 重量級鎖(Mutex Lock)
- 輕量級鎖
- 偏向鎖
- 分段鎖
- 公平鎖與非公平鎖
3.6.1 synchronized關鍵字
- Java中每個對象都有一個鎖或者稱爲監視器,當訪問某個對象的synchronized方法時,表示將該對象上鎖,而不僅僅是爲該方法上鎖。
- 這樣如果一個對象的synchronized方法被某個線程執行時,其他線程無法訪問該對象的任何synchronized方法(但是可以調用其他非synchronized的方法)。直至該synchronized方法執行完。
- 當調用一個對象的靜態synchronized方法時,它鎖定的並不是synchronized方法所在的對象,而是synchronized方法所在對象對應的Class對象。這樣,其他線程就不能調用該類的其他靜態synchronized方法了,但是可以調用非靜態的synchronized方法。
總結:對象鎖和對象鎖互斥,類鎖和類鎖互斥,類鎖和對象鎖互相獨立。對象鎖是指加了synchornized修飾的非靜態方法和synchornized(this)的代碼塊;類鎖是加了synchornized修飾的靜態方法。沒有加synchornized修飾的就是沒有進行同步的代碼塊,任何線程可以訪問。
synchornized鎖的特性:
- Synchronized是非公平鎖
- synchronized是一個重量級操作,需要調用操作系統相關接口,性能是低效的,有可能給線程加鎖消耗的時間比有用操作消耗的時間更多
- 每個對象都有個monitor對象,加鎖就是在競爭monitor對象
ReentrantLock
public class testSynchornized implements Runnable {
private Lock lock = new ReentrantLock();
private int count = 10;
public void run(){
System.out.println("ThreadName=" + Thread.currentThread().getName());
try {
lock.lock();
for(int i =0;i<3;i++){
count--;
System.out.println(count);
Thread.sleep(500);
}
} catch (Exception e) {
// TODO: handle exception
}finally{
lock.unlock();
}
}
}
synchornized和ReentrantLock區別
- ReentrantLock顯示的獲得、釋放鎖,synchronized隱式獲得釋放鎖
- ReentrantLock可響應中斷、可輪迴,synchronized是不可以響應中斷的,爲處理鎖的不可用性提供了更高的靈活性
- ReentrantLock是API級別的,synchronized是JVM級別的
- ReentrantLock可以實現公平鎖
- ReentrantLock通過Condition可以綁定多個條件
- 底層實現不一樣, synchronized是同步阻塞,使用的是悲觀併發策略,lock是同步非阻塞,採用的是樂觀併發策略
- Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現。
- synchronized在發生異常時,會自動釋放線程佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖。
- Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷。
- 通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
- Lock可以提高多個線程進行讀操作的效率,既就是實現讀寫鎖等。
Condition類和Object類鎖方法區別區別
- Condition類的awiat方法和Object類的wait方法等效
- Condition類的signal方法和Object類的notify方法等效
- Condition類的signalAll方法和Object類的notifyAll方法等效
- ReentrantLock類可以喚醒指定條件的線程,而object的喚醒是隨機的
Semaphore信號量
Semaphore是一種基於計數的信號量。它可以設定一個閾值,基於此,多個線程競爭獲取許可信號,做完自己的申請後歸還,超過閾值後,線程申請許可信號將會被阻塞。Semaphore可以用來構建一些對象池,資源池之類的,比如數據庫連接池
實現互斥鎖(計數器爲1)
我們也可以創建計數爲1的Semaphore,將其作爲一種類似互斥鎖的機制,這也叫二元信號量,表示兩種互斥狀態。
// 創建一個計數閾值爲5的信號量對象
// 只能5個線程同時訪問
Semaphore semp = new Semaphore(5);
try {
// 申請許可
semp.acquire();
try {
// 業務邏輯
} catch (Exception e) {
} finally {
// 釋放許可
semp.release();
}
} catch (InterruptedException e){
}
AtomicInteger
首先說明,此處AtomicInteger,一個提供原子操作的Integer的類,常見的還有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference等,他們的實現原理相同,區別在與運算對象類型的不同。令人興奮地,還可以通過AtomicReference將一個對象的所有操作轉化成原子操作。
可重入鎖
線程可以進入任何一個它已經擁有的鎖所同步着的代碼塊。ReentrantLock和synchronized都是可重入鎖
ReadWriteLock讀寫鎖
- 讀鎖 如果你的代碼只讀數據,可以很多人同時讀,但不能同時寫,那就上讀鎖
- 寫鎖 如果你的代碼修改數據,只能有一個人在寫,且不能同時讀取,那就上寫鎖。總之,讀的時候上讀鎖,寫的時候上寫鎖!
- Java中讀寫鎖有個接口java.util.concurrent.locks.ReadWriteLock,也有具體的實現ReentrantReadWriteLock。
鎖的狀態
鎖的狀態總共有四種:無鎖狀態、偏向鎖、輕量級鎖和重量級鎖。
- 鎖升級
隨着鎖的競爭,鎖可以從偏向鎖升級到輕量級鎖,再升級的重量級鎖(但是鎖的升級是單向的,也就是說只能從低到高升級,不會出現鎖的降級)。
- 輕量級鎖
“輕量級”是相對於使用操作系統互斥量來實現的傳統鎖而言的。但是,首先需要強調一點的是,輕量級鎖並不是用來代替重量級鎖的,它的本意是在沒有多線程競爭的前提下,減少傳統的重量級鎖使用產生的性能消耗。在解釋輕量級鎖的執行過程之前,先明白一點,輕量級鎖所適應的場景是線程交替執行同步塊的情況,如果存在同一時間訪問同一鎖的情況,就會導致輕量級鎖膨脹爲重量級鎖。
- 偏向鎖
Hotspot的作者經過以往的研究發現大多數情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得。偏向鎖的目的是在某個線程獲得鎖之後,消除這個線程鎖重入(CAS)的開銷,看起來讓這個線程得到了偏護。引入偏向鎖是爲了在無多線程競爭的情況下儘量減少不必要的輕量級鎖執行路徑,因爲輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令(由於一旦出現多線程競爭的情況就必須撤銷偏向鎖,所以偏向鎖的撤銷操作的性能損耗必須小於節省下來的CAS原子指令的性能消耗)。上面說過,輕量級鎖是爲了在線程交替執行同步塊時提高性能,而偏向鎖則是在只有一個線程執行同步塊時進一步提高性能。
- 重量級鎖(Mutex Lock)
Synchronized是通過對象內部的一個叫做監視器鎖(monitor)來實現的。但是監視器鎖本質又是依賴於底層的操作系統的Mutex Lock來實現的。而操作系統實現線程之間的切換這就需要從用戶態轉換到核心態,這個成本非常高,狀態之間的轉換需要相對比較長的時間,這就是爲什麼Synchronized效率低的原因。因此,這種依賴於操作系統Mutex Lock所實現的鎖我們稱之爲“重量級鎖”。JDK中對Synchronized做的種種優化,其核心都是爲了減少這種重量級鎖的使用。JDK1.6以後,爲了減少獲得鎖和釋放鎖所帶來的性能消耗,提高性能,引入了“輕量級鎖”和“偏向鎖”。
鎖優化
- 減少鎖持有時間
- 減小鎖粒度
- 鎖分離
- 鎖粗化
- 鎖消除
線程常用方法
- sleep():強迫一個線程睡眠N毫秒。
- isAlive(): 判斷一個線程是否存活。
- join(): 等待線程終止。
- activeCount(): 程序中活躍的線程數。
- enumerate(): 枚舉程序中的線程。
- currentThread(): 得到當前線程。
- isDaemon(): 一個線程是否爲守護線程。
- setDaemon(): 設置一個線程爲守護線程。(用戶線程和守護線程的區別在於,是否等待主線程依賴於主線程結束而結束)
- setName(): 爲線程設置一個名稱。
- wait(): 強迫一個線程等待。
- notify(): 通知一個線程繼續運行。
- setPriority(): 設置一個線程的優先級。
- getPriority()::獲得一個線程的優先級。
volatile關鍵字的作用
- 變量可見性
- 禁止重排序
ThreadLocal
ThreadLocal,很多地方叫做線程本地變量,也有些地方叫做線程本地存儲,ThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命週期內起作用,減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的複雜度。
CAS
CAS(Compare-and-Swap),即比較並替換,是一種實現併發算法時常用到的技術,Java併發包中的很多類都使用了CAS技術。但是,CAS存在ABA問題,解決方案是:可以對每個值添加一個版本號來判斷,CAS只是一種思想
StringBuffer\StringBuilder\String
- 執行速度:StringBuilder>StringBuffer>String
- StringBuffer是線程安全的、StringBuilder是非線程安全的
對於三者的總結:
- 如果操作少量的數據用String
- 單線程下操作大量的數據用StringBuilder
- 多線程下操作大量的數據用StringBuffer
HashMap\HashTable\concurrentHashMap
- 線程安全
HashMap是線程不安全的;HashTable是線程安全的concurrentHashMap是線程安全的
- 結構
concurrentHashMap底層採用分段的數組+鏈表(紅黑樹)實現,線程安全;HashTable和HashMap底層採用數組+鏈表(紅黑樹)實現
- 異同
HashMap可以接受爲null的鍵值(key)和值(value),而Hashtable則不行
ConcurrentHashMap\HashTable\synchronized Map
三者均可以實現線程安全的HashMap,實現方法不同
線程安全的集合類
Vector\Stack\Hashtable\ConcurrentHashMap\enumeration
4、JVM
4.1 JVM內存結構(運行時數據區)
JVM內存結構::堆、虛擬機棧、方法區、程序計數器和本地方法棧
-
堆: 對於大多數應用來說,Java堆(Java Heap)是Java虛擬機所管理的內存中最大的一塊。Java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建。此內存區域的唯一目的就是存放對象實例,幾乎所有的對象實例都在這裏分配內存。
-
虛擬機棧:Java虛擬機棧(Java Virtual Machine Stacks)是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀(Stack Frame)用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息。每一個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。 局部變量表存放了編譯期可知的各種基本數據類型對象(boolean、byte、char、short、int、float、long、double)、對象引用。
-
方法區:方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然Java虛擬機規範把方法區描述爲堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。
-
程序計數器:此內存區域是唯一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError情況的區域。
-
本地方法棧:本地方法棧則是爲虛擬機使用到的Native方法服務
線程共享的內存區域:堆和方法區;
線程私有的內存區域:棧和程序計數器;
4.2 Java內存模型(JMM)
4.2.1 主內存與工作內存
Java內存模型中規定了所有的變量都存儲在主內存中,每條線程還有自己的工作內存(可以與前面將的處理器的高速緩存類比),線程的工作內存中保存了該線程使用到的變量到主內存副本拷貝,線程對變量的所有操作(讀取、賦值)都必須在工作內存中進行,而不能直接讀寫主內存中的變量。
4.2.2 內存間交互操作
4.2.3 重排序
4.2.4 同步機制
4.2.5 原子性、可見性與有序性
4.3 Java中的NIO,BIO,AIO分別是什麼
- BIO
- 同步並阻塞,服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務器端就需要啓動一個線程進
行處理,如果這個連接不做任何事情會造成不必要的線程開銷,當然可以通過線程池機制改善。 - BIO方式適用於連接數目比較小且固定的架構,這種方式對服務器資源要求比較高,併發侷限於應用中,
JDK1.4以前的唯一選擇,但程序直觀簡單易理解。
- 同步並阻塞,服務器實現模式爲一個連接一個線程,即客戶端有連接請求時服務器端就需要啓動一個線程進
- NIO
- 同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的連接請求都會註冊到多路複用器上,多
路複用器輪詢到連接有I/O請求時才啓動一個線程進行處理。 - NIO方式適用於連接數目多且連接比較短(輕操作)的架構,比如聊天服務器,併發侷限於應用中,編程比較複雜,JDK1.4開始支持。
- 同步非阻塞,服務器實現模式爲一個請求一個線程,即客戶端發送的連接請求都會註冊到多路複用器上,多
- AIO
- 異步非阻塞,服務器實現模式爲一個有效請求一個線程,客戶端的I/O請求都是由OS先完成了再通知服務器應用去啓動線程進行處理
- AIO方式使用於連接數目多且連接比較長(重操作)的架構,比如相冊服務器,充分調用OS參與併發操作,編程比較複雜,JDK7開始支持
4.4 JVM如何GC,新生代,老年代,持久代,都存儲哪些東西
- 新生代
- 在方法中去new一個對象,那這方法調用完畢後,對象就會被回收,這就是一個典型的新生代對象。
- 老年代
- 在新生代中經歷了N次垃圾回收後仍然存活的對象就會被放到老年代中。而且大對象直接進入老年代
- 當Survivor空間不夠用時,需要依賴於老年代進行分配擔保,所以大對象直接進入老年代
- 永久代(1.8已去除)
- 即方法區
4.5 JVM垃圾處理方法(標記清除、複製、標記整理)
- 標記-清除算法
- 標記階段:先通過根節點,標記所有從根節點開始的對象,未被標記的爲垃圾對象
- 清除階段:清除所有未被標記的對象
- 複製算法
- 將原有的內存空間分爲兩塊,每次只使用其中一塊,在垃圾回收時,將正在使用的內存中的存活對象複製到未使用的內存塊中,然後清除正在使用的內存塊中的所有對象。
- 標記-整理
- 標記階段:先通過根節點,標記所有從根節點開始的可達對象,爲被標記的爲垃圾對象
- 整理階段:將所有的存活對象壓縮到內存的一段,之後清理邊界所有的空間
4.6 各個垃圾收集器是怎麼工作的
- Serial收集器
- 是一個單線程的收集器,不是隻能使用一個CPU。在進行垃圾收集時,必須暫停其他所有的工作線程,直到集結束。
- 新生代採用複製算法,Stop-The-World
- 老年代採用標記-整理算法,Stop-The-World
- 簡單高效,Client模式下默認的新生代收集器
- ParNew收集器
- ParNew收集器是Serial收集器的多線程版本
- 新生代採用複製算法,Stop-The-World
- 老年代採用標記-整理算法,Stop-The-World
- 它是運行在Server模式下首選新生代收集器
- 除了Serial收集器之外,只有它能和CMS收集器配合工作
- ParNew Scanvenge收集器
- 類似ParNew,但更加關注吞吐量。目標是:達到一個可控制吞吐量的收集器。
- 停頓時間和吞吐量不可能同時調優。我們一方面希望停頓時間少,另外一方面希望吞吐量高,其實這是矛盾的。因爲:在GC的時候,垃圾回收的工作總量是不變的,如果將停頓時間減少,那頻率就會提高;既然頻率提高了,說明就會頻繁的進行GC,那吞吐量就會減少,性能就會降低。
- G1收集器
- 是當今收集器發展的最前言成果之一,對垃圾回收進行了劃分優先級的操作,這種有優先級的區域回收方式保證了它的高效率
- 最大的優點是結合了空間整合,不會產生大量的碎片,也降低了進行gc的頻率
- 讓使用者明確指定指定停頓時間
- CMS收集器:(Concurrent Mark Sweep:併發標記清除老年代收集器)
- 一種以獲得最短回收停頓時間爲目標的收集器,適用於互聯網站或者B/S系統的服務器上
- 初始標記(Stop-The-World):根可以直接關聯到的對象
- 併發標記(和用戶線程一起):主要標記過程,標記全部對象
- 重新標記(Stop-The-World):由於併發標記時,用戶線程依然運行,因此在正式清理前,再做修正
- 併發清除(和用戶線程一起):基於標記結果,直接清理對象
- 併發收集,低停頓
4.7 類加載器
4.7.1 加載器
- 啓動(Bootstrap)類加載器:是用本地代碼實現的類裝入器,它負責將JAVA_HOME\lib下面
的類庫加載到內存中(比如rt.jar)。由於引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到
啓動類加載器的引用,所以不允許直接通過引用進行操作。
JAVA代碼編譯執行整個過程 - 擴展類加載器(Extension ClassLoader):負責加載 JAVA_HOME\lib\ext 目錄中的,或通過java.ext.dirs系統變量指定路徑中的類庫。
- 應用程序類加載器(Application ClassLoader):負責加載用戶路徑(classpath)上的類庫。
4.7.2 雙親委派模型
如果一個類加載器收到了類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個請求委派給父類加
載器去完成,每一個層次的加載器都是如此,因此所有的類加載請求都會傳給頂層的啓動類加載器,只有當
父加載器反饋自己無法完成該加載請求(該加載器的搜索範圍中沒有找到對應的類)時,子加載器纔會嘗試
自己去加載。
4.7.3 Java 代碼編譯和執行的整個過程包含了以下三個重要的機制
- Java 源碼編譯機制
- 類加載機制
- 類執行機制
4.8 JAVA四種引用類型
- 強引用:在Java中最常見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即使該對象以後永遠都不會被用到JVM也不會回收。因此強引用是造成Java內存泄漏的主要原因之一。
- 軟引用:軟引用需要用SoftReference類來實現,對於只有軟引用的對象來說,當系統內存足夠時它不會被回收,當系統內存空間不足時它會被回收。軟引用通常用在對內存敏感的程序中。
- 弱引用:弱引用需要用WeakReference類來實現,它比軟引用的生存期更短,對於只有弱引用的對象來說,只要垃圾回收機制一運行,不管JVM的內存空間是否足夠,總會回收該對象佔用的內存。
- 虛引用:虛引用需要PhantomReference類來實現,它不能單獨使用,必須和引用隊列聯合使用。虛引用的主要作用是跟蹤對象被垃圾回收的狀態。