如下總結不保證全對,若有錯誤,還望可以指出。謝謝前端
相關代碼實如今個人GitHub裏:git
喜歡的話麻煩star一下哈github
本系列技術文章首發於個人我的博客:面試
更多關於Java後端學習的內容請到個人CSDN博客上查看:bootstrap
首先JVM是一個虛擬機,當你安裝了jre,它就包含了jvm環境。JVM有本身的內存結構,字節碼執行引擎,所以class字節碼才能在jvm上運行,除了Java之外,Scala,groovy等語言也能夠編譯成字節碼然後在jvm中運行。JVM是用c開發的。數組
內存模型老生常談了,主要就是線程共享的堆區,方法區,本地方法棧。還有線程私有的虛擬機棧和程序計數器。安全
堆區存放全部對象,每一個對象有一個地址,Java類jvm初始化時加載到方法區,然後會在堆區中生成一個Class<?>對象,來負責這個類全部實例的實例化。
棧區存放的是棧幀結構,棧幀是一段內存空間,包括參數列表,返回地址,局部變量表等,局部變量表由一堆slot組成,slot的大小固定,根據變量的數據類型決定須要用到幾個slot。
方法區存放類的元數據,將原來的字面量轉換成引用,固然,方法區也提供常量池,常量池存放-128到127的數字類型的包裝類。 字符串常量池則會存放使用intern的字符串變量。
這裏指的是oom和內存泄漏這類錯誤。
oom通常分爲三種,堆區內存溢出,棧區內存溢出以及方法區內存溢出。
堆內存溢出主要緣由是建立了太多對象,好比一個集合類死循環添加一個數,此時設置jvm參數使堆內存最大值爲10m,一會就會報oom異常。
棧內存溢出主要與棧空間和線程有關,由於棧是線程私有的,若是建立太多線程,內存值超過棧空間上限,也會報oom。
方法區內存溢出主要是因爲動態加載類的數量太多,或者是不斷建立一個動態代理,用不了多久方法區內存也會溢出,會報oom,這裏在1.7以前會報permgem oom,1.8則會報meta space oom,這是由於1.8中刪除了堆中的永久代,轉而使用元數據區。
內存泄漏通常是由於對象被引用沒法回收,好比一個集合中存着不少對象,可能你在外部代碼把對象的引用置空了,可是因爲對象還被集合給引用着,因此沒法被回收,致使內存泄漏。測試也很簡單,就在集合裏添加對象,添加完之後把引用置空,循環操做,一會就會出現oom異常,緣由是內存泄漏太多了,致使沒有空間分配新的對象。
命令行工具備jstack jstat jmap 等,jstack能夠跟蹤線程的調用堆棧,以便追蹤錯誤緣由。
jstat能夠檢查jvm的內存使用狀況,gc狀況以及線程狀態等。
jmap用於把堆棧快照轉儲到文件系統,而後能夠用其餘工具去排查。
visualvm是一款很不錯的gui調試工具,能夠遠程登陸主機以便訪問其jvm的狀態並進行監控。
class文件結構比較複雜,首先jvm定義了一個class文件的規則,而且讓jvm按照這個規則去驗證與讀取。
開頭是一串魔數,而後接下來會有各類不一樣長度的數據,經過class的規則去讀取這些數據,jvm就能夠識別其內容,最後將其加載到方法區。
jvm的類加載順序是bootstrap類加載器,extclassloader加載器,最後是appclassloader用戶加載器,分別加載的是jdk/bin ,jdk/ext以及用戶定義的類目錄下的類(通常經過ide指定),通常核心類都由bootstrap和ext加載器來加載,appclassloader用於加載本身寫的類。
雙親委派模型,加載一個類時,首先獲取當前類加載器,先找到最高層的類加載器bootstrap讓他嘗試加載,他若是加載不了再讓ext加載器去加載,若是他也加載不了再讓appclassloader去加載。這樣的話,確保一個類型只會被加載一次,而且以高層類加載器爲準,防止某些類與核心類重複,產生錯誤。
類加載classloader中有兩個方法loadclass和findclass,loadclass聽從雙親委派模型,先調用父類加載的loadclass,若是父類和本身都沒法加載該類,則會去調用findclass方法,而findclass默認實現爲空,若是要自定義類加載方式,則能夠重寫findclass方法。
常見使用defineclass的狀況是從網絡或者文件讀取字節碼,而後經過defineclass將其定義成一個類,而且返回一個Class<?>對象,說明此時類已經加載到方法區了。固然1.8之前實現方法區的是永久代,1.8之後則是元空間了。
jvm經過字節碼執行引擎來執行class代碼,他是一個棧式執行引擎。這部份內容比較高深,在這裏就不獻醜了。
編譯期優化主要有幾種
1 泛型的擦除,使得泛型在編譯時變成了實際類型,也叫僞泛型。
2 自動拆箱裝箱,foreach循環自動變成迭代器實現的for循環。
3 條件編譯,好比if(true)直接可得。
運行期優化主要有幾種
1 JIT即時編譯
Java既是編譯語言也是解釋語言,由於須要編譯代碼生成字節碼,然後經過解釋器解釋執行。
可是,有些代碼因爲常常被使用而成爲熱點代碼,每次都編譯太過費時費力,乾脆直接把他編譯成本地代碼,這種方式叫作JIT即時編譯處理,因此這部分代碼能夠直接在本地運行而不須要經過jvm的執行引擎。
2 公共表達式擦除,就是一個式子在後面若是沒有被修改,在後面調用時就會被直接替換成數值。
3 數組邊界擦除,方法內聯,比較偏,意義不大。
4 逃逸分析,用於分析一個對象的做用範圍,若是隻侷限在方法中被訪問,則說明不會逃逸出方法,這樣的話他就是線程安全的,不須要進行併發加鎖。
1
1 GC算法:中止複製,存活對象少時適用,缺點是須要兩倍空間。標記清除,存活對象多時適用,可是容易產生隨便。標記整理,存活對象少時適用,須要移動對象較多。
2 GC分區,通常GC發生在堆區,堆區可分爲年輕代,老年代,之前有永久代,如今沒有了。
年輕代分爲eden和survior,新對象分配在eden,當年輕代滿時觸發minor gc,存活對象移至survivor區,而後兩個區互換,等待下一場gc, 當對象存活的閾值達到設定值時進入老年代,大對象也會直接進入老年代。
老年代空間較大,當老年代空間不足以存放年輕代過來的對象時,開始進行full gc。同時整理年輕代和老年代。 通常年輕代使用中止複製,老年代使用標記清除。
3 垃圾收集器
serial串行
parallel並行
它們都有年輕代與老年代的不一樣實現。
而後是scanvage收集器,注重吞吐量,能夠本身設置,不過不注重延遲。
cms垃圾收集器,注重延遲的縮短和控制,而且收集線程和系統線程能夠併發。
cms收集步驟主要是,初次標記gc root,而後停頓進行併發標記,然後處理改變後的標記,最後停頓進行併發清除。
g1收集器和cms的收集方式相似,可是g1將堆內存劃分紅了大小相同的小塊區域,而且將垃圾集中到一個區域,存活對象集中到另外一個區域,而後進行收集,防止產生碎片,同時使分配方式更靈活,它還支持根據對象變化預測停頓時間,從而更好地幫用戶解決延遲等問題。
在Java併發中講述了synchronized重量級鎖以及鎖優化的方法,包括輕量級鎖,偏向鎖,自旋鎖等。詳細內容能夠參考個人專欄:Java併發技術指南
微信公衆號【黃小斜】大廠程序員,互聯網行業新知,終身學習踐行者。關注後回覆「Java」、「Python」、「C++」、「大數據」、「機器學習」、「算法」、「AI」、「Android」、「前端」、「iOS」、「考研」、「BAT」、「校招」、「筆試」、「面試」、「面經」、「計算機基礎」、「LeetCode」 等關鍵字能夠獲取對應的免費學習資料。