第一部分 走進Java
1、走進Java
一、概述html
java普遍應用於嵌入式系統、移動終端、企業服務器、大型機等各類場合,擺脫了硬件平臺的束縛,實現了「一次編寫,處處運行」的理想java
二、java技術體系結構程序員
按照功能來劃分算法
- 包括如下幾個組成部分:Java程序設計語言,各類硬件平臺的java虛擬機,Java API類庫,來自商業機構和開源社區的第三方Java類庫,Class文件格式
- Java程序設計語言,java虛擬機,Java API類庫統稱爲JDK,是用於支持java程序開發的最小環境
- Java API類庫中的Java SE API子集和Java虛擬機統稱爲JRE,是支持java程序運行的基本環境
按照技術所服務的領域劃分分爲4個平臺數據庫
- Java Card:支持java小程序運行在java小內存設備(如智能卡)上的平臺
- Java ME:支持Java程序運行在移動設備上的平臺
- Java SE:支持面向桌面級應用的平臺
- Java EE:支持使用多層架構的企業級應用的平臺
第二部分 自動內存管理機制
2、內存區域和內存溢出異常
一、運行時數據區
程序計數器編程
- 記錄的是正在執行的虛擬機字節碼指令的地址,能夠當作是當前線程所執行的字節碼的行號指示器,每一個線程都有一個獨立的程序計數器,各條線程的程序計數器互不影響,獨立存儲,這類內存區域成爲「線程私有的內存」。
- 此內存區域是惟一在虛擬機規範中沒有OutOfMemoryError的狀況的區域
Java虛擬機棧小程序
- 同程序計數器同樣,也是線程私有的。每一個方法在執行的時候都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。
- 每個方法從調用直至執行完成的過程,都對應着一個棧幀在虛擬機棧中入棧和出棧的過程。
- 局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在棧幀中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。
- 若是請求的棧深度超過虛擬機鎖容許的深度,將拋出StackOverFlowError異常。若是拓展沒法申請到足夠的內存,將拋出OutOfMemoryError異常。
本地方法棧緩存
- 爲虛擬機使用的native方法服務,和虛擬機棧同樣,本地方法棧也會拋出StackOverFlowError和OutOfMemoryError異常。
Java堆tomcat
- Java堆是全部線程共享的一塊內存區域,用來存放對象實例,幾乎全部的對象實例都在這裏分配。
- Java堆是垃圾回收的主要區域,採用分代收集算法。
- Java堆分爲新生代和老年代,新生代在細緻一點分爲Eden,From Survivor,To Survivor空間。
- 若是堆中沒法完成對象實例的內存分配,且堆也沒法擴展時,將拋出OutOfMemoryError異常。
方法區安全
- 是各個線程共享的內存區域,用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,HotSpot虛擬機的設計團隊把GC分代收集擴展至方法區,或者說使用永久代來代替方法區。
- 在目前已經發布的JDK1.7的HotSpot中,已經把本來放在永久代的字符串常量池移出了。當方法區沒法知足內存的分配需求時,將拋出OutOfMemoryError異常。
運行時常量池
- 是方法區的一部分,Class文件除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用於存放編譯器生成的各類字面量和符號引用,這部份內容將在類加載後進入方法區的運行時常量池存放。
- 運行時常量池相對於Class文件常量池,具備動態性,運行期間也能夠將新的常量放入常量池,好比String類的intern()方法。
- 當運行時常量池沒法申請到更多的內存時,將會拋出OutOfMemoryError異常。
直接內存
- 並非運行時區域的一部分,JDK 1.4加入的NIO 它可使用Native函數庫直接分配堆外內存,而後經過Java堆中的DirectByteBuffer對象做爲這塊內存的引用進行操做。
二、HotSpot虛擬機對象探祕
對象的建立
- 當遇到new指令時,先判斷這個類是否被加載、解析、初始化過,若是沒有,先執行相應類的加載過程。
- 類加載檢查經過後,爲新生對象分配內存,若是Java堆內存是規整連續的,採用「指針碰撞」的分配方式,若是是不連續規整的,採用「空閒列表」分配方式。內存是否規整取決於垃圾收集器是否帶有壓縮整理功能。
- Serial,ParNew等帶有Compact過程的收集器,採用的分配算法是「指針碰撞」。而CMS這種基於Mark-Sweep算法的收集器,一般採用「空閒列表」分配方式。
- 建立對象涉及到分配內存和指針指向兩個操做,不是原子性的,不是線程安全的。針對這個問題,有兩個解決辦法:1是採用CAS加上失敗重試來保證操做的原子性。2是採用TLAB(Thread Local Allocation Buffer)策略,在Java堆中預先爲每個線程分配一小塊內存,稱爲TLAB(Thread Local Allocation Buffer),哪一個線程要分配內存就在各自的TLAB上進行內存的分配,只有TLAB用完進行新的TLAB的分配時才須要同步鎖定,虛擬機是否使用TLAB,能夠經過 -XX:+/- UseTLAB
- 內存分配完成後,須要對對象頭進行設置,包括這個對象是哪一個類的實例、如何才能找到類的元數據信息、對象的哈希碼、對象的GC分代年齡等信息。
- 最後執行init方法,把對象按照程序員的意願進行初始化。這樣一個真正可用的對象纔算徹底生產出來。
對象的內存佈局
- 分爲三塊區域,對象頭(Header)、實例數據(Instance Data)、對齊補充(Padding)。
- 對象頭,存儲對象自身的運行時數據,如哈希碼、對象的GC分代年齡、鎖狀態標誌、偏向線程ID、偏向時間戳,這部分數據的長度在32位和64位虛擬機中分別爲32bit和64bit。
- 另外一個部分是類型指針,虛擬機經過這個對象來肯定這個對象是哪一個類的實例。
對象的訪問定位
- Java程序須要經過棧上的reference數據來操做堆中的具體對象,具體實現有兩種方式:使用句柄和直接指針兩種。
- 使用句柄:Java堆中劃分出一塊內存做爲句柄池,reference中存儲的就是對象的句柄地址,句柄中包括了對象的實例數據和類型數據各自的地址信息。最大好處是當對象修改時,reference自己不須要修改,由於reference中存儲的是穩定的句柄地址

- 直接指針:reference中存儲的直接就是堆中的對象地址,堆對象的佈局中須要考慮如何放置訪問類型數據的相關信息。最大好處是速度更快,節省了一次指針定位的開銷,HotSpot就採用的直接指針方式。

三、OutOfMemoryError異常
堆溢出
- 不斷建立對象,保證GC Roots到對象之間有可達路徑來避免垃圾回收機制清除這些對象,達到最大堆的容量限制後就會產生內存溢出異常。
- -Xms20m 堆的最小值;-Xmx20m 堆的最大值;-XX:+HeapDumpOnOutOfMemoryError 內存溢出異常時Dump出當前的內存堆轉儲快照以便往後分析
虛擬機棧和本地方法棧溢出
方法區和運行常量池溢出
- 屢次調用String.intern()方法能夠產生內存溢出異常。JDK 1.6之間,能夠經過 -XX:PermSize 和 -XX:MaxPermSize 限制永久代大小,從而達到限制方法區大小的目的
本地直接內存溢出
- 能夠經過 -XX:MaxDirectMemorySize 指定。若是不指定,則默認和Java堆最大值(-Xmx 指定)同樣
3、垃圾收集器和內存分配策略
一、對象已死嗎?如何肯定對象是否還「活着」
引用計數器方法
- 給對象添加一個引用計數器,每當有一個地方引用它時,計數器就加1,當引用失效時,計數器就減1。
- 優勢是斷定簡單,效率也很高。缺點是沒法解決相互循環引用的問題
可達性分析方法
- 經過一系列的稱爲「GC Roots」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲「引用鏈」,當一個對象到GC Roots沒有任何引用鏈相連時,說明這個對象是可回收的。
- Java語言中,可做爲GC Roots的對象包括如下幾種:虛擬機棧中引用的對象,方法區中類靜態屬性引用的對象,方法區中常量引用的對象,本地方法棧中JNI引用的對象。
再談引用
- JDK1.2 以後把引用分爲了四種:強引用、軟引用、弱引用、虛引用
- 強引用:只要強引用還存在,就不會被垃圾回收器回收。相似 Object o=new Object()
- 軟引用:指一些有用但並不是必須的對象,在系統將要發生內存溢出的時候,會將這部分對象回收。SoftReference 類來實現軟引用
- 弱引用:被弱引用關聯的對象只能生存到下一次垃圾回收。WeakReference 類來實現弱引用
- 虛引用:一個對象是否有虛引用的存在,徹底不會對其生存時間造車影響,也沒法經過虛引用取得對象的引用。一個對象設置虛引用的惟一目的是在被垃圾回收的時候收到一個系統通知
對象被回收的過程
- 當對象進行可達性分析沒有與GC Roots相連的引用鏈,將會被第一次標記,並根據是否須要執行finalize()方法進行一次篩選,對象沒有重寫finalize()或者虛擬機已經調用過finalize(),都被視爲不須要執行
- 若是對象有必要執行finalize,會被放入到F-Queue隊列中,並在稍後由虛擬機自動建立的低優先級的Finalizer線程去觸發它,並不保證等待此方法執行結束。
- 若是對象在finalize()方法執行中,從新和GC Roots產生了引用鏈,則能夠逃脫這次被回收的命運,但finalize()方法只能運行一次,因此並不能經過此方法逃脫下一次被回收
- 筆者不建議使用這個方法,建議你們徹底忘掉這個方法的存在。
回收方法區
- 主要包括廢棄常量和無用類的回收。判斷類無用:類的實例都被回收,類的ClassLoader被回收,類的Java.Lang.Class對象沒有在任何地方引用。知足這三個條件,類才能夠被回收(卸載)
- HotSpot虛擬機經過 -Xnoclassgc 參數進行控制是否啓用類卸載功能。在大量使用反射、動態代理、CGLib等框架,須要虛擬機具有類卸載功能,避免方法區發生內存溢出
二、垃圾回收算法
標記-清除
- 先標記出全部要回收的對象,在標記完成後統一進行對象的回收。有兩個不足:
1 是效率問題,標記和清除的效率都不高。
2 是空間問題,會產生大量不連續的內存碎片,碎片太多會都致使大對象沒法找到足夠的內存,從提早觸發垃圾回收。
複製算法
- 新生代分爲一個Eden,兩個Survival空間,默認比例是8:1。回收時,將Eden和一個Survival的存活對象所有放入到另外一個Survival空間中,最後清理掉剛剛的Eden和Survival空間
- 當Survival空間不夠時,由老年代進行內存分配擔保
標記-整理
- 根據老年代對象的特色,先標記存活對象,將存活對象移動到一端,而後直接清理掉端邊界之外的對象
分代收集
- 新生代採用複製算法,老年代採用標記-刪除,或者標記-整理算法。
三、HotSpot算法實現
枚舉根節點實現
- 可達性分析時會進行GC停頓,停頓全部的Java線程。
- HotSpot進行的是準確式GC,當系統停頓下來後,虛擬機有辦法得知哪些地方存在着對象引用,HotSpot中使用一組稱爲OopMap的數據結構來達到這個目的
安全點
- HotSpot沒有爲每一個指令都生成OopMap,只在特定的位置記錄這些信息,這些位置稱爲安全點。安全點的選定不能太少,也不能太頻繁,安全點的選定以「是否讓程序長時間執行」爲標準
- 採用主動式中斷的方式讓全部線程都跑到最近的安全點上停頓下來。設置一個標誌,各個程序執行的時候輪詢這個標誌,發現中斷標誌爲真時本身就中斷掛起
安全區域
四、垃圾收集器
若是兩個收集器之間有連線,說明能夠搭配使用。沒有最好的收集器,也沒有萬能的收集器,只有對應具體應用最合適的收集器。

Serial 收集器
- 新生代收集器,單線程回收。優勢在於,簡單而高效,對於運行在Client模式下的虛擬機來講是一個很好的選擇(好比用戶的桌面應用)
- 參數 -XX:UseSerialGC,打開此開關後,使用Serial+Serial Old的收集器組合進行內存回收
ParNew收集器
- 新生代收集器,Serial的多線程版本,除了Serial收集器以外,只有它能與CMS收集器配合工做。
- -XX:+UseConcMarkSweepGC 選項後默認的新生代收集器,也可使用 -XX:+UseParNewGC 選項來強制指定它
- ParNew收集器在單CPU的環境中,效果不如Serial好,隨着CPU的增長,對於GC時系統資源的利用仍是頗有效的。
- 默認開啓的收集線程數和CPU數相等,可使用 -XX:ParallelGCThreads 指定
Parallel Scavenge收集器
- 新生代收集器,並行收集器,複製算法,和其餘收集器不一樣,關注點的是吞吐量(垃圾回收時間佔總時間的比例)。提供了兩個參數用於控制吞吐量。
- -XX:MaxGCPauseMillis,最大垃圾收集停頓時間,減小GC的停頓時間是以犧牲吞吐量和新生代空間來換取的,不是設置的越小越好
- -XX:GCTimeRatio,設置吞吐量大小,值是大於0小於100的範圍,至關於吞吐量的倒數,好比設置成99,吞吐量就爲1/(1+99)=1%。
- -XX:UseAdaptiveSizePolicy ,這是一個開關參數,打開以後,就不須要設置新生代大小(-Xmn)、Eden和Survival的比例(-XX:SurvivalRatio)、 晉升老年代對象年齡(-XX:PretenureSizeThreshold)等細節參數,收集器會自動調節這些參數。
Serial Old 收集器
- 單線程收集器,老年代,主要意義是在Client模式下的虛擬機使用。在Server端,用於在JDK1.5以及以前版本和Parallel Scavenge配合使用,或者做爲CMS的後備預案。
Palallel Old 收集器
- 是Parallel Scavenge的老年代版本。在注重吞吐量的場合,均可以優先考慮Parallel Scavenge 和Palallel Old 配合使用
CMS 收集器
- Concurrent Mark Sweep,是一種以獲取最短回收停頓時間爲目標的收集器,尤爲重視服務的響應速度。基於標記-清除算法實現。
- 分爲四個步驟進行垃圾回收:初始標記,併發標記,從新標記,併發清除。只有初始標記和從新標記須要停頓。
- 初始標記只是標記一下GC Roots能直接關聯到的對象,速度很快。併發標記就是進行GC Roots的Tracing。
- 從新標記爲了修正併發標記期間因用戶程序繼續運做而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間會比初始標記階段稍長,遠比並發時間短。
- 耗時最長的併發標記和併發清除過程當中,處理器能夠與用戶線程一塊兒工做。
- 它並非完美的,有以下三個比較明顯的缺點:
一、垃圾回收時會佔用一部分線程,致使系統變慢,總吞吐量會下降。
二、沒法處理浮動垃圾,須要預留足夠的內存空間給用戶線程使用,能夠經過 -XX:CMSInitiatingOccupancyFraction 參數控制觸發垃圾回收的閾值。
若是預留的內存沒法知足程序須要,就會出現「Concurrent Mode Failure」失敗,這時將啓動應急預案,啓用Serial Old 進行垃圾回收,停頓時間會變長
因此-XX:CMSInitiatingOccupancyFraction 參數的值設置的過高,會致使頻繁「Concurrent Mode Failure」失敗,性能反而下降。
三、標記-清理,容易產生內存碎片。-XX:+UseCMSCompactAtFullColletion 開啓碎片整理功能,默認開啓,-XX:CMSFullGCsBeforeCompaction,控制多少次不壓縮的FullGC以後來一次帶壓縮的
G1 收集器
- 包括新生代和老年代的垃圾回收。和其餘收集器相比的優勢:並行和併發,分代收集,標記-整理,可預測的停頓。垃圾回收分爲如下幾個步驟:
- 初始標記:標記GC Roots可以直接關聯到的對象,這階段須要停頓線程,時間很短
- 併發標記:進行可達性分析,這階段耗時較長,可與用戶程序併發執行
- 最終標記:修正發生變化的記錄,須要停頓線程,可是可並行執行
- 篩選回收:對各個Region的回收價值和成本進行排序,根據用戶所指望的停頓時間來執行回收計劃
五、內存分配和回收策略
- 對象優先在Eden分配,當新生區沒有足夠的內存是,經過分配擔保機制提早轉移到老年代中去
- 大對象直接進入老年代。大對象是指須要大量連續內存空間的對象,虛擬機提供了參數 -XX:PretenureSizeThreshold(只對Serial,PerNew兩個回收器起效),令大於這個值得對象直接在老年代分配,避免了Eden和兩個Survival之間發生大量的內存複製。
- 長期存活的對象將進入老年代。虛擬機給每一個對象定義了對象年齡計數器(Age),若是對象在Eden出生,通過第一次Minor GC後依然存活,而且能被Survival容納的話,將被移動到Survival,對象年齡設爲1。對象在Survival中每熬過一次Major GC,年齡就增長1,達到必定程度(默認是15),就會被晉升到老年代。對象晉升老年代的閾值,能夠經過參數-XX:MaxTenuringThreShold 指定
- 動態對象年齡判斷。若是在Survival空間中相同年齡全部對象的大小綜合超過了Survival空間的一半,年齡大於等於這個年齡的對象都會被晉升到老年代。無需等待年齡超過MaxTenuringThreShold指定的年齡
- 空間分配擔保。只要老年代的連續空間大於新生代對象總和或者歷次晉升的平均大小,就進行Major GC,不然進行Full GC。
4、虛擬機性能監控與故障處理工具
一、jps
命令用法: jps [options] [hostid]
功能描述: jps是用於查看有權訪問的hotspot虛擬機的進程. 當未指定hostid時,默認查看本機jvm進程
經常使用參數:-lmvV
詳細說明:JAVA JPS 命令詳解
二、jstat。監視JVM內存工具。
語法結構:
Usage: jstat -help|-options
jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]
例子: jstat -gcutil 25444 1000 5
詳細說明:JDK之jstat的用法
三、jinfo。查看和修改JVM運行參數
java -XX:+PrintFlagsFinal -version|grep manageable 【查看JVM中哪些參數能夠被jinfo動態修改】
jinfo -flag +PrintGCDetails 105704 【修改參數 PrintGCDetails 的值】
四、jmap。命令用於生成heap dump文件
若是不使用這個命令,還可使用-XX:+HeapDumpOnOutOfMemoryError參數來讓虛擬機出現OOM的時候自動生成dump文件。
jmap不只能生成dump文件,還能夠查詢finalize執行隊列、Java堆和永久代的詳細信息,如當前使用率、當前使用的是哪一種收集器等。
詳細使用:JVM調優命令-jmap
五、jstack。Java堆棧跟蹤工具
詳細使用:使用jstack精確找到異常代碼,jstack 工具使用,性能調優
注意:dead lock問題,佔用cpu時間最多的線程,頻繁GC
入手點總結:
wait on monitor entry: 被阻塞的,確定有問題,等待synchronized鎖
runnable : 注意IO線程,IO阻塞的線程
in Object.wait(): 注意非線程池等待,調用Object.wait()的對象
5、常見JVM配置說明
一、JVM配置
1.一、G1
CPU 核數:8;內存(GB):16;磁盤(GB):400
-Xms10g
-Xmx10g
-Xss512k
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
-XX:MaxDirectMemorySize=128M
-XX:+UseG1GC
-XX:MaxGCPauseMillis=150
-XX:+ParallelRefProcEnabled
-XX:+UnlockExperimentalVMOptions
-XX:G1MaxNewSizePercent=70
-XX:ParallelGCThreads=8
-XX:ConcGCThreads=2
1.二、CMS+ParNew
CPU 核數:4;內存(GB):8;磁盤(GB):200
-Xmx4g
-Xms4g
-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=256m
-XX:SurvivorRatio=8
-XX:NewRatio=4
-XX:+HeapDumpOnOutOfMemoryError
-XX:+DisableExplicitGC
-XX:+PrintGCDetails
-XX:+UseConcMarkSweepGC
-XX:ParallelGCThreads=4
-XX:+CMSClassUnloadingEnabled
-XX:CMSFullGCsBeforeCompaction=1
-XX:CMSInitiatingOccupancyFraction=72
1.三、參數說明
通用參數
-Xms10g -Xmx10g |
-Xms10g , 初始化堆大小。一般狀況和-Xmx大小設置同樣,避免虛擬機頻繁自動計算後調整堆大小。 -Xmx10g ,最大堆大小。 |
-XX:MaxDirectMemorySize=128M |
本地直接內存大小。適合頻繁的IO操做,例如網絡併發場景(NIO) 直接內存和堆內存比較:
|
-XX:+PrintGCApplicationStoppedTime |
打印垃圾回收期間程序暫停的時間 |
-XX:+PrintGC |
打印GC基本日誌 |
-XX:+PrintGCDetails |
打印GC詳細日誌 |
-XX:+DisableExplicitGC |
關閉System.gc() |
-Xss512k |
每一個線程堆棧大小。每一次方法的調用都對應一個入棧和出棧 |
-XX:PermSize=256m -XX:MaxPermSize=256m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m |
JDK8之前:
JDK8及之後:
|
-XX:+HeapDumpOnOutOfMemoryError |
JVM異常自動生成堆轉儲 |
CMS
-XX:+UserConcMarkSweepGC |
年老代指定CMS垃圾回收器,新生代默認用ParNew收集 |
-XX:ParallelGCThreads=4 |
設置垃圾收集線程數 |
-XX:CMSInitiatingOccupancyFraction=72 |
老年代垃圾佔比達到這個閾值開始CMS收集,設置太高容易致使併發收集失敗,會出現SerialOld收集的狀況 |
-XX:+UseCMSCompactAtFullCollection(默認開啓,不須要設置) |
在FULL GC的時候, 對年老代開啓碎片整理功能,默認開啓 |
-XX:CMSFullGCsBeforeCompaction=1 |
控制FullGC壓縮的間隔。多少次不壓縮的FullGC以後來一次帶壓縮的 |
-XX:+CMSClassUnloadingEnabled |
CMS收集器默認不會對永久代進行垃圾回收。若是但願對永久代進行垃圾回收,能夠設置標誌 |
-XX:+CMSParallelRemarkEnabled |
爲了減小第二次暫停的時間,開啓並行remark,下降標記停頓 |
-XX:NewRatio=4 |
新生代:老年代=1:4 |
-XX:SurvivorRatio=8 |
2個survivor和eden的比值。表示2:8 默認爲8,也就是說Eden佔新生代的8/10,From倖存區和To倖存區各佔新生代的1/10 |
G1
-XX:+UseG1GC |
使用G1垃圾回收 |
-XX:MaxGCPauseMillis=150 |
GC最大暫停時間 |
-XX:+ParallelRefProcEnabled |
打開並行引用處理 |
-XX:+UnlockExperimentalVMOptions |
有些時候當設置一個特定的JVM參數時,JVM會在輸出「Unrecognized VM option」後終止。若是參數輸入是正確的,而且JVM並不識別,須要設置-XX:+UnlockExperimentalVMOptions 來解鎖參數。 |
-XX:G1NewSizePercent |
新生代最小值比例。默認5% |
-XX:G1MaxNewSizePercent=70 |
新生代最大值比例 |
-XX:ParallelGCThreads=8 |
STW期間,並行GC線程數 |
-XX:ConcGCThreads=2 |
併發標記階段,並行執行的線程數 |
6、JVM調優案例分析與實踐
一、Minor GC、Major GC和Full GC之間的區別
- 每次 Minor GC 會清理年輕代的內存
- Major GC 是清理老年代,Full GC 是清理整個堆空間—包括年輕代和老年代。大部分時候Major GC和Full GC區分的不是很明顯
- 不只僅Major GC和Full GC會stop-the-world。全部的 Minor GC 都會觸發「全世界的暫停(stop-the-world)」,中止應用程序的線程。對於大部分應用程序,停頓致使的延遲都是能夠忽略不計的。其中的真相就 是,大部分 Eden 區中的對象都能被認爲是垃圾,永遠也不會被複制到 Survivor 區或者老年代空間。若是正好相反,Eden 區大部分新生對象不符合 GC 條件,Minor GC 執行時暫停的時間將會長不少。
二、經常使用命令
查看java進程號
兩種方式均可以查看tomcat進程號
ps -ef | grep java
jps -lmvV |grep java
結果以下:2556
查看進程內線程狀況
top -Hp 2556(2556爲上一步查詢出來的進程號)
找到佔用cpu時間最長的線程號:3345
獲得線程號的十六進制數
printf "%x\n" 3345(輸出爲a05)
使用jstack定位問題
jstack 2556 | grep a05
查看內存和swap使用狀況。參考:https://www.cnblogs.com/coldplayerest/archive/2010/02/20/1669949.html
free -h
三、問題排查
內存和SWAP問題
7、虛擬機類加載機制
一、虛擬機把表示類的class文件加載到內存,通過校驗、轉換解析、初始化,最終造成能夠被虛擬機直接使用的java類型,這就是虛擬機的類加載機制
二、類加載的時機
- 使用new關鍵字實例化對象的時候、讀取一個類的靜態字段的時候、調用類的靜態方法的時候
- 使用java.lang.reflect包的方式對類進行反射調用的時候
- 初始化類,發現其父類還未初始化,須要對父類進行初始化
三、類加載的過程
- 加載。經過類的全限定名獲取到定義此類的二進制字節流。將字節流所表明的靜態存儲結構轉化成方法區的運行時數據結構。在方法區生成這個類的java.lang.Class對象。加載階段和鏈接階段的部份內容是交叉進行的。用戶能夠經過本身寫的類加載器去控制字節流的獲取方式(重寫類加載器的loadClass()方法),
- 驗證。是鏈接階段的第一步。目的是確保class文件中的二進制字節流符合虛擬機的要求,不會危及虛擬機自身安全。包括文件格式驗證、元數據驗證、字節碼驗證
- 準備。是鏈接階段的第二步。是正式爲類變量分配內存空間和設置初始值的階段。這個初始值和初始化階段的賦值不一樣,這裏指的是變量的默認初始值。另外,若是時final修飾的變量,那麼會在準備階段賦予代碼裏指定的初始值
- 解析。是鏈接階段的第三步。是虛擬機將符號引用替換爲直接引用的過程
- 初始化。根據程序代碼去初始化類變量和其餘資源
四、類加載器
- 被不一樣類加載器加載的同名類,也認爲是不一樣的類。
- 雙親委派模型。分爲兩種類加載器: 1 是啓動類加載器 ,是虛擬機自身的一部分;2 是全部的其餘類加載器,這些類加載器都由java語言實現。獨立於虛擬機外部,所有繼承自java.lang.ClassLoader抽象類。類加載器具體層次關係:啓動類加載器->擴展類加載器->系統類加載器->自定義類加載器。每個類的加載,會優先由父加載器來加載。這種方式就稱爲雙親委派,雙親委派保證了java基本類的不會被破壞和替代
第五部分 高效併發
12、Java內存模型與線程
一、硬件的效率與一致性
- 完成計算任務,處理器必須和內存交互才能完成,好比讀取運算數據,寫入計算結果等。這個I/O操做是很難消除的。計算的處理器和存儲設備的運算速度有幾個數量級的差距。因此現代計算機加入了一層讀寫速度儘量接近處理器的高速緩存
- 高速緩存解決了處理器和內存的速度矛盾,卻引入了新的問題:內存一致性。多處理器系統中,各個處理器都有本身的高速緩存,又同時共用內存。爲了解決這一問題,在讀寫內存時須要遵循緩存一致性協議。
- 處理器會對輸入的代碼進行亂序執行優化,相似的,Java虛擬機也存在着指令重排序優化。
二、Java內存模型
Java內存模型規定,全部的變量(這個變量和java編程中的變量有區別,它包括了實例字段、靜態字段。不包括局部變量和方法參數,由於後者是線程私有的)都存儲在主內存,每條線程有本身的工做內存,工做內存中保存了該線程使用到的變量的拷貝副本,線程對變量的全部操做都必須在工做內存中進行,線程間變量值得傳遞需經過主內存來完成
主內存和工做內存間交互協議,8種原子操做:
- lock(鎖定主內存)
- unlock(解鎖主內存)
- read(讀取主內存,爲load準備)
- load(載入主內存至工做內存)
- use(執行引擎使用工做內存)
- assign(接受執行引擎計算後的值賦值給工做內存)
- store(存儲工做內存至主內存,爲write準備)
- write(把工做內存寫入主內存)
volatile是java虛擬機提供的輕量級的同步機制,對於volatile變量的特殊規則:
- 保證了變量對全部線程的可見性,當一個線程修改了這個變量的值,修改後的值對其餘線程來講是當即可見的。普通變量,須要經過把新值會寫到主內存,其餘線程從主內存讀取以後才能夠看到最新值
- 禁止指令重排序優化。
- 沒法保證符合操做的原子性,好比i++
- 經過內存屏障實現的可見性和禁止重排序。不一樣硬件實現內存屏障的方式不一樣,Java內存模型屏蔽了這些差別,由JVM來爲不一樣的平臺生成相應的機器碼來完成。X86 處理器只會對寫-讀進行指令重排序,寫volatile變量時,會加lock總線鎖,將cpu緩存寫入主存,其餘cpu的讀都會被阻塞,而後其餘核的緩存某些對應數據會被標記爲失效,那麼其餘核下次讀的時候先讀緩存發現失效了,而後去主存讀
關於long和double類型變量的特殊規則:容許虛擬機將沒有被volatile變量修飾的64位數據的讀寫操做劃分爲兩次32位的操做來進行。這點就是long和double的非原子性協定
三、Java與線程
Java虛擬機實現線程,有三種方式:
(1)經過內核線程實現。jvm中的一個線程對應一個輕量級進程,一個輕量級進程對應一個內核線程。CPU經過調度器對線程進行調度。缺點:
- 因爲基於內核線程實現,各類線程操做須要系統調用,系統調用代價較高,須要在用戶態和內核態之間來回切換
- 每一個線程都須要一個內核線程的支持,所以輕量級進程會消耗內核資源,一個系統支持的輕量級進程是有限的

(2)使用用戶線程實現。不須要切換回內核態,也能夠支持規模更大的線程數量。部分高性能數據庫的多線程就是使用用戶線程實現的。缺點是沒有系統內核的支援,全部問題須要本身考慮,程序實現比較複雜
(3)內核線程和用戶線程結合

(4)JVM,對於Sun JDK來講,在Windows和LInux系統下,都是使用的一對一的線程模型實現的。
Java線程調度
- 協同式線程調度。線程的執行時間由本身控制,線程執行完畢,會主動通知系統
- java使用的是搶佔式調度。每一個線程有系統分配執行時間,線程的切換也有系統來決定,線程的執行時間是可控的。線程能夠設置優先級,來爭取更多的執行時間。Java一共設置了10個優先級,操做系統的優先級數量可能和java定義的不一致,另外操做系統還能夠更改線程的優先級,因此Java中優先級高的線程並不必定被優先執行。
Java線程狀態轉換



十3、線程安全與鎖優化
高效併發是從jdk1.5 到jdk1.6的一個重要改進,HotSpot虛擬機開發團隊耗費了大量的精力去實現鎖優化技術
- 自旋鎖與自適應自旋。同步互斥對性能最大的影響就是線程掛起、恢復須要從用戶態切換到內核態,切換的過程會形成系統消耗。每每鎖定的代碼段執行時間很是短,爲了這個短的時間去掛起和恢復是不值得的。因此提出了自旋鎖的概念,當線程申請獲取一個其餘線程佔用的鎖時,這個線程不會當即掛起,而是經過必定次數的循環自旋,這個過程不會釋放cpu的控制權,自適應自旋就是根據上一次自旋的結果來決定這一次自旋的次數
- 鎖消除。虛擬機即時編譯器在運行時會把檢測到不可能發生共享數據競爭的鎖消除
- 鎖粗化。一系列的操做都是對同一個對象的加鎖和解鎖,虛擬機檢測到這種狀況會將鎖的範圍擴大(粗化)
- 輕量級鎖
- 偏向鎖。若是程序中大多數的鎖老是被多個線程訪問,那偏向鎖模式就是多餘的。可使用參數 -XX:-UseBiasedLocking來禁止偏向鎖