《深入理解java 虛擬機*JVM 高級特性與最佳實踐讀後感》

本文引用代碼見下載頁:http://www.hzbook.com 

《第一章:java歷史》

java 是什麼:

1.java是面向對象並實現了"一次編寫,到處運行"的跨平臺一門語言;
2.它提供了一個相對安全的內存管理和訪問機制,避免了絕大部分的內存泄露和指針越界問題;
3.它實現了熱點代碼檢測和運行時編譯及優化,使得java應用能隨着運行時間的增加而獲得更高的性能;
4.java擁有一套完善的應用程序接口;

java 技術體系包括
1.java 程序設計語言
2.各種硬件平臺上的java虛擬機
3.class 文件格式
4.java api 類庫
5.來自商業機構和開源社區的第三方java類庫

JDK 和JRE的區別和聯繫

java程序設計語言,java 虛擬機,java api 類庫這三部分統稱爲JDK(java development kit)  是用於支持java 程序開發的最小環境。java api 和java 虛擬機兩部分統稱爲JRE(java runtime environment),jre 是支持java程序運行的標準環境。

java技術體系可以分爲四個平臺:


1.java card :支持一些java小程序applets運行在小內存(如智能卡)設備上的平臺。

2.java ME[J2ME]: 支持java程序運行在移動終端(手機,PDA)上的平臺,對java api 有所精簡,並加入了針對移動終端的支持。

3.javaSE[J2SE]:支持面向桌面級應用(Windows下的應用程序)的java平臺,提供了完整的java 核心API。

4.java EE[J2EE]: 支持使用多層架構的企業應用(ERP,CRM)的java平臺,除了提供javaSE api 外,還對其做了大量的擴充,並提供了相關的部署支持。

《第二部分:自動內存管理機制》

java 程序員在虛擬機自動內存管理機制的幫助下,不需要爲每個new操作去寫配對的delete/free 代碼,不容易出現內存泄露和內存溢出問題。

運行時數據區域:java虛擬機在執行java程序的過程中會把它所管理的內存劃分爲若干個不同的數據區域。這些區域都有各自的用途,以及創建和銷燬時間。有的區域隨着虛擬機進行的啓動而存在,有些區域則依賴用戶線程的啓動和結束而建立和銷燬。

1.程序計數器: 程序計數器是一塊較小的內存空間,可以看作是點錢線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏,字節碼解釋器工作時就是通過改變這個計數器的值來選取下一條需要執行的字節碼指令,分支,循環,跳轉,異常處理,線程恢復等基礎功能都需要依賴這個計數器來完成。

由於java虛擬機的多線程是通過線程輪流並分配處理器執行時間的方式來實現的,在任何一個確定的時刻,一個處理器都只會執行一條線程中的指令。所以,爲了線程切換後能夠恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,我我們稱這類內存區域爲:線程私有的內存

如果線程正在執行的是一個java方法,這個計數器記錄的是正在的虛擬機字節碼指令的地址; 如果執行的是native 本地方法,這個計數器值則爲空undefined。此內存區域是唯一一個在java虛擬機規範中沒有規定任何outOfMemoryError 情況的區域。

2.java 虛擬機棧java虛擬機棧線程私有的,它的生命週期與線程相同。虛擬機棧描述的是java 方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀用於存儲局部變量表,操作數棧,動態鏈接,方法出口燈。每一個方法從調用直至完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。

局部變量表存放了編譯器可知的各種基本數據類型:Boolean,byte,char,short,int,float,long,double ,對象引用等。

long 和double 佔用2個局部變量空間,其餘的都是佔用1個。

在java虛擬機規範中,對這個區域規定了兩種異常狀況

1.如果線程的棧深度大於虛擬機所允許的深度,將拋出StackOverFlowError 異常;

2.如果虛擬機棧可以動態擴展,如果擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError 異常。

3.本地方法棧本地方法棧與虛擬機棧作用相似,區別不過是虛擬機棧爲虛擬機執行java方法[字節碼]服務,而本地方法棧則爲虛擬機使用到的native方法服務。與虛擬機棧一樣,本地方法棧區域也會拋出StackOverFlowError 和 OutOfMemoryError 異常。

4.java堆 heap java堆是java 虛擬機管理內存中最大的一塊java堆是被所有線程共享的一塊內存區域,在虛擬機啓動時創建。此內存區域的唯一目的就是存放對象示例,幾乎所有的對象示例都在這裏分配內存。[所有的對象實例以及數組都要在堆上分配]。

  java 堆是垃圾收集器管理的主要區域,也稱GC 垃圾收集器。由於現在收集器基本都採用分代收集算法,所以java堆中可以細分爲:新生代和老年代;

新生代和老年代: Eden 空間,from survivor 空間,to survivor 空間。

根據java虛擬機規範的規定,java堆可以處於物理上不連續的內存空間中,只要邏輯上是連續的即可。

5.方法區 method area:與java heap 堆一樣,是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息,常量,靜態變量,即時編譯器編譯後的代碼等數據 。java虛擬機對方法區規定:可以不需要連續的內存和可以選擇固定大小或者可擴展外,還可以不實現垃圾收集。當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError 異常。

6.運行時常量池 runtime constant pool: 方法區的一部分。class 文件中除了有類的版本,字段,方法,接口等描述信息外,還有一項信息是常量池 constant pool table,用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類加載後進入方法區的運行時常量池中存放。 運行時常量池相對於class文件常量池的重要特性是具備動態性。因爲運行時常量池是方法區的一部分,所以自然受到方法區內存的限制,當常量池無法再申請到內存時就會拋出OutOfMemoryError 異常

7.直接內存 Direct Memory:從java1.4開始引入了NIO類,引入了一種基於通道與緩衝區Buffer 的I/O 方式它可以使用Native 函數庫直接分配堆外內存,然後通過一個存儲在java堆中的DirectByteBuffer 對象作爲這塊內存的引用進行操作。這樣能在一些場景中顯著的提高性能,因爲避免了java堆和native 堆中來回複製數據。

 
 對象的訪問定位:建立對象是爲了使用對象,我們的java程序需要通過棧上的reference 數據來操作堆上的具體對象。由於reference 類型在java 虛擬機規範中只規定了一個指向對象的引用,並沒有定義這個引用應該通過哪種方式去定位,訪問對重的對象的具體位置,所以對象訪問方式也是取決於虛擬機實現而定的。目前主流的訪問方式有:使用句柄和直接指針兩種
 
 如果使用句柄訪問的話,那麼java堆中將會劃分出一塊內存作爲句柄池,reference 中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據和類型數據各自的具體地址信息。
 
 如果使用直接指針訪問,那麼java堆對象的佈局中就必須考慮如何放置訪問類型數據的相關信息,而reference 中存儲的直接就是對象地址。
 
 總結: 兩種對象訪問方式的優勢:
 
 1.使用句柄來訪問的最大好處就是reference 中存儲的是穩定的句柄地址,在對象唄移動時只會改變句柄中的實例數據指針,而reference 本身不需要修改。
 
 2.使用直接指針訪問方式的最大好處就是速度更快,他節省了一次指針定位的時間開銷,由於對象的訪問在java 中非常頻繁,因此這類開銷積少成多後也是一項非常可觀的執行成本。

實戰:OutOfMemoryError 異常:

在JVM 中,除了程序計數器外,虛擬機內存的其他幾個運行時區域都有發生OutOfMemoryError [OOM]異常的可能。

main 方法,右鍵--debug configurations

java 堆溢出:java堆用於存儲對象實例,只要不斷的創建對象,並且保證GC roots 到對象之間有可達路徑來避免垃圾回收機制清除這些對象,那麼在對象數量到達最大堆的容量限制後就會產生內存溢出異常。

public class HeapOOM {

    static class OOMObject{}

    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while(true){
            list.add(new OOMObject());
        }
    }
}

異常堆棧信息:java.lang.OutOfMemoryError Java heap space 解決方法: 先通過內存映像工具[Eclipse Memory-Analyzer]對Dump 出來的堆轉儲快照進行分析,重點是確認內存中的對象是否是必要的,也就是要先分清楚到底是內存泄露memory leak 還是 內存溢出 memory overflow。[eclipse 查看:https://blog.csdn.net/qq_35781178/article/details/102902041]

虛擬機棧和本地方法棧溢出:

由於在HotSpot 虛擬機中並不區分虛擬機棧和本地方法棧,所以,-Xoss參數(設置本地方法棧大小)並不起作用,棧容量只能由-Xss 參數設定。關於虛擬機棧和本地方法棧只有兩種異常:
1.如果線程請求的棧深度大於虛擬機所允許的最大深度,則拋出StackOverflowError 異常。

2.如果虛擬機在擴展棧時無法申請到足夠的內存空間則拋出OutOfMemoryError 異常。


方法區和運行時常量池溢出

string.intern()方法是一個native 方法,作用:如果字符串常量池中已經包含了一個等於此string對象的字符串,則返回代表池中這個字符串的string對象;否則將此string 對象包含的字符串添加到常量池中,並且返回此string 對象的引用。

本地直接內存溢出:DirectMemory 容量通過-XX:MaxDirectMemorySize 指定,如果不指定,則默認與java 堆最大值一樣。

《第三章:垃圾收集器與內存分配策略》

垃圾收集器工作要考慮的事情:
1.哪些內存需要回收
2.什麼時候回收
3.如何回收


1.引用計數算法: 基本思想:判斷對象存活的條件是給對象添加一個引用計數器,每當有一個地方引用它時,計數器就加1;當引用失效時,計數器就減1;任何時候計數器爲0 的對象就是不可能再被使用的。引用計數算法的實現簡單,判定效率很高。主流虛擬機不是採用引用計數法,因爲它沒有解決對象之間相互循環引用的問題。

2.可達性分析算法: 在主流的商用程序語言[java,c#]的實現中,都是通過可達性分析來判定對象是否存活的。基本思想:通過一系列的稱爲GC roots 的對象作爲起始點,從這些節點開始向下搜索,搜索走過的路徑稱爲引用鏈,當一個對象到GC roots沒有任何引用鏈相連時,則證明此對象是不可用的。

在java 語言中,作爲GC roots 的對象包括下面幾種:
1.虛擬機棧中引用的對象。
2.方法區中類靜態屬性引用的對象。
3.方法區中常量引用的對象。
4.本地方法棧中Native 引用的對象。

總結:引用計數算法 和可達性分析算法都是通過對象引用來判斷對象是否存活的。

對象引用: 強引用,軟引用,弱引用,虛引用。

1.強引用:就是指在程序代碼之中普遍存在的,類似Object obj = new Object() 這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。

2.軟引用:用來描述一些還有用但並非必需的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的內存,纔會拋出內存溢出異常。

3.弱引用:也是用來描述非必須對象的,但是它的強度比軟引用更弱,被弱引用關聯的對象只能生存到下一次垃圾回收發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。

4.虛引用:幻影引用,最弱的一種引用關係。一個對象是否有徐引用的存在,完全不會對其他生存時間構成影響,也無法通過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的唯一目的就是能在這個對象唄收集器回收時收到一個系統通知。

回收方法區:方法區[Hotspot永久代]可以不要求JVM在方法區實現垃圾收集,而且在方法區中進行垃圾收集的性價比比較低。

判定一個類是否是無用的類的條件
1.該類所有的實例都已經被回收,也就是java堆中不存在該類的任何實例。

2.加載該類的classLoader已經被回收。

3.該類對應的java.lang.class 對象沒有在任何地方被引用,無法再任何地方通過反射訪問該類的方法。

總結:在大量使用反射,動態代理,CGLib 等ByteCode框架,動態生成JSP以及OSGi 這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,以保證永久代不會溢出。

垃圾收集算法:
1.標記-清除算法 mark-Sweep:標記-清除算法分爲兩個階段,首先標記處搜優需要回收的對象,在標記完成後統一回收所有被標記的對象。

缺點:效率問題,標記和清除兩個過程效率都不高;另一個是空間問題,標記清除後會產生大量不連續的內存碎片,空間碎片太多可能會導致以後再程序運行過程中需要分配較大對象時,無法找到足夠的連續內存而不得不提前觸發另一次垃圾回收動作。

2.複製算法copying:複製算法是爲了解決效率問題而出現的,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另外一塊上面,然後再把已使用過的內存空間一次清理掉。這樣使得每次都是對整個半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜情況,只要一動堆頂指針,按順序分配內存即可,實現簡單,運行高效。

註解:現在的商業虛擬機都採用這種手機算法去 回收新生代。JVM將內存分爲一塊較大的Eden 空間和兩個較小的Survivor空間,每次使用Eden 和其中的一塊Survivor 。當回收時,將Eden 和Surivor中還存活着的對象一次性的複製到另外一塊Survivor 空間上,最後清理掉Eden 和剛纔使用過的Surivor空間。HotSpot虛擬機默認Eden 和Surivor 的大小比例是:8:1。

3.標記-整理算法:複製收集算法在對象存活率較高時就要進行較多的複製操作,效率將會變低。如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。[建議:標記-清除法]

4.分代收集算法Generational Collection:根據對象存活的週期的不同將內存劃分爲幾塊。一般是把java堆分爲新生代和老年代,根據各個年代的特點採用適合的手機算法:

      1.新生代,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用複製算法,只需要付出少量存活對象的複製成本就可以完成收集。

      2.老年代,因爲對象存活率高,沒有額外空間對它進行分配擔保,必須使用"標記-清除法" 或者"標記整理算法" 進行回收。

垃圾收集器
1.Serial 收集器Serial 收集器是最基本,發展歷史最悠久的收集器,曾經是虛擬機新生代收集的唯一選擇。它是一個單線程的垃圾收集器,它的特點是:不僅僅說明它只會使用一個CPU或一條收集線程去完成垃圾收集工作,更重要的是在它進行垃圾收集時,必須暫停其他所有的工作線程,直到它收集結束。

2. ParNew 收集器:Serial收集器的多線程版本,除了使用多條線程進行垃圾收集之外,其他行爲包括Serial 收集器可用的所有控制參數[-XX:Survivorratio,-XX:PretenureSizeThreshold,-XX:HandlePromotionFailure等],收集算法,Stop The World,對象分配規則,回收策略等都與Serial收集器完全一樣。 

註解:ParNew 收集器是許多運行在Server模式下的JVM 首選的新生代收集器,因爲目前只有它能與CMS收集器配合工作。

3.Parallel Scavenge 收集器:Parallel Scavenge 收集器是一個新生代且使用複製算法的多線程收集器。Parallel Scavenge 收集器的目標是達到一個可控制的吞吐量。[吞吐量就是CPU用於運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量=運行用戶代碼時間 / (運行用戶代碼時間+垃圾收集時間),JVM總運行了100分鐘,其中垃圾收集花掉1分鐘,那麼吞吐量就是99%。]

4.Serial Old 收集器:Serial Old 是Serial 收集器的老年代版本,它的特點是單線程,使用"標記-整理算法"。它的出現在於給client 模式下的虛擬機使用。

5.Parallel Old 收集器:Parallel Old 是Parallel Scavenge收集器
的老年代版本,使用多線程和"標記-整理算法"。

6.CMS收集器 Concurrent Mark Sweep:CMS是一種以獲取最短回收停頓時間爲目標的收集器。主要體現在互聯網或B/S系統的服務端,採用"標記-清除算法"。

CMS 垃圾收集器優點:併發收集,低停頓。
缺點:
1.CMS 收集器對CPU 資源非常敏感。由於併發收集,所以雖然不會導致用戶線程停頓,但因爲佔用了一部分線程而導致應用程序變慢,總吞吐量降低。

2.CMS 收集器無法處理浮動垃圾,可能失敗而導致另一次full gc d的產生。[浮動垃圾:由於CMS 併發清理階段用戶線程還在運行着,伴隨着程序運行自然就還會有新的垃圾不斷產生,這一部分出現在標記過程之後哦,CMS無法再當次收集中處理掉它們,只好留到下一次GC 時再處理掉,這就是浮動垃圾。]

3.還有"標記-清除算法"的會產生大量的空間碎片等特點。

7.G1收集器 Garbage-First: 當今收集器技術發展的最前沿成果之一。G1是一款面向服務端應用的垃圾收集器,特點有:

1.並行與併發:G1 能充分利用多CPU,多核環境下的硬件優勢,使用多個CPU 來縮短stop the world 停頓的時間,部分其他收集器原本需要停頓java線程執行的GC 動作,G1收集器仍然可以通過併發的方式讓java程序繼續執行。

2.分代收集:雖然G1可以不需要其他收集器配合就能獨立管理整個GC堆,但它能夠採用不同的方式去處理新創建的對象和已經存活了一段時間,熬過多次GC的舊對象以獲取更好的收集效果。

3.空間整合:"標記-整理算法",不會產生空間碎片。

4.可預留的停頓:降低停頓時間是G1和CMS共同的關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度爲M毫秒的時間片段內,消耗在垃圾收集器上的時間不得超過N秒。

G1 動作:
1.初始標記
2.併發標記
3.最終標記
4.篩選回收

對象優先在Eden 分配:一般情況下,對象在新生代Eden區中分配。當Eden 區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC。

新生代GC [Minor GC]: 發生在新生代的垃圾收集動作,因爲java對象大多都具備朝生夕死的特性,所以Minor GC 非常頻繁,一般回收速度也比較快。

老年代GC[Major GC | Full GC]:發生在老年代的GC ,出現了Major GC ,經常會伴隨至少一次Major GC。Major GC的速度一般會比Minor GC 慢10倍以上。

大對象直接進入老年代:大對象指的是需要大量連續內存空間的java對象,最典型的是那種很長的字符串以及數組。

長期存活的對象將進入老年代: 一個對象熬過了一次又一次的垃圾回收,超過15次會被晉升到老年代中。

JDK可視化工具:C:\Program Files\Java\jdk1.8.0_73\bin\jconsole.exe
                             C:\Program Files\Java\jdk1.8.0_73\bin\jvisualvm.exe

JConcole: java 監視與管理控制檯。它是一種基於JMX 的可視化監視管理工具。它管理部分的功能是針對JMX MBean 進行管理,由於MBean 可以使用代碼,中間件服務器的管理控制檯或者所有符合JMX規範的軟件進行訪問,裏面包括:概覽,內存,線程,類,VM概要,MBean 等信息。

 

 

 

內存監控:內存相當於可視化的jstat 命令,用於監視受收集器管理的虛擬機內存的變化趨勢。

線程監控:相當於可視化的jstack 命令,遇到線程停頓時可以使用這個頁面進行監控分析。
[停頓的主要原因有:等待外部資源(數據庫連接,網絡資源,設備資源等),死循環,鎖等待(活鎖|死鎖)]

VisualVM:多合一故障處理工具:性能分析,VisualVM 的性能分析功能甚至比JProfiler,YourKit 等專業收費的profiling 工具都不會遜色,而且它的有點很明顯:不需要被監視的程序基於特殊Agent 運行,因此它對應用程序的實際性能的影響很小,使得它可以直接應用在生產環境中。

VisualVM 基於NetBeans 平臺開發,因此它具備插件擴展功能的特性,通過插件擴展支持,VisualVM可以做到:

1.顯示虛擬機進程以及進程的配置,環境信息(jps,jinfo)

2.監視應用程序的CPU,GC,堆,方法區以及線程的信息(jstat,jstack)

3.dump 以及分析堆轉儲快照(jmap,jhat)

4.方法級的程序運行性能分析,找出被調用最多,運行時間最長的方法。

5.離線程序快照:手機程序的運行時配置,線程dump,內存dump 等信息建立一個快照,可以將快照發送開發者進行bug反饋。

6.其他plugins 的無限的可能性。

BTrace 動態日誌跟蹤:BTrace 是一個VisualVM插件,本身也是可以獨立運行的程序。它的作用是在不停止目標程序運行的前提下,通過HotSpot虛擬機的HotSwap技術動態加入原本並不存在的調試代碼。在實際生產中的程序很有意義:經常遇到程序出現問題,但排查錯誤的一些必要信息,比如方法參數,返回值等,在開發中並沒有打印到日誌之中,這時就可以採用BTrace。BTrace安裝步驟地址[可以避免入坑]:https://blog.csdn.net/qq_35781178/article/details/102936149

BTrace 使用教程如下[先把項目啓動起來哦]:

   

@OnMethod(clazz="com.yonghui.yh.soi.perform.pickcenter.view.service.impl.PickingScreenServiceImpl",
                  method="queryUnderSKUNum",location=@Location(Kind.RETURN))
    public static void func(@Self com.yonghui.yh.soi.perform.pickcenter.view.service.impl.PickingScreenServiceImpl instance,
     String locationCode,@Return String result)
    {
      println("調用堆棧");
      jstack();
      println(strcat("方法參數locationCode",locationCode));
      println(strcat("方法結果result",result));
    }

《第七章:虛擬機類加載機制》

類加載的時機: 加載,驗證,準備,解析,初始化,使用,卸載。

1.使用java.lang.reflect 包的方法對類進行反射調用的時候,如果類沒有進行過初始化,則需要先觸發其初始化。

2.當初始化一個類的時候,如果發現其父類還沒有進行初始化,則需要先觸發其父類的初始化。

3.當虛擬機啓動時,用戶需要執行一個要執行的朱磊,虛擬機會先初始化這個主類。


加載:
 1.通過一個類的全限定名來獲取來獲取定義此類的二進制字節流
 2.將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構
 3.在內存中生成一個代表這個類的java.lang.Class 對象,作爲方法區這個類的各種數據的訪問入口。

 雙親委派模型:
  從JVM 來說,只存在李閬中不同的類加載器:一種是啓動類加載器,這個類加載器使用C++ 語言實現,是JVM自身的一部分;另一種就是所有其他的類加載器,這些類加載器都由java 語言實現,獨立於JVM 外部,並且全都繼承自抽象類java.lang.ClassCoader。

volatile 可以理解成java 虛擬機提供的最輕量級的同步機制。當一個變量定義爲volatile 之後,它將具備兩種特性:第一就是保證此變量對所有線程的可見性,指的是當一條線程修改了這個變量的值,新值對於其他線程來說是可以立即得知的。而普通變量是不能做到。【volatile 的特殊規則保證了新值能立即同步到主內存,以及每次使用前立即從主內存刷新。】

由於volatile 變量只能保證可見性,在不符合以下兩條規則的運算場景下,我們仍然要通過加鎖來保證原子性:
1.運算結果並不依賴變量的當前值,或者能夠確保只有單一的線程修改變量的值。
2.變量不需要與其他的狀態變量共同參與不變約束。

java 語言提供了volatile 和 synchronized 兩個關鍵字來保證線程之間操作的有序性,volatile 關鍵字本身就包含了禁止指令重排序的語義,而synchronized 則由一個變量在同一時刻只允許一條線程對其進行lock操作這條規則獲取的,這條規則決定了持有同一個鎖的兩個同步塊只能串行的進入。

java 線程調度: 線程調度是指系統爲線程分配處理器使用權的過程,主要調度方式有兩種,分別是協同式線程調度和搶佔式線程調度。

如果使用協同式調度的多線程系統,線程的執行時間由線程本身來控制,線程把自己當工作執行完了之後,要主動通知系統切換到另一個線程上。協同式多線程的最大好處就是實現簡單。

狀態轉換: java 語言定義了5種線程狀態,在任意一個時間點,一個線程有且只有其中的一種狀態: 新建new,運行runable,等待wait,阻塞blocked,結束terminated。

新建new:創建後尚未啓動的線程處於此狀態。

運行runable:runable 包括了操作系統線程狀態中的running 和ready,也就是處於此狀態的線程有可能正在執行,也有可能正在等待着CPU 爲它分配執行 時間。

無限期等待waiting:處於這種狀態的線程不會被分配CPU執行時間,他們要等待被其他線程顯示的喚醒。

以下方法會讓線程陷入無限期的等待狀態:
1.沒有設置timeout 參數的object.wait()方法。
2.沒有設置timeout參數的Thread.join()方法。
3.lockSupport.park()方法。

限期等待 timed waitting :處於這種狀態的線程也不會被分配CPU 執行時間,不過無需等待被其他線程顯示的喚醒,在一定時間之後他們會由系統自動喚醒:

1.thread.sleep()方法。
2.設置了timeout 參數的object.wait()方法。
3.locksupport.parknanos()方法。
4.locksupport.parkUntil()方法。

阻塞blocked:線程被阻塞了,阻塞狀態與等待狀態的區別就是:阻塞狀態在等待着獲取到一個排他鎖,這個事件將在另外一個線程放棄這個鎖的時候發生;而等待狀態則是在等待一算時間,或者喚醒動作的發生。在程序等待進入同步區域的時候,線程將進入這種狀態。

結束terminated:已終止線程的線程狀態,線程已經結束執行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章