第三階段、深刻JVM內核—原理、診斷與優化

JVMjava

1.       虛擬機是經過軟件模擬具備完整硬件系統的,運行在隔離環境中的完整計算機系統。算法

VMWare模擬的是實際物理機包括內存,cpu等指令集,而JVM則是用軟件方式模擬java的字節碼指令集。爲了精簡,cpu是有寄存器來加快存取速度,而jvm的模擬對寄存器的模擬是定製的(由於是軟件級的)。編程

2.1996 JDK1.0 classic VM是純解釋運行,速度會很是慢,因此感受是性能不高。1997 JDK1.1,內部類,JDBC,RMI,reflection。1998 JDK1.2 出現j2se,j2ee,j2me,能夠作到精確的內存管理,瞭解內存的區間內容,編譯,解釋混合執行,gc改善。JDK 1.3 Hotspot 默認虛擬機。2002 JDK1.4,NIO,IPV6,日誌API。2000 JDK1.5,泛型,註解,裝箱。JDK1.6,腳本語言支持,JDBC4,java編譯api(容許java調用編譯)。2014,JDK8,Lambda表達式,函數編程減小行數和冗餘,語法加強。bootstrap

3.2006,Hotspot爲sun jdk,open jdk中的虛擬機。2010 sun 被oracle收購後把2008收購的Bea jrockit VM進行整合,就是在hotspot基礎上移植jrockit的優秀特性。windows

4.JVM 規範,java語言規範,JVM規範多數是二進制格式,變參語法結構,語法結構最後只要是UNICODE就是javaLetter。api

5.類型和變量,基礎數據類型就不是對象類型的。Java語言規範和JVM規範相對獨立。JVM主要定義了二進制class結構和JVM指令集等等。好比Groovy,Scala,Clojure語言均可以在JVM中運行。數組

6.JVM規範,class文件格式,數字內部標示,java裏面是沒有無符號整數的。ReturnAddress指向操做碼的指針,不對應java數據類型,不在運行時修改,finally實現須要。整數如何表達,tomcat

7.JVM中整數的存儲都是用補碼來存儲的,與C語言相似的。正數的原碼,反碼,補碼相同,負數補碼原碼符號位不變,反碼+1.安全

原碼:00000101,首位符號位(+0,-1)網絡

反碼:11111010,符號位不動,數字位取反

補碼:00000101,正數補碼就是原碼

補碼:10000110,-6的原碼

補碼:11111010,符號位不懂,反碼+1

補碼:10000001,-1的原碼

反碼:11111110,-1的反碼

補碼:11111111, -1的補碼

爲何要補碼錶示呢?有什麼好處呢?

a.對應0來講,不知道是正或負,但用補碼能夠沒有歧義的表示00000000

整數         原碼         反碼         補碼

0                00000000 01111111 00000000

-0               10000000 11111111 00000000

b.補碼很好的參與2進制運算,只要簡單的將補碼相加便可(符號位是參與運算的,能夠進位變化掉,原碼符號位就不是咱們想要的結果)

8.float的表示和定義(單精度爲例)

$      eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm

+/-    2^(e-127)(此處e也可能表示一些0.0000021212這樣的數字,正負號,一半正,一半負,基本各佔一半)

S*m*2^(e-127)

m的位數係數是2(-x)是小數,從左往右運算。

還定義了一些特殊化方法:類構造方法,實例構造方法。

9.JVM規範:

JVM沒有寄存器的存在因此有入棧出棧的方法。JVM對java liberary也提供了一些支持,reflect,classloader,初始化class,inteerface,安全,多線程,弱引用等。

JVM彙編的格式:

Index opcode [<operand1>[<operand2>]] [comment] ,如java彙編以後的結構以下,在JVM中實際執行的是彙編後的指令代碼。

Hotspot是JVM的一種實現,實際JVM只是一種文檔和規範,平時增強對JVM虛擬機的理解。

Java xxx->config->jvm.dll->jvm JNIEnv接口->main 方法

Class->loader->內存(m,d,s,na)pc,ruun,native gc

Pc寄存器:線程指向下條指令的地址(類 常量 方法信息 方法字節碼)基本認爲保存類的原信息對類進行描述 一般和永久區關聯。

Java堆:和程序開發比較密切,好比new 對象比較集中,是全部線程共享的。對不一樣的GC來講,堆也是分代的。

Java棧:棧是線程私有的,有一些幀組成。幀裏面放的是局部量,操做數棧,常量池指針。每次方法調用都會建立新的幀而且壓到棧中去。(這個就能解釋struts1雖然是單例模式的,但調用方法的時候倒是線程安全的)好比這個例子:一個槽位最多容納32位,因此long佔用2個槽位。這是靜態的方法。非靜態方法的首個槽位傳遞的是當前對象的引用。其餘基本相同。無論是否是遞歸,每次方法調用都會產生一個幀棧。

Java棧:操做數棧

由於java沒有寄存器,全部參數傳遞都必須依靠棧來完成,就叫操做數棧。經過這圖說明局部變量和操做數棧是有區別的。

棧上分配內存使用完畢就會被回收,不太可能出現內存泄露的問題。由於JVM進行了必定的優化。但棧空間分配的對象內容比較小。棧上分配只能分配非逃逸的,若是逃逸,別的線程也使用的話,就只能堆中分配了。

 

 

 

棧上分配小對象是有好處的,棧是線程私有,函數調用完畢會自動回收減輕gc壓力(說明gc是對堆進行操做的)

堆,方法區,棧交互方法。線程運行的變量引用是在棧中的,對象的實例分配是在堆當中的,因此當須要對象內容時,堆又會去方法區裏讀取信息。(由於class的字節碼和類描述是在方法區中的)

如何讓遞歸函數調用的次數多一些,該怎麼作呢?

內存模型,線程有本身的工做內存和共享的主存,。線程操做的是本身的工做線程,工做內存和主存是有時差的。因此他們之間有值的交互。一個線程更新的值是不能立刻反應到其餘工做線程中。可是想其餘線程也能馬上得到改動的值須要用到volatile關鍵字,其餘線程就會去主存中拿去數據。

Volatile說性能可能比同步強,實際上不絕對,並且也不能替代鎖的功能。語義是否知足需求。

可見性:線程修改變量後,其餘線程能夠當即知道。

保證可見性方法:

Volatile,synchornized解鎖以前,數據會進行同步,回寫信息到主存中。Final定義的變量在初始化後其餘線程是能夠當即可見的。

無序性,指令重排或工做內存和主存的同步延時問題,a線程作的事情對b線程來講多是無序的。

若是能保證同個線程當中按順序語義執行結果是相同的話,但其餘線程中是不能保證的。

對於線程內的語句順序由編譯器保證,並不必定是有序的。

有一些先後的限定依賴。

解釋執行,執行的是字節碼。編譯執行(是運行時編譯字節碼成機器碼)有數量級的提高。保守估計性能差10倍以上。

JVM結構,功能實現機制,平時更注意些什麼內容,減小避免一些低級的錯誤。

 

經常使用JVM配置參數

Trace跟蹤參數

Def new

Eden

From tp

Tenured

The space

Compacting perm

The space

Ro rw space

 

Xloggc:log/gc.log能夠重定向log日誌,當服務關閉後仍是能夠追溯到日誌文件的。

-XX:+PrintHeapAtGc後以下面的gc打印信息能夠看出,eden內的部分信息移到了tenured區,而其餘區內容被清除了。

-XX:+TraceClassLoading主要監控系統中類的加載,查看耿宗調試場合。

-XX:+PrintClassHistogram,ctrl+break後,打印類的信息,看看若是出現outmemmery可能就是byte數組引發的,是按序排序的。

-Xmx –Xms

最多和至少使用多少空間。Java的運行通常會限制在最小的空間左右運行,gc後實在不能保持在最小空間內就進行擴容。

Total mem是已經使用的空間,總空間-totalmem是freeme

Xmn-新生代大小

From to 是倖存區的意思survivorRatio

新生代不夠可能會向老年代分配,tenured放一些classloader,線程等系統級別的對象。

合理的survivor大小有利於eden帶的使用,GC的次數減小也能下降老年代的增長。

-XX:+HeapDumpOnOutOfMemoryError,出現OutOfMemoryError後作一個轉存。正常可能會立刻宕掉,重現,運行時間可能都比較長,在運行時就把堆信息。

-XX:+HeapDumpPath,導出OOM的路徑。

甚至能夠作一個腳本的調用工做,重啓,發郵件呀,報警呀都是能夠作的。

參數的調整是根據實際的狀況具體調整的,官方推薦edge,3/8,survivo1/10,OOM時,記得dump出堆的信息,確保可排查現場問題,要麼很難復現outofmemery狀況。

-XX:PermSize –XX:MaxPermSize 永久區初始空間和最大空間。表示一個系統能夠容納多少類型。

使用 CGLIB等庫時候可能產生大量的類,這些類可能會撐爆永久區致使OOM,發生fullGC後也,即便堆空間沒有用完,永久區Perm gen使用完也是有可能致使溢出的。

-Xss棧大小分配,每一個線程有且私有區域,棧幀內包括(局部變量表,參數分配在棧上),棧空間對線程運行是不可缺乏的,棧通常仍是比較小的,由於線程數可能會比較多。N*ss,爲了增多線程數,減少棧的大小。可是棧空間又決定了調用深度(好比遞歸調用)。沒有出口的遞歸必然致使棧溢出。爲了增多遞歸調用次數,由於棧幀內局部變量表,因此減小局部變量能夠減少棧幀空間。

 

GC算法

及時回收無用的內存,以確保系統有足夠內存可用。Java裏由gc線程來回收管理,目的是防止人爲引發和防止內存的泄露。

堆和永久區是受GC管理的。如何釋放,就由算法來處理了。下面是算法。

1.       引用計數

只要有人使用某對象a,就會+1,釋放後-1,當爲0時,這個對象就能夠被釋放。只要有路徑可達對象,對象就被引用。不然就能夠回收。

但垃圾對象的循環引用是不可被處理的,這是個根本問題。

2.       標記清除

  1. 標記階段(區分是否可達)
  2. 清除階段(未被標記的就能夠清除)

3.       標記壓縮(是對清除算法的一種改良)

使用存活對象比較多的狀況。先從根對象進行一次標記,清理時是將存活的對象複製(移動)到一端,清理邊界外空間(這個邊界目前指的就是行尾)。

4.       複製算法

  1. 不適合存活對象多的場合,將正使用中的對象複製到未使用的內存塊中,而後清理原使用區的全部對象,交換角色,完成垃圾回收。

一次只使用其中一半的空間,有些空間浪費。

那麼怎麼優化,大對象進入老年代,老年對象(通過多次回收都沒有回收掉計數增大)進入老年代。若是減小浪費。增長擔保空間,總有一部分複製空間是不使用的。由於複製空間放一些小的對象,因此大對象通常放老年空間中。

如今對照這個堆空間信息來看eden就是新生代,from,to就是複製空間,咱們實際計算new generation總大小(0x28d80000-0x27e80000)/1024/12014=15M,而eden大小不足12288k,不足總大小,緣由就是有一部分被複制區佔用了。

分代思想:短命的是新生代,長命的爲老年代。少許對象存活,使用用複製算法(由於用到了複製)。大量對象存活(好比老年代,少部分是做爲擔保區進入的,大部分仍是由於沒有被回收而進入老年代,老年代存的多,時期長,複製的話機會把整個區都會複製一遍)因此用標記清理或標記壓縮可能性能好些。

總結:引用計數沒有采用。而標記清除老年代使用,複製算分新生代使用。這些算分如何識別垃圾對象呢,可觸及性。

可觸及性:從根開始,能夠觸及到這個對象。

可復活的:對象的引用丟了,可是可能復活,從新觸及。雖然暫時不可達,可是是不能回收的。

不可觸及:不可達,也不能復活,因此能夠回收。

Finalize只會調用1次,有可能使對象復活。因此避免使用finalize方法,並且gc調用時調用,基本是不受控制的。卻是能夠用try-catch-finally來替代,finalize何時調用並不肯定,gc的調用點也不肯定,因此不太推薦使用這個方法。

可觸及性:根如何判斷,棧中引用對象,靜態或常量(全局對象),JNI方法棧中引用對象。

Stop-The-World:全局停頓,native代碼雖然能夠執行卻不能交互,雖然dump線程,死鎖檢查,堆dump均可能出現全局停頓,但多半是由gc引發。爲什麼要全局停頓?當gc在清理過程當中,若是java還在不斷產生垃圾的話,1是不會完全的將垃圾清除,2是此時清理也會給gc形成很大負擔,給gc算法也帶來很大難度(很差判斷究竟是不是垃圾),所以gc一旦開始工做,其餘全部java線程都要停下來進行垃圾標記,若是過程動盪,垃圾標記也不會很順利,gc的新生停頓較短,老年花的時間可能就比較大了,有時可能沒有相應。那有沒有解決方法呢?使用主備的方式,可是同時服務也是很危險的。

JVM的gc,一屋不掃,何以掃天下,若是家裏的垃圾不清理,程序是不可能長期運行的,負載是大的,空間和容量能力是有限的。

 

GC參數

1.       堆結構

通常分配對象都到新生區eden,若是過大的對象超過預值就放到tenured區。若是新生代回收後對象依然存在,就放到倖存代(s0-s1),from to大小相同,徹底對稱,功能相同,只是浪費空間,若是經歷屢次垃圾回收就會到老年代。

2.       串行收集器

useSerialgc古老而穩定,只不過是單線程的,啓用後新生代複製算法,老年代用壓縮算法(先標記可達對象,而後向一端複製,最後有多少是知道的,邊界以外的就清除掉)。這就是串行執行的模型了。

3.       並行收集器

啓用useparnewGC後只應用到新生代的收集,使用多線程的複製算法。下面就是模型了。這種方式使用多個線程進行回收,而後程序繼續運行。

總結:都會出現stop the hole word.

Parallel收集器:

更關注吞吐量,新生代依然是複製,老年代是標記壓縮,能夠當作是一個新生代與老年代的並行化。-XX+UseParallelGC,-XX+UseParallelOldGC。

-XX:MaxGCPauseMills,是每次GC回收不超過的設定值.

-XX:GCTimeRatio和吞吐量有關,單位時間cpu分到了gc仍是應用程序,應用程序執行時間越長,應答能力也越長,吞吐量就好過。因此這兩個參數是矛盾的,咱們要知道問題的主要矛盾在那,不可能全都提升。要麼就優化GC算分。

4.       CMS收集器:concurrent mark sweep併發標記清除,垃圾回收器和應用程序一塊兒執行,交替執行,停頓時間相對減小,可能下降吞吐量。由於要達到和應用程序併發執行,因此會比較複雜。a.從根快速作一次標記(快速標記s-h-wold)。B.併發標記。(所有標記)C.過程當中有新的垃圾產生,因此修正從新標記(基本完善,快速stopholeworld)D.開啓清理操做。用併發清除,而不是壓縮,若是是壓縮,應用程序很難繼續執行了。

5.       它儘量下降系統停頓,給gc的時間多,性能就差。由於cms和用戶線程同時執行,若是應用程序內存不夠花了。若是cms方式致使應用沒有足夠可用的內存。能夠用串行回收器做爲備用回收器,啓用回收。這時可能就有長時停頓並且內存是消耗殆盡的狀態。

6.       標記清除可能有大量碎片存在和標記壓縮會複製到一端,而後清除邊界外的空間。碎片多的會影響連續的內存空間分配。Cms之因此用標記清除,是由於它更關注停頓時間。若是用標記壓縮不但停頓時間可能稍長,並且可能它會找不到那些可用對象的位置。CMS爲了回收後的整理。-XX:+UseCMSCompactAtFullCollection Full GC(整理時間獨佔停頓)

-XX:+CMSFullGCsBeforeCompaction,對象多,一個大的堆,整理時停頓的時間將會變長。這些參數也只能保證大部分時間的停頓週期較短,若是數量過多,仍是仍是會形成較長的停頓時間的。-XX:parallelCMSThreads約等於cpu的數量。

如何減輕GC壓力呢?

A.如何架構,

b.代碼怎麼寫。

C.堆空間怎麼分配。

JVM有一些串行cms的內存回收算法和策略,均衡和減小回收時付出的代價。

7.       Tomcat實例演示

FullGC是比較花時間的,沒有漲的話,吞吐量會有個比較好的效果。若是堆會有個擴展過程,若是直接把堆設置到64m,那麼GC數量就會大大減小,(由於JVM會盡可能維持一個低內存的運做,內存不足會執行較多GC)。

同堆大小,新生代,老年待使用並行回收,吞吐量影響並不大。

減小堆大小,增長GC壓力,使用Serial回收器。

Serial 646 串行回收(N+O)

Parallel 685 並行回收(N+O)

Parnew 660 隻影響新生代(N)

仍是都使用並行算法效率高呀

 

Tomcat7+JDK6 622.5

Tomcat7+JDK7 680

性能提高和JDK版本也是有關係的,JDK不是隨隨便便升的,要充分測試。

性能:

  1. 在於應用的架構。各類配置參數只是一個微調只是一個錦上添花的做用。
  2. 在良好應用程序的基礎上,若是是堆大小設置不合理,那GC的調整會影響局部的部分,不太可能影響全局的性能。
  3. 參數只是個良好應用程序的配置項。固然設置不合理也會影響一些性能,產生大的延時。

Class Loader類裝載器

加載

1.無論是文件仍是網絡中加載,都是二進制流。將內容轉爲方法區中的數據結構,在堆中生成java.lang.Class對象,這是加載基本過程。

鏈接

驗證a.類名,方法信息,字段信息呀,要驗證是不是個正常的格式。0xCAFEBABE開頭,jdk版本號範圍;元數據基本類型檢查基本的語法和語義;字節碼檢驗,操做數棧,局部變量是否和參數吻合,實際運行可能並不是如此,字節碼沒發正常執行,還有跳轉指定位置是否合理,檢查不合理確定是有問題的,經過也未必合理。也會作符號引用,繼承和實現的肯能是不合理的,權限,包或子類是不能經過符號驗證的。

準備階段:分配內存,並設置初始值(方法區),好比初始階段多是0,初始纔會被設成1.而常量準備階段設置成1後期使用永遠不變化。

解析:符號引用(java.lang.Object)替換爲直接引用(指針或地址偏移量,引用對象必定在內存中)

執行類構造器:clinit,static變量賦值語句,static{}語句,父類clinit被調用,clinit是線程安全的。

Java.lang.NoSuchFieldError錯誤什麼階段拋出?

classLoader是一個抽象類,將讀入java字節碼將類裝載到JVM中,能夠定製知足不一樣字節碼流的獲取方式,只負責類的加載過程。

         loadClass(String className) defineClass(字節長度) findClass(String class)回調方法,自定義classLoader的推薦方法,這個怎麼回調呢?findLoadedClass(String name),尋找已經被加載的類。

那些相關的BootStrap classloader,extension classloader,app classloader(和應用相關的),custom classloader,每一個classLoader都有一個parent,bootstrap classloader是不存在的。

 

自底向上裝載classloader,咱們本身寫的類會去app中找是否已經加載了這個類,若是沒有找到,會將請求給父類,(Extension Classloader)如今本身層面去找,有就返回就行了,不然向上問,若是到bootstrap classloader一路都沒有找到,那就開始加載,注意順序是從頂向下進行加載的。上面的加載完畢,下面就輪不到加載了,bootstrap沒有,再給exten,再給app,     。有些是在bootstrap classLoader,一般rt.jar核心包,是系統的核心,bootstrap classLoader就會加載。Java的擴展都會加載lib/ext,appcloassloader會在classpath下。有一種從基礎開始架構和構建class類的意思。

舉了個例子就是強制加載到apploader中,而後從下往上找,直接找到了。

將個類注入到啓動class當中去,從下向上查找,從頂向下加載,rt.jar如何加載應用類呢,service provider interface spi,這個接口再rt.jar中多是沒有實現的,那麼在app classloader中實現,而接口,工廠是在rt.jar中,那麼如何在rt.jar中產生appclassloader中定義的class,如何解決。Thread.setContextClassLoader(),上下文加載器是一種角色,在頂層中傳入底層的實例。因此把FactoryFinder放到一個正確的ClassLoader中就能突破雙親模式的侷限性。

雙親加載是JVM的一種默認模式,但不是必須的。Tomcat有個WebappClassLoader,先加載本身的class,自己就是雙親模式的違背,找不到再給parent。正常是從parent開始加載。有點像從object根開始向下加載的過程。

OSGI模塊化熱加載,有一套查找類的機制,是網狀的,徹底不符合。

findClass,先查找類,找不到就查找類文件,流轉換成2進制流,而後轉換成class模板,class模板就能夠分配對象空間。

 

加載類,沒有定義父類,默認就是Object類,由於要初始化它。因此加載時會從Object開始加載,而後咱們的OrderClassLoader卻沒有這個Object類,這本來屬於rt.jar中的內容,這說明ClassLoader重定義後自行加載。假若不重載默認是在appclassloader中加載rt.jar加載的。

熱替換

Class文件替換後不重啓系統讓他生效,如何實現這個功能?

應該是監聽文件是否修改,觸發事件編譯,而後在自定義classloader進行替換。

而這種實現是破壞了原有的雙親模式實現的熱替換,使程序架構及其靈活有無限的想象空間。

 

性能監控工具:

1.       運行中可能出現些瓶頸,借用一些內部外部的工具肯定系統總體運行的狀態,基本從大方向上定位問題的所在,java自帶工具查看java程序運行細節,進步必定位問題。

若是用到了swap,說明內存可能不很足夠,可能涉及到了I/O讀寫。

說明系統有線程在頻繁切換。

2.       pidstat,能夠監控cpu,內存,i/o,這個能夠查看到那個線程對I/O有影響。

3.       windows: perfmon,pslist(定時定點用腳本作些事情,命令行工具,自動化數據收集)

4.       java自帶工具初步定位是那方面問題?

Jps:查看進程

Jinfo:能夠查看一些參數的值,甚至能夠在運行時修改某些參數的值。

Jmap: 打印堆內存信息

Jstack:打印線程堆信息

jConsole:圖形化工具,能夠查看類,堆,多個進程,能夠查看各個硬件佔用狀況,甚至能夠查看具體的內存待狀況。一個好的thread名字有利於排查線程問題

VisualVM:基本都能實現jconsole的功能。各類圖像,各類抽樣統計。其實主要仍是想看和跟蹤一些線程。

來實踐:jps找到id號,打印出線程的執行狀況,基本能夠發現問題所在。

本身佔用資源,又不釋放資源,相互等待對方的資源。像這種狀況須要有一個對象消失。

咱們想調優,或查找系統瓶頸,咱們須要學會使用一些工具,不然很難查看系統運行細節信息,也就不能排除和定位問題所在,所以,掌握這些是個基本技能。

 

X.java堆分析

內存溢出(OOM),堆,永久區,線程棧,直接內存。

 

堆溢出

解決:增大值或釋放內存空間。

 

永久區溢出,類數量過多,永久區過滿。

解決方法:增大perm大小,容許class回收。

 

棧溢出

線程請求分配空間,若是操做系統不能沒法給足夠空間,也會報OOM。

減小堆的使用,讓棧有跟多的空間可使用。

減小棧的空間,可分配更多線程。

 

直接內存溢出(會致使OOM,但不會觸發GC,)

         棧+堆+直接內存 <= 操做系統分配給內存的,是在堆外的。堆空間用的少,就會給直接內存更多空間。只不過手動觸發gc是能夠回收直接內存的。

支配樹:

沒有任何別的路徑到某個節點,那就造成了支配和被支配的結構,支配者被回收,被支配者也會被回收。這個能夠決定惟一路徑的作法是能夠估算究竟多少內存空間被佔用的。

MAT若是出現內存問題,多數是由佔用內存大的地方出現。

淺堆:內存結構佔用內存的大小,和對象結構有關,和內容無關。只是指向內存的引用地址。

深堆:是實際對象存在佔用的內存空間大小(GC主要也是收集這部份內容,和支配樹是有關的,淺堆應該是記錄了深堆的大小的)

Tomcat OOM案例分析:

將堆空間導出。CurrentHashMap是個高併發hashmap,使用是隻加鎖其中結構單元的segment(table(entry(seg_sessions(每一個對象就是tomcat持有session的信息))))過多session可能致使OOM。有些屬性值,訪問時間,過時時間,建立時間也能夠看出一段時間的平均壓力狀況。解決辦法:堆增多,過時時間減小。(VISUAL VM,MAT工具的使用對數據的分析探索)

 

三:鎖

1.       線程安全

  1. 統計網上人數。若是精確統計的話,多線程對一個變量操做,不加鎖極可能是減小的。

多線程中,arraylist不是線程安全的,在擴充的不可用狀態時是會拋出indexoutofboundException.

  1. 對象標記(hash,年齡,gc標記狀況,偏向鎖等信息)

2.       偏向鎖,偏向已經佔用這個鎖的線程。爲何用偏向鎖呢,認爲大部分是沒有鎖競爭的,這時就把MARK放到偏向鎖中,下次就直接進入了。固然競爭激烈的狀況對性能是有影響的。偏向鎖通常是在JVM啓動後才啓動的,啓動階段競爭會比較激烈。競爭小的狀況下有5%的性能提高能力。

3.       BasicObjectLock,是放在線程棧中的一個鎖。一個是對象頭或指向持有這個鎖的對象的指針。爲了改善偏向鎖的性能。

4.       若是輕量失敗??

5.       自旋鎖,若是咱們假定線程會很快得到鎖,就沒有必要讓操做系統進行掛起,多轉幾回便可。只要空轉的代價小於掛起的代價,就是划算的。同步塊短自旋鎖成功率高。

線程會先嚐試偏向鎖,而後嘗試輕量鎖,而後嘗試自旋轉鎖,而後普通鎖。鎖的級別和性能代價都是有關聯的。

6.       減小鎖粒度,偏向鎖和輕量鎖成功率就會提升,性能就會很好。 好比ConcurrentHashMap就被分紅若干個Segment,說白了就是一個hashmap被拆成了多個數組,那麼競爭就小了不少。

7.       減小鎖的粒度也是有反作用的。

8.       鎖分離也是一種方式。你們能夠都拿讀鎖,就不須要等待了。寫有排他性,好比arraylist擴展的時候禁止訪問。再好比LinkedBlockedQueue就是一種讀寫鎖分離的模式。

9.       有的時候鎖的請求和釋放多是會更消耗資源的,此時咱們反而減小鎖的得到釋放for(){synchornize(){}}能夠調整爲synchornized(){}

10.   一般狀況下,局部變量或根本不須要加鎖的代碼塊在運行時是會取消鎖的。-xx:+DoEscapeAnalysis 進行逃逸分析,執行鎖消除。

無鎖:認爲預期鎖是不存在的,cas(compare and swap)是一種不斷嘗試的過程,和現實中有些事情有點像,有個預期的值和將會變化的值。更多的嘗試是在應用層面判斷多線程的干擾,若是有干擾則通知線程重試,程序變的複雜,但性能變的更好。好比有些原子的更新操做就是。無鎖性能高於通常的有鎖性能。

道可道,很是道,萬事萬物都沒有放之四海皆準的道理,雖然一些情景下有與之相適應的方法手段,具體的仍是看當時的狀況來應對。

 

Class文件結構

1.       語言無關性,jvm直接運行的是.class文件。Jvm甚至推出了只有動態腳本語言可用的指令,甚至java都是不能使用的。單純爲jvm進行擴展。再本身實現的編譯器中能夠調用這個指令,以class爲分界,java和jvm幾乎是兩塊獨立的部分。有不少不少語言都在jvm上獲得完善的支持。

2.       class文件結構

這就是class的結構了

Magic u4 – 0XCAFFBABE表示是4byte的class文件,代表是java的class文件,   還有minor_version u2,major_version u2能夠看出是那個版本編譯出的class文件。

常量池:

注意,方法,字段都會引用和依賴於常量池具體內容。CONSTANT_NameAndType :是對字段的描述的。

CONSTANT_Utf8

字節長度,內容

CONSTANT_Integer

CONSTANT_String不會保存內容,保存的是索引

CONSTANT_NameAndType name名字指向,描述指向

CONSTANT_Class  也是指向的長度和具體的String值。

CONSTANT_Fieldref,CONSTANT_Methodref,CONSTANT_InterfaceMethodref,注意上面結構都是文件結構常量池中的

Access flag u2:類的標誌符

在標誌後面有個指向this_class u2 super_class u2指向常量池的class。

常量池的重要性,幾乎全部的描述都會檢索到常量池中的數據。

Interface_count u2接口每一個interface是指向CONSTANT_Class的索引。

Field_count,字段訪問標誌。各類表示方式是不一樣的。Description_field,字段存放在那裏。

Method_count,也是各類名稱都被常量描述,簡單幾個字符就能表示清楚。

Attribute能夠嵌套的加入不少。其中有個code就是method的字節碼信息,Exceptions是method拋出的異常,LineNumberTable是用來表示行號和字節碼的關係,,LocalVaribleTable,方法局部變量表描述。SourceFile,使用那個文件編譯出來的,會有個說明,synthetic編譯器產生的方法或字段。

Attribute的deprecated的名稱。ConstantValue,大部分都是name和長度,constantvalue_index,常量值,指向常量池,能夠是個自變量類型的值。

Code_attribute{

Name_index,

Length,

Statck,最大棧長

Locals,最大局部鼻樑

Code_length,字節碼長度

Exception_table_length,異常數量

{

Start_pc,異常開始,code都有一些偏移量

U2 End_pc,

U2 handler_pc,

Catch_type,指向的是常量池的exception類

}exception_table(exception_table_length)

Attributes_count,屬性數量lineNumberTable,VaribleTable等。

}

LineNumberTable{

Name,

Length,

{

Start_pc,

Line_number,

}line_number_table[line_number_table_length]

}

localVariableTable{

name,

length,

variable_table,length

{

Start_pc,

Length,

Name_index,

Descriptor_index,名稱類型

Index , slot位置

}local_variable_table[local_variable_table_length]

}

上面是code屬性中的。

Exceptions,throws部分和code屬性是平級的

Name,length,num_exception,exception_table[number_of_exception]指向constant_class的索引

能夠看出某些方法的屬性,code值等異常信息。

SourceFile,name,length。

類型,個數,大小版本,數據類型常量,指向常量池執指針,指明長度偏移量等。

看出基本都是十六進制類型的偏移量:

根據各類偏移量及編碼類型,定位到code的位置,各類結構的描述,包括最大的棧空間描述等。內部有各類code偏移量和最後由那個sourcefile編譯的結果。

Class是個複雜的數據結構,是個jvm的基石,字節碼指令。

JVM字節碼執行過程:

Javap,字節碼反彙編,而後執行。

計數器,操做數棧,局部變量表。解讀下字節碼指令。

字節碼指令是個byte整數,左邊是足助記符,右邊是一個整數。因此一串字節碼2A 1B B5 00 20 B1最後都能對應到相應的指令實現上。

對象操做指令:

Xxconst_xx表明xx入棧,原來底層是這麼複雜的結構,局部變量入棧。

Xload,壓棧

Xstore,出棧

類型轉化

I2l

Invokevirtual

ASM生成java字節碼,修改已有的class字節碼,cglib就是用了ASM進行動態代理的。

這就是底層直接修改字節碼的一種方式,基本到了貼近字節碼彙編語言的狀態。

如何模擬實現AOP字節碼織入呢?

好比日誌什麼的。、

進行方法包裝後

         織入字節碼

而後覆蓋原來的二進制class文件

有的熱點代碼,class執行性能可能並不理想,能夠先編譯成與本地平臺相關的機器碼,執行時性能要好的多。

 

熱點代碼,有方法體調用計數器,還有一個方法體內的循環調用計數器(可能出現棧上替換的時候)。

Xint解釋執行,Xcomp所有編譯執行(工做量很大),Xmixed默認混合。路漫漫其修遠兮,吾將上下而求索。Jrt的編譯還有不少並無展開。

相關文章
相關標籤/搜索