初識
Java SE + 擴充 = Java EE
擴充通常以 javax. 做爲包名,java. 均爲Java SE API的核心包,因爲歷史緣由,核心包中也包含很多 javax.*。java
JDK 1.4,引入NIO類。程序員
2004.9.30 發佈 JDK 1.5,引入java.util.concurrent 包。算法
JDK 1.7,引入java.util.concurrency.forkjoin。數組
Apach Hadoop Map/Reduce: 分佈式並行運算框架。瀏覽器
Scale, Erlang, Clojure: 天生具有並行運算能力。緩存
JDK 1.6 Update 14 後,提供了普通對象指針壓縮功能以減緩64位虛擬機的內存消耗與性能問題(指針膨脹和數據類型對白補齊引發)。安全
自動內存管理機制
Java 內存區域與溢出
JVM將管理的的內存劃分爲:服務器
- 程序計數器: 當前執行字節碼的行號指示器。執行native方法時,值爲空。
- 虛擬機棧: Java方法執行的內存模型,當執行時建立棧幀,用於存儲局部變量表,操做棧,動態連接,方法出口等。調用方法則入棧,結束出棧。局部變量所需內存在編譯期完成分配。大小由-Xss設置。無限遞歸,定義大量本地變量可發生StackOverflow(由OOM引發)。定義大量線程可發生OOM。
- 本地方法棧: Native方法。Sum HotSpot 將其與虛擬機棧合二爲一,故-Xoss參數(設置本地方法棧大小)存在但無效。
- Java堆(線程間共享): 存儲對象實例及數組。物理上不連續,邏輯上連續,通常設計成可拓展(經過-Xmx和-Xms實現)。GC主要區域。不斷生成對象並加入List可發生OOM。
- 方法區(線程間共享): 存儲已被加載的類信息,常量,靜態變量,即時編譯後產生的代碼等。GC較少,JVM規範限制很是寬鬆。-XX:PermSize和-XX:MaxPermSize。使用反射,動態代理,CGLib可實現OOM。
- 運行時常量池: 存放編譯期生成的字面量和符號引用。具備動態性。使用Native方法String.intern()(動態生成字符串並加入運行時常量池)生成大量常量並加入List可發生OOM。
直接內存: 不屬於JVM數據區。NIO使用Native函數直接分配外內存,經過存儲在Java堆中的DirectByBuffer對象做爲此塊內存的引用。這樣避免Java堆與Native堆間數據複製。其不受Java堆大小限制(-Xmx參數)。其大小由-XX:MaxDirectMemorySize控制,當不指定此值時,其默認值與-Xmx同樣)。根據反射得到Unsafe實例並進行內存分配可發生OOM。
Socket緩衝區網絡
Object object = new Object()多線程
- object 以reference類型存儲於Java棧的本地變量表中。
- Object類型全部的實例數據值以一個結構化內存形式存儲於Java堆。同時包含此對象的類型數據(類,父類,實現的接口,方法等)的地址。
- 類型數據存儲於方法區中。
reference 定位對象的方法
- 直接指向Java堆的地址,其中還存放指向類型數據的地址信息。Sun HotSpot採用此方法。優勢: 尋址快。
- 指向句柄池,Java堆劃分一塊內存做爲句柄池,其中包含對象地址(Java堆)及對象類型地址(方法區)。優勢: GC代價小,對象被移動時無需更改reference.
JDK1.2 後,Java對引用進行了擴充
- Strong Reference: 正常的引用。
- Soft Reference: 系統將要發生OOM時觸發第二次GC回收並將Soft Reference列入回收範圍,依舊內存不足則觸發OOM。
- Week Reference: 僅能存活到下次GC。
- Phantom Reference: 沒法經過虛引用取得實例對象。其惟一目的是在對象被回收時收到一個系統通知。
垃圾收集器與內存回收策略
GC重點關注Java堆和方法區。
識別無用對象
引用計數
當對象相互引用時,就是兩者均未再被外界引用,計數器均爲1,不會被回收。Java未用此方法。
根搜索
以一系列GC root爲起點搜索引用鏈,未與任何引用鏈相關聯的對象爲可回收。Java及其餘主流商用語言(如C#)採用此方法
GC root對象包括如下幾種:
- 虛擬機棧(棧幀中的本地變量表)中引用的對象
- 方法區中類靜態屬性引用的對象
- 方法區中常量引用的對象
- 本地方法棧的JNI(即Native方法)引用的對象
GC過程
發現不可達到到真正GC,至少要經歷兩次標記過程。
- 發現未與GC Root相連,被第一次標記並進行篩選。對象未覆蓋finalize方法或finalize已被虛擬機調用過,則無必要執行finalize。
- 如有必要執行finalize,將放入F-Queue。稍後由一個虛擬機創建的,低優先級的Finalize線程執行。(不承諾等待其執行完畢)
- 稍後GC對F-Queue進行二次標記,若是對象在finalize函數中從新與引用鏈創建聯繫,則就將在這次標記時移出「即將回收」集合。
方法區(或者HotSpot虛擬機的永久代)的垃圾回收
主要分爲
*. 判斷棄用常量:與回收Java堆的對象相似。
*. 判斷無用類:
- 該類全部實例均被回收,即Java堆中不存在該類的實例。
- 加載該類的ClassLoader已被回收。
- 該類對應的java.lang.Class 未被引用。沒法在任何地方經過反射訪問到該類。
與對象不一樣,被判斷爲可回收後,並不必定被回收,HotSpot中可由-Xnoclassgc控制。-verbose:class, -XX:+TraceClassLoading, -XX:+TraceClassUnloading(具體能否使用有虛擬機版本限制)可查看類的加載和卸載信息。在某些場景下,類卸載是保證永久代不會溢出的關鍵。
垃圾收集算法
- 標記 - 清除(Mark-Sweep):缺點:標記和清除過程效率都不高。空間碎片太多,分配大對象時會提早觸發另外一次GC。多應用於老年代。
- 複製算法:將內存劃分爲大小相同的兩塊,每次只使用一塊,只移動堆頂指針,順序分配。當內存不足時,將存活的對象複製到另外一塊內存。優勢:無需考慮碎片問題,實現簡單,運行高效。缺點:內存縮小一半,對象存活率較高時,複製操做變多,效率變低。基於多數狀況下,98%的新生代對象均爲朝生夕死,商用虛擬機使用此方法回收新生代。實際使用中,將內存劃分爲一塊較大的Eden和兩塊較小的Survivor,當回收時,將Eden和使用的Survivor中的存活對象拷貝到另外一塊Survivor並清空兩者。HotSpot中,兩者大小比爲8:1,即僅10%會被浪費。當可回收的對象大於10%時(沒法徹底被拷貝到一個Survivor),須要依賴老年代進行分配擔保。
- 標記 - 整理:標記後將存活的對象向一端移動,而後清理掉邊界之外的內存。多應用於老年代。
- 分代收集:通常將Java堆劃分爲新生代和老年代。
垃圾收集器
- Serial:使用複製算法,利用一個線程且暫停其餘全部工做線程。JDK 1.3.1以前是虛擬機新生代的惟一選擇。對於單線程環境,其沒有線程交互開銷,專心GC使其簡單而高效。時至今日,它依然是Client模式下運行的虛擬機的新生生代收集器。在用戶的桌面環境,分配給虛擬機的內存通常不大,停頓時間能夠接受。
- ParNew:使用多線程進行回收,其餘與Serial相同。-XX:ParallelGCThreads可限制回收線程數目,默認與CPU數量相同。它是許多Server模式下的虛擬機首選的新生代收集器(由於只有它和Serial能與CMS配合),也是使用-XX:+UseConcMarkSweepGC後的默認新生代收集器,也可以使用-XX:UsePerNewGC強制指定。
- Parallel Scavenge:同2同樣,爲新生代收集,複製算法、並行多線程。其以提升吞吐量而非停頓時間爲目標(吞吐量優先收集器),適合在後臺運算而不須要太多交互。-XX:MaxGCPauseMillis 控制垃圾回收最大停頓時間,-XX:GCTomeRatio 控制設置吞吐量大小(用戶運行時間/GC時間)。-XX:+UseAdaptiveSizePolicy 開啓後無需手動指定 -Xmm 新生代大小, -XX:SurvivorRatio Eden與Survivor比例,-XX:PetenureSizeThreshold 晉升老年代年齡等。
- Serial Old:Serial的老年代版本,使用 標記 - 整理 算法,依然主要用於Client模式。另Server模式下,在JDK 1.5 及以前與Parallel Scavenge 配合使用(Parallel Scavenge架構中有PS MarkSweep進行老年代收集,其以Serial Old 爲模版且很是相近,因此許多常直接用Serial Old進行講解)。同時做爲CMS的後備預案,在併發收集發生Concurrent Mode Failure時使用。
- Parallel Old:JDK 1.6 中釋出,Parallel Scavenge的老年代版本,使用多線程 標記 - 整理算法。
- CMS(Concurrent Mark Sweep / Concurrent Low Pause Collector):HotSpot於JDK 1.5時期推出的老年代收集器,以最短回收時間爲目標,採用 標記 - 清理 算法。其運做過程爲:1. 初始標記,標記GC Roots可達的對象,速度很快,須要暫停其餘工做線程。2. 併發標記,進行GC Roots Tracing的過程,相對耗時較長。3. 從新標記,修正併發標記階段因程序繼續程序運做而致使標記產生的變更,比初始標記稍長,須要暫停其餘工做線程。4. 併發清除。耗時較長的2和4均採用與用戶工做線程併發運做的方式。缺點:對CPU敏感,其啓用(CPU MUBER + 3) / 4個回收線程,當CPU不足4時,有一半被佔用,嚴重影響吞吐量。虛擬機提供 增量式併發收集器(Incremental Concurrency Mark Sweep / i-CMS / Deprecated 不推薦使用)使用單CPU年代的搶佔式模擬多任務機制,使垃圾收集週期增加,從而減少多用戶的影響。沒法處理浮動垃圾。可經過 -XX:CMSInitialingOccupancyFraction設置觸發回收的內存餘量閥值(默認68%)。-XX:+UseCMSCompactAtFullCollection開關用於在GC(標記 - 清除算法)後整理產生的碎片,此灰過程沒法併發,使停頓時間變長。-XX:CMSFullGCsBeforeCompaction可設置屢次GC對應一次整理。
- G1:於6,使用 標記 - 整理 機制。可精確控制停頓,也即在必定時間中GC消耗的時間。其將Java堆(老年代和新生代)劃分爲大小固定的獨立區域,並跟蹤區域的垃圾堆積程度,維護一張優先列表,在GC優先回收高優先級區域。它與Parallel Scanvege未使用傳統GC代碼框架,故沒法與其餘收集器配合工做。
Minor GC指新生代GC,一般小較快。Full GC / Major GC 指老年代GC,一般伴隨Minor GC(Parallel Scavenge就有直接Full GC的策略選擇過程),速度慢10倍以上。
其餘配置:
- UseSerialGC: Client模式下的默認值,使用Serial + Serial Old。
- UseParNewGC:ParNew + Serial Old。
- UseConcMarkSweepGC: 使用ParNew + CMS + Serial Old(做爲預備方案)。
- UseParallelGC:Service模式下的默認值,使用Parellel Scavenge + Serial Old(PS MarkWeep)。
- UseParallelOldGC:Parallel Scavenge + Parallel Old。
- PretenureSizeThreshold:直接晉升老年代的對象大小閥值。避免複製算法中Survivor和Eden間發生大量複製。僅Serial 和 ParNew 有效。
- MaxTenuringThreshold:晉升老年代的年齡。默認15。
- HandlePromotionFailure:是否容許擔保失敗。發生Minor GC時,虛擬機會檢測以前每次晉升的平均對象大小是否大於老年代剩餘空間,若大於則進行Full GC,若小於且此值爲容許則不進行,小於且容許則進行。大多數狀況打開此開關避免頻繁的Full GC。
- PrintGCDetails:輸出GC過程。
- -Xms:堆最小值。
- -Xmx:堆最大值。
- -Xmn:分配給新生代的大小。
- HeapDumpOnOutOfMemorryError:出現內存溢出時Dump出當前內存堆轉儲快照。
- -XX:+DisableExplicitGC:禁用手動觸發GC(System.gc())。
內存分配與回收策略
- 對象優先在Eden分配。
- 大對象直接進入老年代:避免新生代內存間出現大量的大文件拷貝操做。
- 長期存活對象進入老年代。
- 動態對象年齡判斷:若是Suvivor中相同年齡的對象佔用大於一半以上空間,則此年齡及以上的對象直接進入老年代。
虛擬機性能監控與故障處理工具
主要數據來源:運行日誌,異常堆棧,GC日誌,線程快照(threaddump / javacore文件),堆轉儲快照(heapdump / hprof文件)等。
Sun JDK監控和故障處理工具:
- jps:JVM Process Status Tool,顯示指定系統內的全部HotSpot虛擬機進程。
- jstat:JVM Statistics Monitoring Tool,收集HotSpot各方面的運行數據。
- jinfo:Configuration Info for Java,顯示虛擬機配置信息。
- jmap:Memory Map for Java,生成虛擬機的內存轉儲快照(heapdump文件)。
- jhat:JVM Heap Dump Brower,用於分析heapdump文件並創建服務器讓用戶能夠在瀏覽器上查看分析結果。
- jstack:Stack Trace for Java,顯示虛擬機線程快照。
可視化工具:
- JConsole:JDK 1.5時期提供。
- VisualVM:JDK 1.6首發。
調優案例分析及實戰
虛擬機執行子系統
類文件結構
平臺與語言無關性的基石:字節碼存儲格式(Class文件)。
虛擬機類加載機制
將類的描述數據從Class文件加載到內存並進行一系列操做造成可被JVM直接使用的Java類型的過程。
與編譯時鏈接的語言不一樣,Java類型的 加載 和 鏈接 過程是在程序運行期間完成的,使其擁有動態拓展的特性。例如,編寫一個使用接口的應用程序,可待運行時再肯定具體實現。
類加載的時機
過程:加載 - 驗證 - 準備 - 解析 & 初始化 - 使用 - 卸載。
解析和初始化順序不定是爲了支持運行時綁定(動態綁定,晚期綁定)。
驗證 - 準備 - 解析 合稱爲鏈接。各過程可同時進行但開始順序必定。
各過程的時機:
加載:虛擬機規範未強制規定。
初始化:
- new, get static, put static, invoke static四條字節碼指令(static指代類的除final修飾、已在編譯期把結果放入常量池的靜態字段和靜態方法)。
- 使用java.lang.reflect包的方法對類進行反射調用。
- 子類被初始化以前(與類不一樣,對於接口,只有真正被子類使用時纔會初始化,如使用接口定義的常量)。
- 程序執行的主類。
全部引用類的方式,不會觸發初始化,即被動引用。
- 經過子類引用父類的靜態字段。(Sun HotSpot中,經過-XX:+TraceClassLoading 可發現子類被加載)
- 定義類的數組。(此時虛擬機中會使用newarray字節碼指令初始化自動生成的繼承於Object的名爲 「[$類名」 的類。 )
- 訪問類中的常量(因其在編譯期已進入調用類的常量池,未直接引用類)。
類加載過程
- 經過類的 全限定名 獲取 定義此類的二進制字節流。
流來源有不少,如Class文件,ZIP包中獲取(JAR, EAR, WAR文件),網絡中獲取(Applet),運行時計算(動態代理),其餘文件生成(JSP)等。
- 將此二進制流所表明的 靜態存儲結構 轉換爲 方法區的運行時結構。
- 在 Java堆 中生成一個表明此類的 java.lang.Class對象 做爲方法區訪問此類的入口。
虛擬機的一項自我保護工做,工做量佔比較大。對於可信的代碼集,可以使用-Xverify:none關閉大部分的類驗證措施。大體分爲分爲:
- 文件格式驗證:經此驗證後字節流纔會進入內存的方法區進行存儲。
- 元數據校驗:
- 字節碼校驗:最複雜的階段。
- 符號引用驗證:
- 準備:正式爲類變量(被static修飾的變量,不包含實例變量)分配內存並設置類變量初始值(類型的初始值,並不進行賦值,如int初始化爲0,賦值將在初始化階段進行。對於被static final修飾的變量,在編譯時已爲其值生成ConstantValue屬性,從時則會直接使用該值進行準確的賦值)。
- 解析:將常量池中的符號引用(引用目標不必定已被加載到內存)引用轉換爲直接引用(直接指向目標的指針、相對偏移量、能間接定位到目標的句柄)。
對同一個符號引用進行解析時虛擬機可能會對結果進行緩存,在運行時常量池中記錄直接引用,並把常量標記爲已解析。
當子類和父類聲明同名Static字段,編譯器將拒絕編譯。接口均爲public故針對接口的解析通常不會拋出java.lang.IIlegleAccessError。
- 初始化: 執行類構造器<client>()方法。其在編譯期收集全部類變量的賦值語句和靜態代碼塊(static{})中的語句。
類加載器
類加載過程被放置在虛擬機外部,以便應用自行決定如何去獲取類。實現此動做的代碼塊稱爲 類加載器。
- 啓動類加載器(Bootstrap ClassLoader): 加載java_home/lib目錄、被-Xbootclasspath指定的目錄、而且被虛擬機識別的類庫到虛擬機內存。與下面所述的加載器不一樣,其沒法被Java直接引用,對於HotSpot,其使用C++實現。
- 擴展類記載器(Extension ClassLoader): 由sun.misc.Launcher$ExtClassLoader實現,負責加載java_home/lib/ext目錄、被java.ext.dirs系統變量指定的目錄中的類庫。
- 應用程序加載器(Application ClassLoader): 由sun.misc.Launcher$AppClassLoader實現,是ClassLoader.getSystemClassLoader()方法的返回值。
雙親委派模型
JDK1.2中被引入。
按上所述順序造成父子類加載器(不以繼承(Inheritance)關係而以組合(Composition)方式實現來複用毒加載器的代碼),當子類收到類加載請求,則先委派給父加載器去完成(若是類還未被加載的話),當父加載器拋出ClassNotFoundException後,再調用本身的findClass()方法進行加載。其主要代碼集中在java.lang.ClassLoader的loadClass()方法中。此模型使得無論哪一個加載器要加載特定的類,最終均會使用同一加載器完成,即爲同一個類,實現統一性(不一樣加載器加載的同一Class屬於不一樣類)。
被破壞的雙親委派模型:
- 例如JNDI、JDBC、JCE、JAXB、JBI等(代碼由啓動類加載器加載)涉及SPI(Service Provider Interface, 接力提供者)的加載動做, 須要調用獨立廠商實現並部署在ClassPath下的接口提供者代碼(啓動類加載器並不認識)。爲此新增線程上下文類加載器(經過java.lang.Thread.setContextClassLoader()設置,若建立線程時爲設置,則從父加載器繼承一個,若全局範圍均未設置,也默認爲應用類加載器),使父加載器能夠請求子加載器完成加載操做。
- OSGi環境下,爲實現模塊化熱部署,類加載進一步發展爲網狀結構。
虛擬機字節碼執行引擎
每一個方法從調用開始到完成均對應着一個棧幀從入棧到出棧的過程。
編譯程序代碼時,棧幀中須要多大的局部變量和多深的操做數棧都已經肯定。一個棧幀分配多少內存不受運行期變量數據影響。
運行時棧幀結構:
- 局部變量表: 一組變量值存儲空間,以變量槽(Slot)爲最小範圍,單位大小可簡單理解爲32位長度的內存空間。對於64位數據類型,以高位在前爲其分配兩個Slot。虛擬機使用Slot的索引值(0值開頭)使用局部變量表。若是是實例方法(Not Static Method),則局部變量表中的第0位索引的Slot默認是用於傳遞方法所述對象實例的引用,也即this關鍵字所訪問的的參數。Slot可被重用,若字節碼PC計數器已超過某變量運用域,則變量對應的Slot可交由其餘變量使用。不一樣於類變量,局部變量無「準備階段」,即不會存在默認值(Boolean類型默認爲false是不存在的)。
- 操做數棧(操做棧): 其中內容可爲任意Java數據類型。方法開始執行時,棧爲空,當作諸如算法等操做時,字節碼指令會向棧中提取和寫入內容。原則上兩個棧幀相互獨立,但做爲優化,會有部分重合,使方法調用無須額外的參數複製。解釋執行引擎即基於棧的執行引擎,棧即指操做數棧。
如下三項可統稱爲棧幀信息
- 動態鏈接: 各棧幀均包含指向運行時常量池中該幀所屬方法的引用以支持動態連接。
- 方法返回地址: 調用者的PC計數器能夠做爲返回地址。當有返回值時,會將其壓入調用者的操做數棧中。
- 附加信息: 虛擬機容許添加規範中爲涉及的信息,如調試信息。
方法調用
虛擬機編譯不包含鏈接過程,一切方法調用在Class文件中都是符號引用,非實際內存入口,使Java擁有強大的擴展能力,但方法調用更爲複雜。
- 解析: 每一個目標調用方法在Class文件裏都是一個常量池中的符號引用。對於
靜態方法
,私有方法,實例構造器,父類方法
,final修飾的方法五類(invokestatic
,invokespecial
, invokevirtual, invokeinterface字節碼指令中的前兩個所調用的方法及final修飾的方法),在類加載的時候就會把符號引用解析爲直接引用,稱爲解析調用(對應於分派調用,這些方法稱爲非虛方法)。
- 分派:
靜態分派:
static abstract class Human{}
static class Women extends Human{}
static class Man extends Human{}
Human爲靜態類型(外觀類型),Women和Man爲實際類型。虛擬機(編譯器)在重載時經過靜態類型判斷,其在編譯期是可知的。當以以上三個類爲參數不一樣點進行重載時,編譯器會編譯期生成invokevirtual指令,調用Human參數的重載方法。使用靜態類型定位調用方法版本的分派動做稱爲靜態分派。當類線性實現接口時,針對接口變量的重載將成回溯形形式定位調用方法版本,當某處同時實現兩個接口且兩個接口均存在重載方法時會拒絕編譯。此時調用重載方法時須要顯式類型轉換。
動態分派: 當調用Women重寫於Human的方法時,執行的invokevirtual指令會先找到操做數棧第一個元素指向的對象實例類型(也即實際類型)。若是其中找到所需方法則進行權限檢驗(未經過則拋java.lang.IIlegalAccessError),不然按繼承關係向上搜索與驗證。若始終找不到,拋java.lang.AbstractMethodError。invokevirtual指令把常量池中的類方法符號引用解析到不一樣的直接引用上,此過程即爲方法重寫的本質。在運行期根據實際類型肯定方法執行版本的分派過程稱爲動態分派。
father.say(_360)
song.say(qq)
編譯階段,靜態分派使用靜態類型進行定位,最終生成Father.say(_360)和Father.say(qq)兩條invokevirtual指令。其根據兩個宗量(目標方法全部者和方法參數稱爲宗量)進行選擇,故稱爲多分派。
運行階段,肯定兩條指令的確切目標是Father仍是Son,參數將再也不成爲選擇因素,故一個宗量進行選擇稱爲單分派。
動態分派很是頻繁且須要運行時在類的方法元數據中搜索合適的目標方法,出於性能考慮,會有一些優化手段。好比使用虛方法表(virtual method table,接口方法表與此相似)存儲各個方法的實際入口,父子類未重寫的方法入口地址將相同。
基於棧的字節碼解釋執行引擎
基於棧的指令集和基於寄存器的指令集
基於棧的解釋器的執行過程
類加載及執行子系統的案例與實戰
程序編譯與代碼優化
- 前段編譯器: .java轉換成.class的過程,Sun的Javac,Eclipse JDT中的增量式編譯器(ECJ)。javac使用語法糖來改善程序員的編碼風格和效率。
- 運行期編譯器(JIT編譯器): 字節碼轉換成機器碼,HotSpot VM的C1, C2編譯器。優化的主要階段,使非javac編譯的Class文件也能夠享受編譯器優化帶來的好處。
- 靜態提早編譯器(AOT編譯器): .java直接轉換成機器碼,GNU Complier for the Java(GCJ),Excelsior JET。
Javac 編譯器
- 解析與充填符號表: 詞法分析,將源碼中字符流轉換成標記(Token)集合,標記指一個關鍵詞如int。語法分析將根據Token序列構造抽象語法樹(AST)。
- 註解處理器
- 語義分析與字節碼生成: 對AST上正確的源碼進行上下文有關性審查(如類型審查)。標註檢查,數據及控制流分析,解語法糖,字節碼生成。
語法糖
泛型與類型擦出
Java與C#不一樣,使用僞泛型,在編譯生成的字節碼文件中,泛型被替換成了原生類型,並在相應的地方進行了強制類型轉換。如New Map<String, String>其實是New Map,.get的時候強制類型轉換爲String。故使用泛型的Map做爲不一樣之處進行重載時,沒法被編譯。
自動裝箱,拆箱與循環遍歷
==操做在沒有遇到算數運算的狀況下不會拆箱,equal方法不會處理數據轉型關係。
條件編譯
編譯器會將分支中不成立的代碼刪除(如: if False)。其也在解語法糖階段完成。
晚期(運行期)優化
即時編譯器JWT(Just InTime Compiler),無特殊說明均指HotSpot虛擬機的相應模塊。
解釋器和編譯器
解釋器使程序快速啓動和執行,省去編譯時間。隨着時間的推移,愈來愈多代碼被編譯成本地代碼以後,可得到更高的執行效率。
HotSpot內置兩個即時編譯器,Client Complier(C1編譯器)和Server Complier(C2編譯器)。默認採用一個解釋器和其中一個編譯器直接配合使用。具體可設置。
分層編譯: 第0層,程序解釋執行,解釋器不開啓性能監控功能。可觸發第1層編譯。第1層,也稱C1編譯,將字節碼編譯成本地代碼,進行簡單優化,可加入性能監控邏輯。第2層,也稱C2編譯,於2啓動了一些編譯耗時較長的優化。甚至會根據性能監控信息進行一些不可靠的激進優化。
實施分層編譯後,C1與C2同時工做,許多代碼會被屢次編譯,使用C1得到更好的編譯速度,C2得到更好的編譯質量。
編譯對象與觸發條件
判斷一段代碼是否是熱點代碼,需不須要觸發即時編譯,此行爲稱爲熱點探測。包含如下兩種方法:
- 基於採樣: 週期性檢查棧頂。認爲常常出如今棧頂的方法是熱點方法。簡單高效但不精確如遇到線程阻塞時會出現嚴重誤判。
- 基於計數器: 爲每一個方法(甚至是代碼塊)創建計數器,計數超過閥值(-XX:CompileThreshhold, Client 模式下默認值1500,Server 10000)則認爲是熱點。HotSpot採用此方法。調用方法時,檢查是否有JIT編譯後的版本,無也加計數器,若達閥值則提交代碼編譯請求而不等待。調用計數器存在熱度衰減,半衰週期後將計數值減半,此動做是垃圾回收時順便進行的,可以使用-XX:UseCounterDecay關閉,-XX:CounterHalfLifeTime設置半衰週期,單位s。回邊計數器用於統計一個方法中循環代碼的執行次數。在字節碼中遇到控制流向後跳轉的指令就稱爲回邊。一樣可設定,超過閥值提交一個OSR編譯請求,但不存在熱度衰減。
編譯過程
-XX:BackgroundComplilation可禁止後臺編譯。
C1: 第一階段,將平臺獨立的
編譯優化技術
方法內聯
對於虛方法(實例方法默認是虛方法),沒法直接使用方法內聯,優化器會作其餘處理以保證能夠內聯,可加入final修飾已略微提升響應。
Java與C++編譯器對比
也即即時編譯和靜態編譯的對比。
Java內存模型與線程
處理器可能會對輸入的代碼進行亂序執行,最後會將亂序執行的結果重組,保證最終結果一致。
Java虛擬機的即時編譯器中也有相似的指令重排序優化。
C/C++等語言直接是用物理硬件(操做系統的內存模型),所以會因爲不一樣平臺內存模型的差別,在不一樣平臺併發時可能會出現問題。
主內存和工做內存
需虛擬機擁有主內存,各線程擁有本身的工做內存。工做內存中存放的是須要使用的變量的主內存的副本拷貝,其對線程的操做均在工做內存完成,後進行回寫。
volatile型變量
輕量級的同步機制,被其修飾的變量在被線程修改時會當即對其餘線程可見,即修改會當即同步到主內存,讀取是會當即從主內存刷新。此處僅僅是可見,依然會出現讀取數值後對數值進行修改所帶來的併發結果被覆蓋的狀況。如果直接賦值-修改的值不依賴當前值則可達到線程安全。
同時,可禁止指令重排序,即針對此變量的操做,其以前的代碼必定會在此操做以前完成,其以後的也在其以後執行。但不保證其以前和以後的代碼不會發生指令重排序。
典型應用場景爲,使用一個參數辨識系統啓動完成,在啓動後將該參數賦True值,但因爲指令重排序,其有可能不會等到系統啓動完成就被賦值。
其它實現可見性機制: synchronized: 每次unlock以前,必須把變量同步回主內存。final: 一旦在構造器中被初始化,通常狀況下其餘線程就能看到final字段的值。
synchronized基於一個變量同一時刻只能被一個線程lock。
先行發生原則
Java與線程
- 使用內核實現: 程序通常不會直接使用內核線程(Kernel Thread, KLT),而是去使用內核的一個高級接口,輕量級進程(Light Weight Process, LWP)。這種輕量級進程與內核線程1:1的關係稱爲一對一的線程模型。
- 使用用戶線程(User Thread, UT)實現: 廣義上講,非內核線程即爲用戶線程。狹義上,其徹底創建在用戶空間的線程庫上。這種進程與用戶線程之間1:n的關係稱爲一對多的線程模型。
- 混合實現: 用戶線程與輕量級進程的數量比不定,稱爲M:N的線程模型。
- 線程模型基於操做系統的原生線程模型實現。Windows and Linux採用一對一。線程模型。
Java線程調度
協同式( operative Threads-Scheduling)線程調用: 線程自行控制其執行時間。不穩定,容易存在堅持不讓的狀況。
搶佔式(Preemptive ThreadsScheduling) 線程調用: 執行時間由系統分配,Java採用此方式。
Java中的線程優先級是不穩定的,由於其實際是映射到系統的原生線程實現,其有本身的優先級機制。
進程狀態轉換
新建。
運行。
無限期等待: 須要主動喚醒。如沒有設置timeout的Object.wait和Thread.join,LockSupport.park()。
限期等待: 一段時間後自動喚醒,如設置了timeout的Object.wait和Thread.join,LockSupport.parkNanos, LockSupport.parkUntil。
阻塞: 等待某個事件。
結束。
線程安全與鎖
線程安全級別:
- 不可變: 被final修飾變量,在構造函數賦值結束後,在未發生this指針逃逸時此變量將永遠是線程安全的。
- 絕對線程安全: Vector的全部方法均被synchronized修飾,但並不表明併發的讀寫是安全的,只能說併發的讀和併發的寫是安全的。
- 相對線程安全: Vector,HashTable, Collections的synchronizedCollection方法包裝的集合等。
- 線程兼容: ArrayList,HashMap等可在調用端實現線程安全的。
- 線程對立: 沒法在多線程中使用代碼。
線程安全的實現方法
- 互斥同步: synchronized 修飾符(編譯以後會在同步塊的先後調用moniterenter和monitorexit兩個字節碼指令已鎖定和解鎖制定的對象,不指定則有默認規則)等互斥機制,如臨界區,互斥量,信號量。因爲Java線程是映射到系統的原生線程上,阻塞和喚醒一條線程均須要操做系統從用戶太轉換爲核心態,其須要大量的處理器時間。因此對於代碼簡單的同步塊,如被synchronized的setter和getter,其轉換時間可能比代碼執行時間長,故synchronized 的是一個重量級操做,無非必要無需使用。java.util.concurrent.ReentrantLock於其: 等待中斷,即等過久可選擇放棄而執行任務。可選擇性開啓公平鎖,多個等候時按時間順序得到鎖。綁定多個條件。出於性能考慮,兩者優先推薦synchronized。互斥同步可認爲是悲觀鎖。
- 非阻塞同步: 樂觀鎖機制,java.util.current.AtomicInteger等原子類,其會先進行操做,若最後發現有資源爭用,產生了衝突,則進行補救,如再次嘗試直至成功。
- 無同步方案: 可重入性,若是一個方法,其結果時可預測的,輸入同一個值均能獲得相同結果。可重入的代碼均是線程安全的,反之不成立。線程本地存儲,若是一個變量是線程共享的,可以使用volatile修飾。線程獨佔數據可以使用java.lang.ThreadLocal存儲。
鎖優化
-
自旋鎖與自適應自旋
- 線程請求鎖後,執行一個忙循環(自旋),當鎖佔用時間很短時,請求鎖的線程無需進行阻塞/喚醒,效率有很大提升。當自旋次數超過默認值10時,則進入阻塞。自適應循環則是動態決定自旋次數甚至不進行自旋
-
鎖消除
- 對於編譯器和程序員添加的鎖,若在即時編譯時被認爲是針對線程私有,不存在逃逸的變量或代碼塊加鎖,則會被消除。
-
鎖粗化
- 若是虛擬機探測到一串零碎的操做都對同一對象加鎖,則會把加鎖單位擴大到整個操做序列的外部。
-
輕量級鎖