JVM規範: Class文件格式 數字的內部表示和存儲 -Byte -128 to 127 returnAddress數據類型定義: -指向操做碼的指針。不對應java數據類型,不能再運行時修改。 定義PC 堆 棧 方法區 JVM運行機制: JVM啓動流程: 如圖: JVM基本結構: 如圖: PC寄存器: - 每一個線程擁有一個PC寄存器 - 在線程建立時 建立 - 指向嚇一跳指令的地址 - 執行本地方法時,PC的值爲undefined 方法區: - 保存類的源信息 -類的常量池 -字段、方法的信息 -方法字節碼 - 一般和永久去(Perm)關聯再一塊兒 Java堆: - 和程序開發密切相關 - 應用系統對象都保存在java堆中 - 全部的線程共享java堆 - 對分代GC來講,堆也是分代的 - GC的主要工做空間 ----------------------------------------- | | | | | | eden | s0 | s1 | tenured | | | | | ----------------------------------------- 複製算法 java棧: - 線程私有的 - 棧由一系列的幀組成(所以java棧也叫做幀棧) - 棧保存一個方法的局部變量、操做數棧、常量池指針 - 每一次方法調用建立一個幀,並壓棧 內存模型: - 每個線程有一個工做內存和主存獨立 - 工做內存存放主存中變量值的拷貝 ---------------- | 線程執行引擎 | ---------------- | ^ assign | | use V | ---------------- | 線程工做內存 | ---------------- ^ | read,load | | store,write | V ---------------- | 主內存 | ---------------- 注:當數據從主存複製到工做內存是,必須出現兩個動做: 1:與主內存執行讀(read)操做 2:由工做內存執行相應的load操做 當數據從工做內存拷貝到主內存時,也出現兩個操做: 1:由工做內存執行的存儲(store)操做 2:由主內存執行相應的寫(write)操做 每個操做都是原子的,即執行期間不會被中斷 對於普通變量,一個線程中更新的值,不能立刻反應再其餘的變量中 若是須要在其它線程中當即可見,須要是用volatile關鍵字 可見性: - 一個線程修改了變量,其餘線程能夠當即知道 - 保證線程可見性的方法: - volatile - synchronized(unlock以前,寫變量值會主存) - final(一旦初始化完成,其餘線程就可見) 有序性: - 在本線程內,操做都是有序的 - 在線程外觀察,操做都是無序的 JVM經常使用的配置參數: Trace跟蹤參數: *:-verbose:gc *:-XX:+pringGC *:能夠打印GC的簡要信息 ---[GC 4790K->364K(15782K),0.001606 secs] ---[GC 4790K->364K(15782K),0.001474 secs] ---[GC 4790K->364K(15782K),0.001563 secs] ---[GC 4790K->364K(15782K),0.001682 secs] *-XX:+PrintGCDetails -打印GC詳細信息 *-XX:+PrintGCTimeStamps -打印GC發生的時間戳 *-Xloggc:log/gc.log -指定GC log的位置,以文件輸出 - 幫助開發人員分析問題 *-XX:+PringHeapAtGC -每一次GC先後,都打印堆信息 *-XX:+TraceClassLoading -監控類的加載 *-XX:+PrintClassHistogram -按下Ctrl + Break之後,打印類的信息 分別顯示:序號、實例數量、總大小、類型 (這個參數能夠看各個數據類型的使用狀況) 堆的分配參數: -Xmx -Xms -指定最大堆和最小堆 -Xmx20m -Xms5m -Xmn -設置新生代的大小 -XX:NewRatio -新生代(eden + 2*s)和老年代(不包含永久區)的比值 -4表示 新生代:老年代 = 1:4,即年輕代佔堆的1/5 -XX:SurvivorRatio -設置兩個Survivor區和eden的比 -8表示 兩個Survivor:eden = 2:8 一個Survivor也就是佔年輕代1/10 -XX:+HeapDumpOnOutOfMemoryError -OOM時導出到堆 -XX:+HeapDumpPath -導出OOM的路勁 -XX:OnOutOfMemoryError -在oom時,執行一個腳本 -"-XX:OnOutOfMemoryError=xxxx.bat %P" %P java進程ID 當程序oom時就會執行bat文件 能夠用來發郵件甚至重啓服務 堆的分配參數總結: 根據實際狀況調整新生代和心存代的大小 官方推薦新生代佔堆的3/8 辛存代佔新生代的1/10 在oom時,記得Dump出堆,確保能夠排查現場問題 永久區分配參數: -XX:PermSize -XX:MaxPermSize -設置永久帶的初始空間和最大空間 -他們表示,一個系統能夠容納多少個類型 棧大小分配: -Xss -一般只有幾百K -決定了函數調用的深度 -每一個線程都有獨立的棧空間 -局部變量、參數 分配在棧上 GC算法與種類: GC的概念:垃圾收集,在java中,GC的對象是堆空間和永久區。 GC算法: 引用計數法: 引用計數法的概念 對於一個對象A,只要有任何一個對象引用了A,則A的引用計數器就加1,當引用失效時,引用計數器就減1,只要對象A的引用計數器的值爲0, 則A對象就不可能再被使用。 引用計數法的問題: -引用計數法伴隨着加法和減法,影響性能。 -很難處理循環引用。 標記-清除算法: 概念: 標記-清除算法分爲兩個階段:標記階段和清除階段。一種可行的實現是,在標記階段,首先經過根節點,標記全部從根節點開始的可達對象。 所以,從未被標記的對象就是未被引用的垃圾對象。而後在清除階段,清除全部未被標記的對象。 標記-壓縮算法: 概念: 標記-壓縮算法適合用於存活對象較多的場合,如老年代。它在標記-清除算法的基礎上作了一些優化。和標記清除算法同樣。標記壓縮也須要從根節點開始。 對全部可達對象作一次標記。但以後,它並不簡單的清除未標記對象,而是將全部的存活對象壓縮到內存的一端以後,清理邊界外全部空間。 複製算法: -與標記-清除算法相比,複製算法是一種相對高效的回收方法 -不適用於存活對象比較多的場合 -將原有內存空間分爲兩塊,每次只使用其中一塊,在垃圾收回時,將正在使用的內存中存活的對象複製到未使用的內存塊中,以後,清除真的使用的內存塊 中的全部對象,交換兩個內存的角色,完成垃圾回收。 複製算法的問題: 空間浪費,需預留一半的空間。 分代思想: -根據對象的存活週期進行分代,短命對象歸爲新生代,長命對象歸爲老年代。 -根據不一樣代的特色,選取合適的收集算法: -少許對象存活適合複製算法 -大量對象存活適合標記清理或者標記壓縮 GC算法總結:全部的算法,須要一個識別的垃圾對象,所以須要給出一個可觸及性的定義。 引用計數 -沒有被java採用 標記-清除 標記-壓縮 複製算法 -新生代 可觸及性:(什麼是根:棧中的對象,全局對象,JNI方法棧中引用的對象) 可觸及: -從根節點能夠觸及到這個對象 可復活: -一旦全部引用被釋放,就可復活的狀態 -由於在finallize()中可能復活該對象 不可觸及的: -在finallze()後,可能進入不可觸及的狀態 -不可觸及的對象不可能復活 -能夠回收 Stop-The-World 是什麼: -java中一種全局暫停的現象 -全局停頓,全部java代碼中止,native代碼能夠執行,但不能和jvm交互。 -多半因爲GC引發 -Dump線程 -死鎖檢查 -堆Dump 爲何: -中止製造垃圾才能打掃乾淨。 危害: -長時間服務中止,沒有響應。 -遇到HA系統,可能引發主備切換,嚴重危害生產環境。 GC回收器: GC串行回收器: -最古老,最穩定 -效率高 -可能會產生較長的停頓 -XX:+UseSerialGC -新生代、老年代使用串行回收 -新生代複製算法 -老年代標記-壓縮算法 GC並行回收器: ParNew收集器: -XX:+UseOarNewGC -新生代並行 -老年代串行 -Serial收集器新生代的並行版本 -複製算法 -多線程,須要多核支持 - -XX:ParallelGCThreads 限制線程數量 Parallel收集器: -相似ParNew -新生代複製算法 -老年代 標記-壓縮 -更加關注吞吐量 - -XX:+UseParallelGC -使用parallel收集器+老年代串行 - -XX:+UserParallelOldGC -使用Parallel收集器+並行老年代 並行回收器的參數: -XX:MaxGCPauseMills -最大停頓時間,單位毫秒 -GC盡力保證回收時間不超過設定值 -XX:GCTimeRatio -0-100的取值範圍 -垃圾收集時間佔總時間的比 -默認99,即最大容許1%時間作GC 這兩個參數是矛盾的。由於停頓時間和吞吐量不可能同時調優 CMS收集器: -Concurrent Mark Sweep 併發標記清除 -與標記-清除算法 -與標記-壓縮相比 -併發階段會下降吞吐量 -老年代收集器(新生代使用ParNew) - -XX:+UseConcMarkSweepGC CMS的運行過程: -初始標記 -根能夠直接關聯到的對象 -速度快 -併發標記(和用戶線程一塊兒) -主要標記過程,標記所有對象 -從新標記 因爲併發標記時,用戶線程任然運行,所以在正式清理前,再作修正 -併發清除(和用戶線程一塊兒) 基於標記結果,直接清理對象 CMS的特色: -儘量的下降停頓 -會影響系統總體吞吐量和性能 -好比在用戶線程運行過程當中,分一半CPU去作GC,反應速度就降低一半 -清理不完全 -由於在清理階段,用戶線程還在運行,會產生新的垃圾,沒法清理 -由於和用戶線程一塊兒運行,不能在空間快滿時再清理 - -XX:CMSInitiatingOccupancyFraction設置觸發GC的閾值 -若是不幸內存預留空間不夠,就會引發concurrent failure GC參數 – CMS收集器 -XX:+ UseCMSCompactAtFullCollection Full GC後,進行一次整理 整理過程是獨佔的,會引發停頓時間變長 -XX:+CMSFullGCsBeforeCompaction 設置進行幾回Full GC後,進行一次碎片整理 -XX:ParallelCMSThreads 設定CMS的線程數量 GC參數整理: -XX:+UseSerialGC:在新生代和老年代使用串行收集器 -XX:SurvivorRatio:設置eden區大小和survivior區大小的比例 -XX:NewRatio:新生代和老年代的比 -XX:+UseParNewGC:在新生代使用並行收集器 -XX:+UseParallelGC :新生代使用並行回收收集器 -XX:+UseParallelOldGC:老年代使用並行回收收集器 -XX:ParallelGCThreads:設置用於垃圾回收的線程數 -XX:+UseConcMarkSweepGC:新生代使用並行收集器,老年代使用CMS+串行收集器 -XX:ParallelCMSThreads:設定CMS的線程數量 -XX:CMSInitiatingOccupancyFraction:設置CMS收集器在老年代空間被使用多少後觸發 -XX:+UseCMSCompactAtFullCollection:設置CMS收集器在完成垃圾收集後是否要進行一次內存碎片的整理 -XX:CMSFullGCsBeforeCompaction:設定進行多少次CMS垃圾回收後,進行一次內存壓縮 -XX:+CMSClassUnloadingEnabled:容許對類元數據進行回收 -XX:CMSInitiatingPermOccupancyFraction:當永久區佔用率達到這一百分比時,啓動CMS回收 -XX:UseCMSInitiatingOccupancyOnly:表示只在到達閥值的時候,才進行CMS回收 GC參數-Tomcat實例: 環境: Tomcat7 jsp網站 測試網站吞吐和延時 工具: JMeter 目的: 讓tomcat有一個不錯的吞吐量 類裝載器: class裝載驗證流程: 加載: 裝載類的第一個階段 1:取得累的二進制流 2:轉爲方法區數據結構 3:在java堆中生成對應的java.lang.Class對象 連接--> 驗證: 目的:保證Class流的格式是正確的 -文件格式的驗證 -是否以0xCAFEBABE開頭 -版本號是否合理 -元數據驗證 -是否有父類 -繼承了final類? -非抽象類實現了全部的抽象方法 -字節碼驗證 (很複雜) -運行檢查 -棧數據類型和操做碼數據參數吻合 -跳轉指令指定到合理的位置 -符號引用驗證 -常量池中描述類是否存在 -訪問的方法或字段是否存在且有足夠的權限 連接--> 準備: -分配內存,併爲類設置初始值 (方法區中) public static int v=1; 在準備階段中,v會被設置爲0 在初始化的<clinit>中才會被設置爲1 對於static final類型,在準備階段就會被賦上正確的值 public static final int v=1; 連接--> 解析: -符號引用替換爲直接引用 初始化: 執行類構造器<clinit> static變量 賦值語句 static{}語句 子類的<clinit>調用前保證父類的<clinit>被調用 <clinit>是線程安全的 什麼是類裝載器ClassLoader: ClassLoader是一個抽象類 ClassLoader的實例將讀入Java字節碼將類裝載到JVM中 ClassLoader能夠定製,知足不一樣的字節碼流獲取方式 ClassLoader負責類裝載過程當中的加載階段 系統性能監控 性能監控 - linux: uptime: 系統時間 運行時間 鏈接數 1,5,15分鐘內的系統平均負載 top: 同uptime CPU 內存 每一個進程佔CPU的狀況 vmstat: 能夠統計系統的CPU,內存,swap,io等狀況 CPU佔用率很高,上下文切換頻繁,說明系統有線程正在頻繁切換 pidstat: 細緻觀察進程 須要安裝 sudo apt-get install sysstat 監控CPU 監控IO 監控內存 ps:pidstat -p 2962 -u 1 3 -t (2962/進程號 -u/監控CUP 每秒一次 一共三次 -t/顯示線程) 性能監控 - windows: pslist -命令行工具 -可用於自動化數據收集 -顯示java程序的運行狀況 Java自帶的工具: jps: -列出java進程,相似於ps命令 -參數-q能夠指定jps只輸出進程ID ,不輸出類的短名稱 -參數-m能夠用於輸出傳遞給Java進程(主函數)的參數 -參數-l能夠用於輸出主函數的完整路徑 -參數-v能夠顯示傳遞給JVM的參數 jinfo: 能夠用來查看正在運行的Java應用程序的擴展參數,甚至支持在運行時,修改部分參數 -flag <name>:打印指定JVM的參數值 -flag [+|-]<name>:設置指定JVM參數的布爾值 -flag <name>=<value>:設置指定JVM參數的值 jmap: -生成Java應用程序的堆快照和對象的統計信息 -jmap -histo 2972 >c:\s.txt Dump堆: jmap -dump:format=b,file=c:\heap.hprof 2972 jstack: 打印線程dump -l 打印鎖信息 -m 打印java和native的幀信息 -F 強制dump,當jstack沒有響應時使用 JConsole: 圖形化監控工具 能夠查看Java應用程序的運行概況,監控堆信息、永久區使用狀況、類加載狀況等 - Visual VM: Visual VM是一個功能強大的多合一故障診斷和性能監控的可視化工具 內存溢出(OOM)的緣由: 堆溢出: 佔用大量堆空間,直接溢出,Exception in thread "main" java.lang.OutOfMemoryError: 永久區: 生成大量的類,沒法回收,Caused by: java.lang.OutOfMemoryError: PermGen space 解決方法:增大Perm區 容許Class回收 Java棧溢出: 這裏的棧溢出指,在建立線程的時候,須要爲線程分配棧空間,這個棧空間是向操做系統請求的, 若是操做系統沒法給出足夠的空間,就會拋出OOM。解決方法:減小堆內存 減小線程棧大小 直接內存溢出: ByteBuffer.allocateDirect()沒法從操做系統得到足夠的空間,解決方法:減小堆內存 有意觸發GC 鎖: 對象頭Mark: -Mark Word,對象頭的標記,32位 -描述對象的hash、鎖信息,垃圾回收標記,年齡 -指向鎖記錄的指針 -指向monitor的指針 -GC標記 -偏向鎖線程ID 偏向鎖: 大部分狀況是沒有競爭的,因此能夠經過偏向來提升性能 所謂的偏向,就是偏愛,即鎖會偏向於當前已經佔有鎖的線程 將對象頭Mark的標記設置爲偏向,並將線程ID寫入對象頭Mark 只要沒有競爭,得到偏向鎖的線程,在未來進入同步塊,不須要作同步 當其餘線程請求相同的鎖時,偏向模式結束 -XX:+UseBiasedLocking -默認啓用 在競爭激烈的場合,偏向鎖會增長系統負擔 輕量級鎖:BasicObjectLock -普通的鎖處理性能不夠理想,輕量級鎖是一種快速的鎖定方法。 -若是對象沒有被鎖定 -將對象頭的Mark指針保存到鎖對象中 -將對象頭設置爲指向鎖的指針(在線程棧空間中) -若是輕量級鎖失敗,表示存在競爭,升級爲重量級鎖(常規鎖) -在沒有鎖競爭的前提下,減小傳統鎖使用OS互斥量產生的性能損耗 -在競爭激烈時,輕量級鎖會多作不少額外操做,致使性能降低 自旋鎖: -當競爭存在時,若是線程能夠很快得到鎖,那麼能夠不在OS層掛起線程,讓線程作幾個空操做(自旋) -JDK1.6中-XX:+UseSpinning開啓 -JDK1.7中,去掉此參數,改成內置實現 -若是同步塊很長,自旋失敗,會下降系統性能 -若是同步塊很短,自旋成功,節省線程掛起切換時間,提高系統性能 偏向鎖,輕量級鎖,自旋鎖總結: -不是Java語言層面的鎖優化方法 -內置於JVM中的獲取鎖的優化方法和獲取鎖的步驟 -偏向鎖可用會先嚐試偏向鎖 -輕量級鎖可用會先嚐試輕量級鎖 -以上都失敗,嘗試自旋鎖 -再失敗,嘗試普通鎖,使用OS互斥量在操做系統層掛起 基於java代碼層面鎖的優化: 減小鎖持有時間,也就是不必作同步的方法就儘可能不要去作同步,能在方法上別在類上 減少鎖粒度: -將大對象,拆成小對象,大大增長並行度,下降鎖競爭 -偏向鎖,輕量級鎖成功率提升 -ConcurrentHashMap -HashMap的同步實現 -Collections.synchronizedMap(Map<K,V> m) -返回SynchronizedMap對象 鎖分離: 根據功能進行鎖分離 ReadWriteLock 讀多寫少的狀況,能夠提升性能 鎖粗化: 一般狀況下,爲了保證多線程間的有效併發,會要求每一個線程持有鎖的時間儘可能短,即在使用完公共資源後,應該當即釋放鎖。 只有這樣,等待在這個鎖上的其餘線程才能儘早的得到資源執行任務。可是,凡事都有一個度,若是對同一個鎖不停的進行請求、同步和釋放, 其自己也會消耗系統寶貴的資源,反而不利於性能的優化 鎖消除: 在即時編譯器時,若是發現不可能被共享的對象,則能夠消除這些對象的鎖操做 無鎖: -鎖是悲觀的操做 -無鎖是樂觀的操做 -無鎖的一種實現方式 -CAS(Compare And Swap) -非阻塞的同步 -CAS(V,E,N) CAS算法的過程是這樣:它包含3個參數CAS(V,E,N)。V表示要更新的變量,E表示預期值,N表示新值。僅當V值等於E值時, 纔會將V的值設爲N,若是V值和E值不一樣,則說明已經有其餘線程作了更新,則當前線程什麼都不作。最後,CAS返回當前V的真實值。 CAS操做是抱着樂觀的態度進行的,它老是認爲本身能夠成功完成操做。當多個線程同時使用CAS操做一個變量時,只有一個會勝出, 併成功更新,其他均會失敗。失敗的線程不會被掛起,僅是被告知失敗,而且容許再次嘗試,固然也容許失敗的線程放棄操做。基於這樣的原理, CAS操做即時沒有鎖,也能夠發現其餘線程對當前線程的干擾,並進行恰當的處理。 -在應用層面判斷多線程的干擾,若是有干擾,則通知線程重試