讀書筆記之深刻理解Java虛擬機

前言

本文內容基本摘抄自《深刻理解Java虛擬機》,以供複習之用,沒有多少參考價值。想要更詳細瞭解請參考原書。java

第二章

1.運行時數據區域

圖片描述

程序計數器能夠看做是當前線程所執行的字節碼的行號指示器,每條線程都須要有一個獨立的程序計數器。若是線程執行Java方法,計數器記錄正在執行的虛擬機字節碼指令地址;若是執行Native方法,計數器值爲空。此區域是惟一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域。算法

Java虛擬機棧也是線程私有的,生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每一個方法在執行的同時都會建立一個棧幀用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError異常;若是虛擬機棧能夠動態擴展,若是擴展時沒法申請到足夠內存,就會拋出OutOfMemoryError異常。數組

本地方法棧相似虛擬機棧,區別是本地方法棧爲虛擬機使用到的Native方法服務。安全

Java堆是全部線程共享的內存區域,在虛擬機啓動時建立。全部對象實例以及數組都要在堆上分配。Java堆是GC管理的主要區域。從內存回收角度,Java堆能夠細分爲新生代和老年代,若是使用複製算法收集,還能夠分爲Eden空間、From Survivor空間、To Survivor空間。從內存分配角度,線程共享的Java堆可能劃分出多個線程私有的分配緩衝區(TLAB)。若是堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出OutOfMemoryError異常。數據結構

方法區是全部線程共享區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。HotSpot虛擬機上把方法區稱爲永久代。但用永久代實現方法區有問題,例如String.intern()在不一樣虛擬機有不一樣表現。JDK1.7已經把本來放在永久代的字符串常量池移出。當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError異常。多線程

運行時常量池是方法區的一部分。Class文件中除了類的版本、字段、方法、接口等描述信息外,還有常量池,這部分將在類加載後進入方法區的運行時常量池中存放。運行時常量池相對於Class文件常量池的另一個特徵是動態性,並不是預置入Class文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中(intern())。架構

直接內存不是虛擬機運行時數據區一部分。JDK NIO引入了一種基於通道和緩衝區的I/O方式,它可使用Native函數直接分配堆外內存,而後經過一個存儲在Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。分配的直接內存致使各內存區域總和大於物理內存限制從而致使動態擴展時出現OutOfMenmoryError。併發

2.關於對象

對象的建立:圖片描述模塊化

對象的內存佈局:
對象在內存中存儲的佈局分爲:對象頭、實例數據和對齊填充。對象頭分爲對象運行時數據和類型指針。函數

對象運行時數據包括HashCode、GC分代年齡和鎖狀態標誌位等。類型指針即對象指向它的類元數據的指針。另外,若是對象是一個Java數組,那在對象頭中還有一塊用於記錄數組長度的數據。

實例數據部分是對象真正存儲的有效信息,也是在程序代碼中所定義的各類類型的字段內容。

對齊補充不是必然存在,起着佔位符做用。保證對象起始地址是8字節的整數倍。

對象的訪問:
Java程序經過棧上的reference數據來操做堆上的具體對象。訪問方式分爲使用句柄和直接指針兩種。使用句柄,Java堆中會劃分出一塊內存做爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自的具體地址信息。若是使用直接指針訪問,那麼Java堆對象的佈局中就要放置訪問類型數據的相關信息。

3.部分虛擬機啓動參數
-Xms:堆的最小值
-Xmx:堆的最大值
-Xmn::堆分配給新生代的大小
-XX:+HeapDumpOnOutOfMemoryError:虛擬機出現內存溢出異常時Dump出當前內存堆轉儲快照
-Xss:棧容量
-XX:PermSize:永久代最小值
-XX:MaxPermSize:永久代最大值
-XX:MaxDirectMemorySize:本機直接內存大小,若是不指定,默認與-Xmx同樣。

第三章

1.對象存亡
判斷對象是否存活有兩種方法,引用計數算法和可達性分析。

引用計數算法:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值加1;當引用失效時,計數器值減1;任什麼時候刻計數器爲0的對象就是不可能再被使用的。引用計數算法的優勢是實現簡單,斷定效率也高。缺點是它很難解決對象之間相互循環引用的問題。

可達性分析算法:經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的。可做爲GC Roots的對象包括:虛擬機棧中引用的對象、方法區中類靜態屬性引用的對象、方法區中常量引用的對象、本地方法棧中JNI引用的對象。

對象引用:
強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)。
強引用只要存在,垃圾收集器永遠不會回收被引用對象。軟引用用來描述還有用但並不是必須的對象。對於軟引用關聯着的對象,在系統將要發生內存溢出異常以前,將會把這些對象列進回收範圍進行二次回收。弱引用也用來描述非必須對象,被弱引用關聯的對象只能生存到下一次垃圾收集發生以前。一個對象是否有虛引用的存在不影響生存時間,也沒法經過虛引用取得對象實例。虛引用惟一目的是在垃圾回收時收到一個系統通知。

對象存亡:
宣告對象死亡至少要經歷兩次標記過程:若是對象在進行可達性分析後發現沒有與GC Roots相鏈接的引用鏈,那它將會被第一次標記並進行一次篩選是否有必要執行finalize()方法。若對象沒有覆蓋此方法或曾經調用過此方法,則不必執行。若是在finalize()方法中從新與引用鏈創建聯繫,則對象存活,不然 進行第二次標記。

方法區的回收:
永久代回收包括廢棄常量和無用的類。其中類的回收條件較爲苛刻:該類全部實例已回收;該類的類加載器已回收;該類的Class對象沒有被引用。知足這三個條件才能夠回收,而不是必然回收。

2.垃圾收集算法
標記-清除算法:標記和清除效率都不高且標記清除後產生大量不連續內存碎片。

複製算法:堆分爲一塊Eden和兩塊Survivor,大小爲8:1:1,每次使用Eden和其中一塊Survivor,回收時,將上面存活的對象複製到另一塊Survivor上,清理Eden和剛纔使用過的Survivor空間。當Survivor空間不夠時,須要依賴老年代進行分配擔保。

標記-整理算法:複製算法在對象存活率高時須要較多複製操做,效率較低。標記整理相似標記清除,不過標記後讓全部存活對象移向一端,而後清理掉邊界之外的內存。

分代收集算法:根據對象存活週期不一樣將內存劃分爲幾塊。新生代使用複製算法,老年代使用標記清除和標記整理。

3.算法實現

可達性分析時間敏感:
可達性分析從GC Roots中查找引用鏈,GC Roots節點包括全局性引用與執行上下文,而僅僅方法區就有數百兆,若是逐個檢查,會耗費大量時間。可達性分析還須要GC Roots停頓。

安全點:
HotSpot使用一組OopMap的數據結構存放對象引用。但HotSpot沒有爲每條指令生成OopMap,只在安全點記錄信息。通常「長時間執行」的指令,如方法調用、循環跳轉、異常跳轉會產生SafePoint。GC時讓全部線程在最近安全點停頓分爲搶先式中斷和主動式中斷。

安全區域:
線程處於Sleep或Blocked狀態,沒法響應JVM的中斷請求,就沒法執行到安全點掛起。安全區域是指一段代碼之中,引用關係不會發生變化,這個區域任意地方開始GC都是安全的。

4.垃圾收集器
圖片描述

Serial收集器:
單線程收集器,不只僅只會使用一個CPU或一條收集線程去完成垃圾收集工做,它在進行垃圾收集時必須暫停全部其餘工做線程。優勢是簡單而高效。

ParNew收集器:
多線程收集器,除了多線程收集都與Serial同樣。優勢是除了Serial,只有它能與CMS配合工做。

Parallel Scavenge收集器:
多線程收集器,目標是吞吐量優先(CPU用於運行用戶代碼的時間與CPU總消耗時間的比值)。

Serial Old收集器:
Serial老年代版本,單線程收集器,「標記-整理」算法。

Parallel Old收集器:
Parallel Scavenge的老年代版本,多線程收集,「標記-整理」算法,注重吞吐量。

CMS收集器:
以獲取最短回收停頓時間爲目標的收集器。「標記-清除」算法,分爲:初始標記、併發標記、從新標記、併發清除。初始標記和從新標記仍然須要「Stop The World」。
初始標記僅僅標記GC Roots能直接關聯到的對象,速度很快;併發標記就是進行GC Roots Tracing的過程;從新標記是爲了修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄,時間比初始標記稍長,比並發標記短。因爲整個過程耗時最長的併發標記和併發清除過程收集器線程均可以與用戶線程一塊兒工做。因此CMS的內存回收是與用戶線程一塊兒「併發」執行的。
CMS有3個缺點:對CPU資源很是敏感;沒法處理浮動垃圾、會產生大量空間碎片(標記-清除)。

G1收集器:
G1的優勢是並行與併發、分代收集、空間整合、可預測的停頓。回收過程分爲:初始標記、併發標記、最終標記、篩選回收。

5.內存分配與回收策略

  • 對象優先在Eden分配
  • 大對象直接進入老年代
  • 長期存活的對象將進入老年代
  • 動態對象年齡斷定
  • 空間分配擔保

第四章

1.JDK命令行工具

jps:虛擬機進程情況工具。能夠列出正在運行的虛擬機進程,並顯示虛擬機執行主類名稱以及這些進程的本地虛擬機惟一ID。
jstat:虛擬機統計信息監視工具。能夠顯示本地或者遠程虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。
jinfo:Java配置信息工具。能夠實時地查看和調整虛擬機各項參數。
jmap:java內存映像工具。用於生成堆轉儲快照。
jhat:虛擬機堆轉儲快照分析工具。分析jmap生成的快照。
jstack:Java堆棧跟蹤工具。用於生成虛擬機當時的線程快照,就是當前虛擬機內每一條線程正在執行的方法堆棧的集合。
HSDIS:JIT生成代碼反彙編。

2.JDK可視化工具

jconsole:Java監視與管理控制檯。
VisualVM:多合一故障處理工具。

第六章

1.Class類文件的結構
Class文件格式採用一種相似於C語言結構體的僞結構來存儲數據,這種僞結構只有兩種數據類型:無符號數和表。無符號數分爲u一、u二、u四、u8。表由多個無符號數或者其餘表做爲數據項構成的複合數據類型。

  • 魔數
  • Class文件版本
  • 訪問標誌
  • 類索引、父類索引與接口索引集合
  • 字段表集合
  • 方法表集合
  • 屬性表集合

其中,屬性表集合有Code屬性、Exceptions屬性、LineNumberTable屬性、LocalVariableTable屬性、SourceFile屬性、ConstantValue屬性、InnerClasses屬性、Deprecated及Synthetic屬性、stackMapTable屬性、Signature屬性、BootstrapMethods屬性等。

2.字節碼指令

  1. 加載和存儲指令
  2. 運算指令
  3. 類型轉換指令
  4. 對象建立與訪問指令
  5. 操做數棧管理指令
  6. 控制轉移指令
  7. 方法調用和返回指令
  8. 異常處理指令
  9. 同步指令

第七章

1.類加載機制
虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型,這就是虛擬機的類加載機制。Java語言裏,類型加載、鏈接和初始化過程都是在程序運行期間完成的,所以Java有高度的靈活性。

2.類加載的時機
圖片描述

加載、驗證、準備、初始化、卸載循序漸進的開始,解析階段能夠在初始化以後開始(爲了支持Java語言的運行時綁定)。

當即對類進行初始化的五種狀況:

  • 遇到new(實例化對象)、getstatic(讀取類的靜態字段)、putstatic(設置類的靜態字段)、invokestatic(調用一個類的靜態方法)4條字節碼指令時;
  • 使用reflect對類反射調用時
  • 初始化一個類時,若是其父類還未初始化(對於接口來講,只有在真正使用父接口時纔會初始化)
  • 虛擬機啓動時指定執行主類
  • 使用動態語言支持時,MethodHandle的解析結果的方法句柄所對應的類沒有進行過初始化

經過子類引用父類的靜態字段,不會致使子類初始化;經過數組定義來引用類,不會觸發此類的初始化;常量在編譯階段會存入調用類的常量池中,本質上並無直接引用到定義常量的類,所以不會觸發定義常量的初始化。

3.類加載的過程

加載:

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

驗證:

  1. 文件格式驗證
  2. 元數據驗證
  3. 字節碼驗證
  4. 符號引用驗證

準備:
準備階段是正式爲類變量分配內存並設置類變量初始值(一般狀況爲零值,final則直接賦值)的階段,這些變量所使用的內存都將在方法區中進行分配。

解析:
解析階段是虛擬機將常量池內的符號引用替換爲直接引用的過程。符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,符號引用與虛擬機實現的內存佈局無關;直接引用能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄,直接引用和虛擬機實現的內存佈局相關。

  1. 類或接口的解析
  2. 字段解析
  3. 類方法解析
  4. 接口方法解析
  5. 方法類型解析
  6. 方法句柄解析
  7. 調用點限定符解析

初始化:
前面的類加載過程當中,除了加載階段用戶應用程序能夠經過自定義類加載器參與以外,其他動做徹底由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程序代碼(字節碼)。初始化階段是執行類構造器<clinit>()方法的過程。

4.類加載器
類加載階段中的「經過一個類的全限定名來獲取描述此類的二進制字節流」就是由類加載器實現的。且類加載器左右不限於此,任意一個類都須要由加載它的類加載器和這個類自己一同確立其在Java虛擬機中的惟一性,每個類加載器都擁有一個獨立的類名稱空間。

雙親委派模型:
從JVM角度只存在兩種類加載器:啓動類加載器(Bootstrap ClassLoader),由C++實現;全部其餘類加載器,由Java實現。從開發角度分爲四類:啓動類加載器(Bootstrap ClassLoader),<JAVA_HOME>lib目錄中或被-Xbootclasspath參數指定的路徑中虛擬機識別的類庫,用戶沒法直接引用;擴展類加載器(Extension ClassLoader)負責<JAVA_HOME>libext或被java.ext.dirs系統變量指定的路徑中全部類庫,用戶能夠直接使用;應用程序類加載器(Application ClassLoader),或稱系統類加載器,負責加載ClassPath上指定的類庫,用戶可使用,默認類加載器;自定義類加載器。

雙親委派模型的工做過程是:若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,全部的加載請求最後都應該傳送到頂層的啓動類加載器,只有當父加載器反饋沒法加載(搜索範圍沒找到須要的類)時,子加載器纔會嘗試本身去加載。

雙親委派模型的好處是Java類隨着它的類加載器一塊兒具有了一種帶有優先級的層次關係,保證了Java程序的穩定運做。

破壞雙親委派模型:

  1. 面對已經存在的用戶自定義類加載器的實現代碼,爲了向前兼容,JDK1.2以後的java.lang.ClassLoader添加了一個新的protected方法findClass()。
  2. 雙親委派模型很好地解決了各個類加載器的基礎類的統一問題,但基礎類可能會回調用戶代碼。所以引入線程上下文類加載器,能夠經過java.lang.Thread類的setContextClassLoader()方法進行設置,默認爲應用程序類加載器。這實際上是父類加載器請求子類加載器完成類加載,打破了雙親委派模型。
  3. 用戶對程序動態性的追求致使破壞(代碼熱替換、模塊熱部署)。OSGI實現模塊化部署的關鍵是它自定義的類加載器機制的實現。每個程序模塊(Bundle)都有一個本身的類加載器,當須要更換一個Bundle時,就把Bundle連同類加載器一塊兒換掉以實現代碼的熱替換。

第八章

1.運行時棧幀結構
棧幀存儲了方法的局部變量表、操做數棧、動態連接和方法返回地址等信息。每個方法從調用開始至執行完成的過程,都對應着一個棧幀在虛擬機棧裏面從入棧到出棧的過程。圖片描述

局部變量表:
局部變量表是一組變量值存儲空間,用於存放方法參數和方法內部定義的局部變量。在Java程序編譯爲Class文件時,就在方法的Code屬性的max_locals數據項中肯定了該方法須要分配的局部變量表的最大容量。

虛擬機經過索引定位的方式使用局部變量表,索引值的範圍是從0開始至局部變量表最大的Slot數量。若是執行的是實例方法,局部變量表中第0位索引的Slot默認是用於傳遞方法所屬對象實例的引用,可經過「this」訪問。

存在一種特殊情形,對象佔用內存大、此方法的的棧幀長時間不回收、方法調用次數達不到JIT編譯條件,手動將再也不使用的變量設置null是有用的。

局部變量不像類變量有準備階段,沒有賦初始值不能使用。

操做數棧:
操做數棧是一個後入先出棧。同局部變量表同樣,操做數棧的最大深度也在編譯的時候寫入到Code屬性的max_stack數據項中。

當一個方法剛剛開始執行時,這個方法的操做數棧是空的,在方法的執行過程當中,會有各類字節碼指令往操做數棧中寫入和提取內容,也就是入棧出棧操做。

概念模型中,兩個棧幀做爲虛擬機棧的元素,是徹底相互獨立的。但在大多數虛擬機的實現裏都會作一些優化處理,令兩個棧幀出現一部分重疊。

動態連接:
Class文件的常量池中存有大量的符號引用,這些符號引用一部分會在類加載階段或者第一次使用時轉化爲直接引用,這種轉化稱爲靜態解析;另一部分將在每一次運行期間轉化爲直接引用,稱爲動態連接。

方法返回地址:
方法開始執行後,有兩種退出方式:正常完成出口和異常完成出口。方法返回時須要在棧幀中保存一些信息,用來幫助恢復它的上層方法的執行狀態。方法正常退出時,調用者的PC計數器的值能夠做爲返回地址,會在棧幀中保存;方法異常退出時,返回地址是要經過異常處理器表來肯定,棧幀中不保存。

附加信息:
容許增長附加信息到棧幀中。

2.方法調用
方法調用階段惟一的任務就是肯定被調用方法的版本,暫不涉及方法內部運行。

Class文件的編譯過程當中不包含傳統編譯中的鏈接步驟,一切方法調用在Class文件裏面存儲的都只是符號引用,而不是方法在內存的入口地址。須要在類加載期間,甚至到運行期間才能肯定目標方法的直接引用。

Java虛擬機五條方法調用指令:
invokestatic:調用靜態方法;
invokespecial:調用實例構造器<init>、私有方法和父類方法。
invokevirtual:調用全部的虛方法。
invokeinterface:調用接口方法,會在運行時再肯定一個實現此接口的對象。
invokedynamic:如今運行時動態解析出調用點限定符所引用的方法,而後再執行該方法。

前4條調用指令分派邏輯是固化在Java虛擬機內部的,而invokeddynamic指令的分派邏輯是由用戶所設定的引導方法決定的。

解析:
在類加載的解析階段,會將其中的一部分符號引用轉化爲直接引用,這部分調用目標在程序代碼寫好、編譯器進行編譯時就必須肯定下來。這類方法的調用稱爲解析。

只要能被invokestatic和invokespecial指令調用的方法,均可以在解析階段中肯定惟一的調用版本,在類加載的時候就會把符號引用解析爲該方法的直接引用。

Java中的非虛方法除了使用invokestatic、invokespecial調用的方法以外,還有final修飾的方法,雖然final使用invokevirtual調用。

分派:
解析調用必定是靜態過程,在編譯期間徹底肯定,在類加載的解析階段就會把符號引用替換爲直接引用;而分派調用多是靜態或動態。

  • 靜態分派:全部依賴靜態類型來定位方法執行版本的分派動做稱爲靜態分派。靜態分派的典型應用是方法重載。靜態分派發生在編譯階段,所以肯定靜態分派的動做實際上不是由虛擬機來執行的。另外,編譯器雖然能肯定出方法的重載版本,但有時並不惟一,緣由是字面量不須要定義,因此字面量沒有顯式的靜態類型。
  • 動態分派:運行期間根據實際類型肯定方法執行版本的分派過程稱爲動態分派。動態分派的典型應用是方法重寫。invokevirtual指令執行的第一步就是在運行期肯定接收者的實際類型。
  • 單分派與多分派:方法的接收者與方法的參數統稱爲方法的宗量,根據分派基於多少宗量,能夠將分派劃分爲單分派和多分派。Java語言是一門靜態多分派、動態單分派的語言。
  • 虛擬機動態分派實現:使用虛方法表索引來代替元數據查找以提升性能。虛方法表中存放着各個方法的實際入口地址。

3.動態類型語言支持
動態類型語言的關鍵特徵是它的類型檢查的主體過程是在運行期而不是編譯期。編譯器就進行類型檢查過程的語言就是最經常使用的靜態類型語言。「變量無類型而變量值纔有類型」也是動態類型語言的一個重要特徵。

靜態語言在編譯期肯定類型,最顯著的好處是編譯器能夠提供嚴謹的類型檢查;動態類型語言在運行期肯定類型,能夠提供更大的安全性,也使代碼更加清晰和簡潔。

java.lang.invoke:
這個包的主要目的是在以前JVM單純依靠符號引用來肯定調用的目標方法這種方式以外,提供一種新的動態肯定目標方法的機制,稱爲MethodHandle。

MethodHandle與Reflection類似之處不少,區別以下:Reflection是在模擬Java代碼層次的方法調用,MethodHand是在模擬字節碼層次的調用;Reflection是重量級,MethodHandle是輕量級;因爲MethodHandle是對字節碼的模擬,因此虛擬機在這方面的優化能夠被MethodHandle借鑑。

invokedynamic:
invokedynamic指令與MethodHandle機制的做用同樣,解決原有4條「invoke*」指令方法分派規則固化在虛擬機之中的問題,把如何查找目標方法的決定權從虛擬機轉嫁到具體用戶代碼之中。區別是一個採用上層Java代碼和API實現,另外一個用字節碼和Class中其餘屬性、常量來完成。

4.基於棧的字節碼解釋執行引擎
Java語言中,Javac編譯器完成了程序代碼通過詞法分析、語法分析到抽象語法樹,再遍歷語法樹生成線性的字節碼指令流的過程。這一部分在JVM以外進行,而解釋器和解釋執行在JVM內部,因此Java的編譯是半獨立的實現。

Java編譯器輸出的指令流,是一種基於棧的指令集架構。基於棧的指令集主要優勢就是可移植,缺點是執行速度相對較慢。

第十章

Javac編譯器

從Javac的代碼來看,編譯過程分爲3個過程:

  1. 解析與填充符號表過程:詞法、語法分析;填充符號表
  2. 插入式註解處理器的註解處理過程
  3. 語義分析與字節碼生成過程:標註檢查、數據及控制流分析、解語法糖、字節碼生成

Java語法糖

泛型和類型擦除:泛型的本質是參數化類型,即所操做的數據類型被指定爲一個參數。Java語言中的泛型只在程序源碼中存在,在編譯後的字節碼文件中,就已經替換爲原來的原生類型,並在相應地方插入了強制類型轉化代碼,ArrayList<int>和ArrayList<String>是同一個類。Java的泛型是僞泛型。

自動裝箱、拆箱與循環遍歷:Java語言使用最多的語法糖。

條件編譯:根據布爾常量值的真假,編譯器將會把分支中不成立的代碼塊消除掉,因爲這種條件編譯的實現方式使用了if語句,因此只能寫在方法體內部,只能實現語句基本塊級別的條件編譯。

第十一章

1.HotSpot虛擬機內的即時編譯器

解釋器與執行器:

當程序須要迅速啓動和執行的時候,解釋器能夠首先發揮做用,省去編譯的時間,當即執行。在程序運行後,隨着時間的推移,編譯器逐漸發揮做用,把愈來愈多的代碼編譯成本地代碼以後,能夠獲取更高的執行效率。

HotSpot虛擬機內置兩個即時編譯器,分別稱爲Client Compiler和Server Compiler(C1和C2).目前主流的HotSpot虛擬機中,默認採用解釋器與其中一個編譯器直接配合的方式工做。爲了在程序啓動響應速度與運行效率之間達到最佳平衡,HotSpot虛擬機會逐漸啓用分層編譯的策略。

編譯對象與觸發條件:

在運行過程當中會被即時編譯器編譯的熱點代碼有兩類:被屢次調用的方法;被屢次執行的循環體。這兩種狀況編譯器都會以整個方法做爲編譯對象。

判斷是不是熱點代碼和是否須要觸發即時編譯的行爲稱爲熱點探測,方式分爲:基於採樣的熱點探測、基於計數的熱點探測。

編譯過程:
Server Complier和Client Complier兩個編譯器編譯過程不同。Client是一個簡單快速的三段式編譯器,主要關注局部性的優化,Server是一個充分優化過的高級編譯器。

2.編譯優化技術

  • 方法內聯:去除方法調用的成本(創建棧幀等);能夠便於在更大範圍內採起後續的優化手段。
  • 冗餘訪問消除:
  • 複寫傳播
  • 無用代碼消除
  • 公共子表達式消除:若是表達式E已計算過,且E中全部變量值未發生變化,那就直接用已計算過的代替
  • 數組範圍檢查消除:若是編譯器只要經過數據流分析就能夠斷定循環變量的取值範圍在區間內,那在整個循環中能夠把數組的上下界檢查消除,能夠節省不少次的條件判斷操做
  • 逃逸分析:分析對象動態做用域:當一個對象在方法中被定義後,它可能被外部方法所引用,例如做爲調用參數傳遞到方法中,稱爲方法逃逸,還有可能被外部線程訪問到,稱爲線程逃逸。若是確認沒法逃逸,能夠進行一些高效優化:棧上分配、同步消除、標量替換。

第十二章

1.Java內存模型

Java內存模型規定了全部的變量都存儲在主內存中(虛擬機內存一部分)。每條線程還有本身的工做內存,線程的工做內存中保存了該線程使用到的變量的主內存的副本拷貝,線程對變量的全部操做都必須在工做內存中進行,而不能直接讀寫主內存中的變量。不一樣的線程之間也沒法直接訪問對方工做內存中的變量,線程變量值的傳遞均須要經過主內存來完成。

內存交互:lock、unlock、read、load、use、assign、store、write。

volatile:
volatile變量兩種特性:第一是保證此變量對全部線程的可見性,可見性是指當一條線程修改了這個變量的值,新值對於其餘線程當即可知。第二是禁止指令重排序優化。
volatile使用場景:運算結果並不依賴變量的當前值,或者可以確保只有單一的線程修改變量的值;變量不須要與其餘的狀態變量共同參與不變約束。

原子性、可見性、有序性

先行發生原則:先行發生是Java內存模型中定義的兩項操做之間的偏序關係,若是說操做A先行發生於操做B,其實就是在發生操做B以前,操做A產生的影響能被操做B觀察到:程序次序規則、監視器鎖規則、volatile變量規則。時間前後順序與先行發生原則之間基本沒有太大關係。

2.Java與線程

實現線程主要有3種方式:使用內核線程實現、使用用戶線程實現和使用用戶線程加輕量級進程混合實現。Java採用一對一線程模型。

Java線程調度:線程調度是指系統爲線程分配處理器使用權的過程,分別是協同式線程調度和搶佔式線程調度。

線程狀態:新建、運行、無限期等待、限期等待、阻塞、結束。

第十三章

1.線程安全
Java中各類操做共享的數據分爲如下5類:不可變、絕對線程安全、相對線程安全、線程兼容、線程對立。

線程安全的實現方法:互斥同步、非阻塞同步、無同步方案。

2.鎖優化
自旋鎖和自適應自旋、鎖消除、鎖粗化、輕量級鎖、偏向鎖。

後記

第五章、第九章、第十章的插入式註解處理器等實例分析須要重點看一下。

相關文章
相關標籤/搜索