本章節屬於Java進階系列,前面關於設計模式講解完了,有興趣的童鞋能夠翻看以前的博文,後面會講解JVM的優化,整個系列會完整的講解整個java體系與生態相關的中間件知識。本次將對jvm有更深刻的學習,咱們不只要讓程序能跑起來,並且是能夠跑的更快!能夠分析解決在生產環境中所遇到的各類「棘手」的問題,好比運行的應用卡住了,日誌不輸出,程序沒有反應,CPU負載忽然升高,多線程應用下,如何分配線程數量等。java
做爲java工程師,對於jvm確定不陌生。JVM是Java Virtual Machine的縮寫,通俗來講也就是運行java代碼的容器。當項目啓動時,會根據jvm相關配置參數,在計算機的內存中開啓一片空間用於運行JVM。以後java相關代碼就會被加載進JVM中運行。c++
百度百科對JVM的定義:程序員
對於Java程序員來講,在虛擬機自動內存管理機制的幫助下,再也不須要爲每個new操做去寫配對的delete/free代碼,不容易出現內存泄漏和內存溢出問題,看起來由虛擬機管理內存一切都很美好。不過,也正是由於Java程序員把控制內存的權力交給了Java虛擬機,一旦出現內存泄漏和溢出方面的問題,若是不瞭解虛擬機是怎樣使用內存的,那排查錯誤、修正問題將會成爲一項異常艱難的工做。設計模式
由上面的圖能夠看出,JVM虛擬機中主要是由三部分構成,分別是類加載子系統、運行時數據區、執行引擎。
類加載子系統
Java虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的Java類型。
運行時數據區
Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域。這些區域有各自的用途,以及建立和銷燬的時間,有的區域隨着虛擬機進程的啓動而一直存在,有些區域則是依賴用戶線程的啓動和結束而創建和銷燬。
執行引擎
執行引擎用於執行JVM字節碼指令,主要有兩種方式,分別是解釋執行和編譯執行,區別在於,解釋執行是在執行時翻譯成虛擬機指令執行,而編譯執行是在執行以前先進行編譯再執行。解釋執行啓動快,執行效率低。編譯執行,啓動慢,執行效率高。垃圾回收器就是自動管理運行數據區的內存,將無用的內存佔用進行清除,釋放內存資源。
本地方法庫、本地庫接口
在jdk的底層中,有一些實現是須要調用本地方法完成的(使用c或c++寫的方法),就是經過本地庫接口調用完成的。好比:System.currentTimeMillis()方法。緩存
運行時數據區是jvm中最爲重要的部門。也是咱們在調優時須要重點關注的區域,下面咱們一塊兒瞭解下這個部分的具體內容。服務器
根據《Java虛擬機規範》中的規定,在運行時數據區將內存分爲方法區(Method Area)、Java堆區(Java
Heap)、Java虛擬機棧(Java Virtual Machine Stack)、程序計數器(Program Counter Register)、本地方法
棧(Native Method Stacks)。多線程
程序計數器(Program Counter Register)是一塊較小的內存空間,它能夠看做是當前線程所執行的字節碼的行號指示器。字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,它是程序控制流的指示器,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。架構
因爲Java虛擬機的多線程是經過線程輪流切換、分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(對於多核處理器來講是一個內核)都只會執行一條線程中的指令。所以,爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,咱們稱這類內存區域爲「線程私有」的內存。app
與程序計數器同樣,Java虛擬機棧也是線程私有的,它的生命週期與線程相同。Java虛擬機棧描述的是Java方法執行的線程內存模型:每一個方法被執行的時候,Java虛擬機都會同步建立一個棧幀,用於存儲局部變量表、操做數棧、動態鏈接、方法出口等信息。每個方法被調用直至執行完畢的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。jvm
以 int i = 1; 這樣代碼爲例,看看虛擬機棧的執行
本地方法棧(Native Method Stacks)與虛擬機棧所發揮的做用是很是類似的,其區別只是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的本地(Native)方法服務。
Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,Java世界裏「幾乎」全部的對象實例都在這裏分配內存。
須要注意的是,《Java虛擬機規範》並無對堆進行細緻的劃分,因此對於堆的講解要基於具體的虛擬機,咱們以使用最多的HotSpot虛擬機爲例進行講解。
Java堆是垃圾收集器管理的內存區域,所以它也被稱做「GC堆」,這就是咱們作JVM調優的重點區域部分。
由上圖能夠看出,jdk1.8的內存模型是由2部分組成,年輕代+ 年老代。
年輕代:Eden + 2*Survivor
年老代:OldGen
在jdk1.8中變化最大的Perm區,用Metaspace(元數據空間)進行了替換。
須要特別說明的是:Metaspace所佔用的內存空間不是在虛擬機內部,而是在本地內存空間中,這也是與1.7的永
久代最大的區別所在。
空間分配
若是沒有指定堆內存大小,默認初始堆內存爲物理內存的1/64,最大不超過物理內存的1/4或1G。注意的是元空間會自動擴容,默認狀況下不收限制。
官方給出的解釋是:移除永久代是爲融合HotSpot JVM與 JRockit VM而作出的努力,由於JRockit沒有永久代,不須要配置永久代。
Java程序會經過棧上的reference數據來操做堆上的具體對象。
主流的訪問方式主要有使用句柄和直接指針兩種:
句柄訪問
Java堆中將可能會劃分出一塊內存來做爲句柄池,reference中存儲的就是對象的句柄地址,而句柄中包含了對象實例數據與類型數據各自具體的地址信息.使用直接指針訪問Java堆中對象的內存佈局就必須考慮如何放置訪問類型數據的相關信息,reference中存儲的直接就是對象地址,若是隻是訪問對象自己的話,就不須要多一次間接訪問的開銷
指針訪問
使用句柄來訪問的最大好處就是reference中存儲的是穩定句柄地址,在對象被移動(垃圾收集時移動對象是很是廣泛的行爲)時只會改變句柄中的實例數據指針,而reference自己不須要被修改。使用直接指針來訪問最大的好處就是速度更快,它節省了一次指針定位的時間開銷。HotSpot虛擬機採用的是指針訪問方式實現。